spring 框架介绍

核心容器

核心容器由以下模块组成,spring-core, spring-beans,spring-context,spring-context-support,和spring-expression (Spring表达式语言)。

spring-core和spring-beans模块提供了框架的基础功能,包括IOC和依赖注入功能。 BeanFactory是一个成熟的工厂模式的实现。你不再需要编程去实现单例模式,允许你把依赖关系的配置和描述从程序逻辑中解耦。

上下文(spring-context)模块建立在由Core和Beans模块提供的坚实的基础上:它提供一个框架式的对象访问方式,类似于一个JNDI注册表。上下文模块从Beans模块继承其功能,并添加支持国际化(使用,例如,资源集合),事件传播,资源负载,并且透明创建上下文,例如,Servlet容器。Context模块还支持Java EE的功能,如EJB,JMX和基本的远程处理。ApplicationContext接口是Context模块的焦点。 spring-context-support支持整合普通第三方库到Spring应用程序上下文,特别是用于高速缓存(ehcache,JCache)和调度(CommonJ,Quartz)的支持。

spring-expression模块提供了强大的表达式语言去支持查询和操作运行时对象图。这是对JSP 2.1规范中规定的统一表达式语言(unified EL)的扩展。该语言支持设置和获取属性值,属性分配,方法调用,访问数组,集合和索引器的内容,逻辑和算术运算,变量命名以及从Spring的IoC容器中以名称检索对象。 它还支持列表投影和选择以及常见的列表聚合。

AOP和Instrumentation

spring-aop模块提供了一个符合AOP联盟(要求)的面向方面的编程实现,例如,允许您定义方法拦截器和切入点(pointcuts),以便干净地解耦应该被分离的功能实现。 使用源级元数据(source-level metadata)功能,您还可以以类似于.NET属性的方式将行为信息合并到代码中。

单独的spring-aspects模块,提供了与AspectJ的集成。

spring-instrument模块提供了类植入(instrumentation)支持和类加载器的实现,可以应用在特定的应用服务器中。该spring-instrument-tomcat 模块包含了支持Tomcat的植入代理。

消息

Spring框架4包括spring-messaging(消息传递模块),其中包含来自Spring Integration的项目,例如,Message,MessageChannel,MessageHandler,和其他用来传输消息的基础应用。该模块还包括一组用于将消息映射到方法的注释(annotations),类似于基于Spring MVC注释的编程模型。

数据访问/集成

数据访问/集成层由JDBC,ORM,OXM,JMS和事务模块组成。

spring-jdbc模块提供了一个JDBC –抽象层,消除了需要的繁琐的JDBC编码和数据库厂商特有的错误代码解析。

spring-tx模块支持用于实现特殊接口和所有POJO(普通Java对象)的类的编程和声明式事务 管理。

spring-orm模块为流行的对象关系映射(object-relational mapping )API提供集成层,包括JPA和Hibernate。使用spring-orm模块,您可以将这些O / R映射框架与Spring提供的所有其他功能结合使用,例如前面提到的简单声明性事务管理功能。

spring-oxm模块提供了一个支持对象/ XML映射实现的抽象层,如JAXB,Castor,JiBX和XStream。

spring-jms模块(Java Messaging Service) 包含用于生产和消费消息的功能。自Spring Framework 4.1以来,它提供了与 spring-messaging模块的集成。

Web

Web层由spring-web,spring-webmvc和spring-websocket 模块组成。

spring-web模块提供基本的面向Web的集成功能,例如多部分文件上传功能,以及初始化一个使用了Servlet侦听器和面向Web的应用程序上下文的IoC容器。它还包含一个HTTP客户端和Spring的远程支持的Web相关部分。

spring-webmvc模块(也称为Web-Servlet模块)包含用于Web应用程序的Spring的模型-视图-控制器(MVC)和REST Web Services实现。 Spring的MVC框架提供了领域模型代码和Web表单之间的清晰分离,并与Spring Framework的所有其他功能集成。

测试

spring-test模块支持使用JUnit或TestNG对Spring组件进行单元测试集成测试。它提供了Spring ApplicationContexts的一致加载和这些上下文的缓存。它还提供可用于独立测试代码的模仿(mock)对象

Spring IoC和beans的介绍

IoC又叫依赖注入(DI)。它描述了对象的定义和依赖的一个过程,也就是说,依赖的对象通过构造参数、工厂方法参数或者属性注入,当对象实例化后依赖的对象才被创建,当创建bean后容器注入这些依赖对象。这个过程基本上是反向的,因此命名为控制反转(IoC),它通过直接使用构造类来控制实例化,或者定义它们之间的依赖关系,或者类似于服务定位模式的一种机制。

org.springframework.beans和org.springframework.context是Spring框架中IoC容器的基础,BeanFactory接口提供一种高级的配置机制能够管理任何类型的对象。ApplicationContext是BeanFactory的子接口。它能更容易集成Spring的AOP功能、消息资源处理(比如在国际化中使用)、事件发布和特定的上下文应用层比如在网站应用中的WebApplicationContext。

容器介绍

org.springframework.context.ApplicationContext接口代表了Spring Ioc容器,它负责实例化、配置、组装之前的beans。容器通过读取配置元数据获取对象的实例化、配置和组装的描述信息。它配置的0元数据用xml、Java注解或Java代码表示。它允许你表示组成你应用的对象以及这些对象之间丰富的内部依赖关系。

Spring提供几个开箱即用的ApplicationContext接口的实现类。在独立应用程序中通常创建一个ClassPathXmlApplicationContextFileSystemXmlApplicationContext实例对象。虽然XML是用于定义配置元数据的传统格式,你也可以指示容器使用Java注解或代码作为元数据格式,但要通过提供少量XML配置来声明启用对这些附加元数据格式的支持。

在大多数应用场景中,显示用户代码不需要实例化一个或多个Spring IoC容器的实例。比如在web应用场景中,在web.xml中简单的8行(或多点)样板式的xml配置文件就可以搞定(参见第3.15.4节“Web应用程序的便利的ApplicationContext实例化”)。如果你正在使用Eclipse开发环境中的Spring Tool Suite插件,你只需要鼠标点点或者键盘敲敲就能轻松搞定这几行配置。

配置bean的元数据

配置元数据传统上以简单直观的XML格式提供

基于XML的元数据不是允许配置元数据的唯一形式,Spring IoC容器与实际写入配置元数据的格式是分离的。这些天许多的开发者在他们的Spring应用中选择基于Java配置

更多关于Spring容器使用其他形式的元数据信息,请查看:

  • 基于注解配置: 在Spring2.5中有过介绍支持基于注解的配置元数据
  • 基于Java配置: 从Spring3.0开始,由Spring JavaConfig提供的许多功能已经成为Spring框架中的核心部分。这样你可以使用Java程序而不是XML文件定义外部应用程序中的bean类。使用这些新功能,可以查看@Configuration,@Bean,@Import和@DependsOn这些注解

Spring配置由必须容器管理的一个或通常多个定义好的bean组成。基于XML配置的元数据中,这些bean通过标签定义在顶级标签内部。在Java配置中通常在使用@Configuration注解的类中使用@Bean注解方法。

基于目前spring被广泛应用和spring-boot的火爆,基于java的配置成为主流方式。

1
2
3
4
5
6
7
@Configuration 
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}

容器实例化

使用AnnotationConfigApplicationContext实例化Spring容器

下面的部分介绍Spring的AnnotationConfigApplicationContext,Spring 3.0的新内容。这个通用的ApplicationContext实现不仅可以接受@Configuration注解类为输入,还可以接受使用JSR-330元数据注解的简单类和@Component类。

当@Configuration注解的类作为输入时,@Configuration类本身会被注册为一个bean,在这个类中所有用@Bean注解的方法都会被定义为一个bean。

当使用@Component和JSR-330类时,它们被注册为bean的定义,并且假设在有必要时使用这些类内部诸如@Autowired或@Inject之类的DI元数据。

简单构造

实例化使用@Configuration类作为输入实例化AnnotationConfigApplicationContext和实例化ClassPathXmlApplicationContext时使用Spring的XML文件作为输入的方式大致相同。这在无XML配置的Spring容器时使用:

1
2
3
4
5
public static void main(String[] args) { 
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}

如上所述,AnnotationConfigApplicationContext不限于仅使用@Configuration类。任何@Component或JSR-330注解的类都可以作为输入提供给构造函数。例如:

1
2
3
4
5
public static void main(String[] args) { 
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}

上面假设MyServiceImpl、Dependency1和Dependency2都用了Spring的依赖注入的注解,例如@Autowired

使用register(Class<?>…)的方式构建容器

也可以使用无参构造函数实例化AnnotationConfigApplicationContext,然后使用register()方法配置。当使用编程方式构建AnnotationConfigApplicationContext时,这种方法特别有用。

使用scan(String …)组件扫描

启用组件扫描,只需要在你的@Configuration类中做如下配置:

1
2
3
4
5
@Configuration 
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
...
}

有Spring使用经验的用户,对Spring XML的context的声明非常熟悉:

1
2
3
<beans> 
<context:component-scan base-package="com.acme"/>
</beans>

在上面的例子中,com.acme将会被扫描,它会寻找任何@Component注解的类,这些类将会在Spring的容器中被注册成为一个bean。AnnotationConfigApplicationContext暴露的scan(String…)方法以达到相同组件扫描的功能:

1
2
3
4
5
6
public static void main(String[] args) { 
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}

记住:使用@Configuration注解的类是使用@Component进行元注解,所以它们也是组件扫描的候选,假设AppConfig被定义在com.acme这个包下(或者它下面的任何包),它们都会在调用scan()方法期间被找出来,然后在refresh()方法中它们所有的@Bean方法都会被处理,在容器中注册成为bean。

AnnotationConfigWebApplicationContext对于web应用的支持

AnnotationConfigApplicationContext在WebApplicationContext中的变体为
AnnotationConfigWebApplicationContext。当配置Spring ContextLoaderListener servlet 监听器、Spring MVC DispatcherServlet的时候,可以用此实现。下面为配置典型的Spring MVC DispatcherServlet的web.xml代码段。注意contextClass上下文参数和init-param的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<web-app> 
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>

<!-- Configuration locations must consist of one or more comma- or space-delimited
fully-qualified @Configuration classes. Fully-qualified packages may also be
specified for component-scanning -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>

<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- Declare a Spring MVC DispatcherServlet as usual -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- Again, config locations must consist of one or more comma- or space-delimited
and fully-qualified @Configuration classes -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>

<!-- map all requests for /app/* to the dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>

使用@Bean注解

@Bean是XML元素方法级注解的直接模拟。它支持由提供的一些属性,例如:init-methoddestroy-methodautowiring和name。

您可以在@Configuration@Component注解的类中使用@Bean注解。

定义一个bean

要定义一个bean,只需在一个方法上使用@Bean注解。您可以使用此方法在指定方法返回值类型的ApplicationContext中注册bean定义。默认情况下,bean名称与方法名称相同。以下是@Bean方法声明的简单示例:

1
2
3
4
5
6
7
8
9
@Configuration 
public class AppConfig {

@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}

}

这种配置完全和下面的Spring XML配置等价:

1
2
3
<beans> 
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

两种声明都可以使得一个名为transferService的bean在ApplicationContext可用,绑定到TransferServiceImpl类型的对象实例上:

transferService -> com.acme.TransferServiceImpl

Bean 依赖

@Bean注解方法可以具有描述构建该bean所需依赖关系的任意数量的参数。例如,如果我们的TransferService需要一个AccountRepository,我们可以通过一个方法参数实现该依赖:

1
2
3
4
5
6
7
8
@Configuration 
public class AppConfig {

@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}

这种解决原理和基于构造函数的依赖注入几乎相同,请参考相关章节,查看详细信息。

生命周期回调

任何使用了@Bean定义了的类都支持常规生命周期回调,并且可以使用JSR-250中的@PostConstruct@PreDestroy注解,详细信息,参考JSR-250注解。

完全支持常规的Spring生命周期回调。如果一个bean实现了InitializingBean,DisposableBean或Lifecycle接口,它们的相关方法就会被容器调用。

完全支持*Aware系列的接口,例如:BeanFactoryAware,BeanNameAware,MessageSourceAware,ApplicationContextAware等。

一个完整的基于java配置的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Configuration 
public class ServiceConfig {

@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}

}

@Configuration
public class RepositoryConfig {

@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}

}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

@Bean
public DataSource dataSource() {
// return new DataSource
}

}

public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}

profiles 定义(不同环境)

Bean定义profiles是在核心容器中允许不同的bean在不同环境注册的机制。

@Profile

当一个或者多个特定的profiles被激活,@Profile注解允许你指定一个有资格的组件来注册。使用我们上面的例子,我们可以按照下面的重写dataSource配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration 
@Profile("dev")
public class StandaloneDataConfig {

@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {

@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}

如前所述,使用@Bean方法,通常会选择使用程序话的JNDI查找:要么使用Spring的 JndiTemplate/JndiLocatorDelegate帮助要么直接使用上面展示的JNDI InitialContext,而不是强制声明返回类型为FactoryBean的JndiObjectFactoryBean变体。

@Profile可以被用作为创建一个自定义组合注解的元注解。下面的例子定义了一个@Production注解,它可以被用作替换@Profile(“production”)的注解。

1
2
3
4
5
@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}

在仅仅包含一个特殊bean的配置类中,@Profile也可以被声明在方法级别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration 
public class AppConfig {

@Bean
@Profile("dev")
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}

@Bean
@Profile("production")
public DataSource productionDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}

XML对应元素的profile属性。我们上面的示例配置可以重写为下面的两个XML配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<beans profile="dev" 
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">

<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">

<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

激活profile

现在我们已经更新了我们的配置,我们仍然需要说明哪个profile是激活的。如果我们现在开始我们示例应用程序,我们将会看到一个NoSuchBeanDefinitionException被抛出,因为容器找不到一个名为dataSource的Spring bean。
激活一个profile可以通过多种方式完成,但是大多数情况下,最直接的办法就是通过存在ApplicationContext当中的环境变量的API进行编程:

1
2
3
4
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); 
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

除此之外,profiles也可以通过声明spring.profiles.active属性来激活,这个可以通过在系统环境变量,JVM系统属性,web.xml中的servlet上下文环境参数,甚至JNDI的入口(请参考 3.13.3, “PropertySource abstraction”)。在集成测试中,激活profiles可以通过在Spring-test模块中的@ActiveProfiles注解来声明(参见“使用profiles来配置上下文环境”章节)。

注意,profiles不是“二者选一”的命题;它可以一次激活多个profiles。以编程的方式来看,简单的传递多个profile名字给接受String 可变变量参数的setActiveProfiles()方法:

1
ctx.getEnvironment().setActiveProfiles("profile1", "profile2"); 

在声明式中,spring.profiles.active可以接受以逗号分隔的profile 名称列表:

1
-Dspring.profiles.active="profile1,profile2" 

默认的profile

默认配置文件表示默认启用的配置文件。考虑以下几点:

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration 
@Profile("default")
public class DefaultDataConfig {

@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}

如果没有profile是激活状态,上面的dataSource将会被创建;这种方式可以被看做是对一个或者多个bean提供了一种默认的定义方式。如果启用任何的profile,那么默认的profile都不会被应用。
在上下文环境可以使用setDefaultProfiles()或者spring.profiles.default属性来修改默认的profile名字。

属性源抽象

Spring 环境抽象提供了可配置的属性源层次结构的搜索操作。为了充分的解释,请考虑下面的例子:

1
2
3
4
ApplicationContext ctx = new GenericApplicationContext(); 
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);

在上面的代码段中,我们看到了一个高级别的方法来要求Spring是否为当前环境定义foo属性。为了回答这个问题,环境对象对一组PropertySource对象执行搜索。一个PropertySource是对任何key-value资源的简单抽象,并且Spring 的标准环境是由两个PropertySource配置的,一个表示一系列的JVM 系统属性(System.getProperties()),一个表示一系列的系统环境变量(System.getenv())。

这些默认的属性资源存在于StandardEnvironment,可以在应用中独立使用。StandardServletEnvironment包含其他默认的属性资源,包括servlet配置和servlet上下文参数。它可以选择性的启用JndiPropertySource。详细信息请查看javadocs。

具体的说,当使用StandardEnvironment时,如果在运行时系统属性或者环境变量中包括foo,那么调用env.containsProperty(“foo”)方法将会返回true。

搜索是按照层级执行的。默认情况,系统属性优先于环境变量,所以这两个地方同时存在属性foo的时候,调用env.getProperty(“foo”)将会返回系统属性中的foo值。注意,属性值不会被合并而是被之前的值覆盖。对于一个普通的StandardServletEnvironment,它完整的层次结构如下,最顶端的优先级最高:

  • ServletConfig参数(如果适用,例如DispatcherServlet上下文环境)
  • ServletContext参数(web.xml中的context-param)
  • JNDI环境变量(“java:comp/env/”)
  • JVM系统属性(“-D”命令行参数)
  • JVM系统环境变量(操作系统环境变量)

更重要的是,整个机制都是可配置的。也许你有个自定义的属性来源,你想把它集成到这个搜到里面。这也没问题,只需简单的实现和实例化自己的PropertySource,并把它添加到当前环境的PropertySources集合中:

1
2
3
ConfigurableApplicationContext ctx = new GenericApplicationContext(); 
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

在上面的代码中,MyPropertySource被添加到搜索中的最高优先级。如果它包含了一个foo属性,在任何其他的PropertySource中的foo属性之前它会被检测到并返回。MutablePropertySources API暴露了很多允许精确操作该属性源集合的方法。

@PropertySource

@PropertySource注解对添加一个PropertySource到Spring的环境变量中提供了一个便捷的和声明式的机制。
给出一个名为”app.properties”的文件,它含了testbean.name=myTestBean的键值对,下面的@Configuration类使用@PropertySource的方式来调用testBean.getName(),将会返回”myTestBean”。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration 
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;

@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}

任何出现在@PropertySource中的资源位置占位符都会被注册在环境变量中的资源解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration 
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;

@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}

假设”my.placeholder”已经在其中的一个资源中被注册,例如:系统属性或环境变量,占位符将会被正确的值解析。如果没有,”default/path”将会使用默认值。如果没有默认值,而且无法解释属性,则抛出IllegalArgumentException异常。

容器(ApplicationContext)的额外功能

org.springframework.beans.factory包提供基本的功能来管理和操作bean,包括以编程的方式。The org.springframework.context包增加了ApplicationContext接口,它继承了BeanFactory接口,除了以面向应用框架的风格扩展接口来提供一些额外的功能。

为了增强BeanFactory在面向框架风格的功能,上下文的包还提供了以下的功能:

  • 通过MessageSource接口访问i18n风格的消息
  • 通过ResourceLoader接口访问类似URL和文件资源
  • 通过ApplicationEventPublisher接口,即bean实现ApplicationListener接口来进行事件发布
  • 通过HierarchicalBeanFactory接口实现加载多个(分层)上下文,允许每个上下文只关注特定的层,例如应用中的web层

标准和自定义事件

ApplicationEvent类和ApplicationListener接口提供了ApplicationContext中的事件处理。如果一个bean实现了ApplicationListener接口,然后它被部署到上下问中,那么每次ApplicationEvent发布到ApplicationContext中时,bean都会收到通知。本质上,这是观察者模型。

从Spring 4.2开始,事件的基础得到了重要的提升,并提供了基于注解模型及任意事件发布的能力,这个对象不一定非要继承ApplicationEvent。当这个对象被发布时,我们把他包装在事件中。

Spring提供了一下的标准事件:

内置事件

事件 解释
ContextRefreshedEvent 当ApplicationContext被初始化或者被刷新的时候发布,例如,在ConfigurableApplicationContext接口上调用refresh()方法。”初始化”在这里意味着所有的bean被加载,后置处理器被检测到并且被激活,单例的预加载,以及ApplicationContext对象可以使用。只要上下文还没有被关闭,refresh就可以被触发多次,前提所选的ApplicationContext支持热刷新。例如,XmlWebApplicationContext支持热刷新,而GenericApplicationContext不支持。
ContextStartedEvent 当ApplicationContext启动时发布,在ConfigurableApplicationContext接口上调用start()方法。”已启动”意味着所有bean的生命周期会接受到一个明确的启动信号。通常这个信号用来停止后的重启,但是他也可以被用来启动没有配置为自动启动的组件,例如,在初始化时还没启动的组件。
ContextStoppedEvent 当ApplicationContext 停止时发布,在ConfigurableApplicationContext接口上调用stop()方法。”停止”意味这所有的bean的生命周期都会受到一个明确的停止信号。通过调用start()方法可以重启一个已经停止的上下文。
ContextClosedEvent 当ApplicationContext 关闭时发布,在ConfigurableApplicationContext接口上调用close()方法。”关闭”意味着所有的单例bean都会被销毁。关闭的上下文就是它生命周期的末尾。它不能刷新或者重启。
RequestHandledEvent 接受一个HTTP请求的时候,一个特定的web时间会通知所有的bean。这个时间的发布是在请求完成。此事件仅适用于使用Spring的DispatcherServlet的Web应用程序。

你可以创建并发布自己的自定义事件。这个例子演示了一个继承Spring ApplicationEvent的简单类:

1
2
3
4
5
6
7
8
9
10
public class BlackListEvent extends ApplicationEvent { 
private final String address;
private final String test;
public BlackListEvent(Object source, String address, String test) {
super(source);
this.address = address;
this.test = test;
}
// accessor and other methods...
}

为了发布一个自定义的ApplicationEvent,在ApplicationEventPublisher中调用publishEvent()方法。通常在实现了ApplicationEventPublisherAware接口并把它注册为一个Spring bean的时候它就完成了。下面的例子展示了这么一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class EmailService implements ApplicationEventPublisherAware { 

private List<String> blackList;
private ApplicationEventPublisher publisher;

public void setBlackList(List<String> blackList) {
this.blackList = blackList;
}

public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}

public void sendEmail(String address, String text) {
if (blackList.contains(address)) {
BlackListEvent event = new BlackListEvent(this, address, text);
publisher.publishEvent(event);
return;
}
// send email...
}

}

在配置时,Spring容器将检测到EmailService实现了ApplicationEventPublisherAware,并将自动调用setApplicationEventPublisher()方法。实际上,传入的参数将是Spring容器本身;您只需通过ApplicationEventPublisher接口与应用程序上下文进行交互。

为了自定义ApplicationEvent,创建一个试下了ApplicationListener的类并把他注册为一个Spring bean。下面例子展示这样一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BlackListNotifier implements ApplicationListener<BlackListEvent> { 

private String notificationAddress;

public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}

public void onApplicationEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}

}

请注意,ApplicationListener通常用你自定义的事件BlackListEvent类型参数化的。这意味着onApplicationEvent()方法可以保持类型安全,避免向下转型的需要。您可以根据需要注册许多的事件侦听器,但请注意,默认情况下,事件侦听器将同步接收事件。这意味着publishEvent()方法会阻塞直到所有的监听者都处理完。这种同步和单线程方法的一个优点是,如果事务上下文可用,它就会在发布者的事务上下文中处理。如果必须需要其他的时间发布策略,请参考javadoc的 Spring ApplicationEventMulticaster 接口。

下面例子展示了使用配置和注册上述每个类的bean定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
<bean id="emailService" class="example.EmailService"> 
<property name="blackList">
<list>
<value>known.spammer@example.org</value>
<value>known.hacker@example.org</value>
<value>john.doe@example.org</value>
</list>
</property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
<property name="notificationAddress" value="blacklist@example.org"/>
</bean>

把他们放在一起,当调用emailService的sendEmail()方法时,如果有任何应该被列入黑名单的邮件,那么自定义的BlackListEvent事件会被发布。blackListNotifier 会被注册为一个ApplicationListener,从而接受BlackListEvent,届时通知适当的参与者。

Spring 的事件机制的设计是用在Spring bean和相同应用上下文的简单通讯。然而,对于更复杂的企业集成需求,单独维护Spring Integration工程对构建著名的Spring编程模型轻量级,面向模式,事件驱动架构提供了完整的支持。

基于注解的事件监听器

从Spring 4.2开始,一个事件监听器可以通过EventListener注解注册在任何managed bean的公共方法上。BlackListNotifier可以重写如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BlackListNotifier { 

private String notificationAddress;

public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}

@EventListener
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}

}

如上所示,方法签名实际上会推断出它监听的是哪一个类型的事件。这也适用于泛型嵌套,只要你在过滤的时候可以根据泛型参数解析出实际的事件。

如果你的方法需要监听好几个事件或根本没有参数定义它,事件类型也可以用注解本身指明:

1
2
3
4
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class}) 
public void handleContextStart() {

}

对特殊的时间调用方法,根据定义的SpEL表达式来匹配实际情况,通过条件属性注解,
也可以通过condition注解来添加额外的运行过滤,它对一个特殊事件的方法实际调用是根据它是否匹配condition注解所定义的SpEL表达式。

例如,只要事件的测试属性等于foo,notifier可以被重写为只被调用:

1
2
3
4
@EventListener(condition = "#blEvent.test == 'foo'") 
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}

每个SpEL表达式在此评估专用的上下文。下表列出的条目存在上下文中可用,所以可以调用他们处理conditional事件:

表3.8. 存在元数据中的Event SpEL 表达式

名字 位置 描述 例子
事件 根路径 实际的ApplicationEvent #root.event
参数数组 根路径 参数(数组) 目标调用 #root.args[0]
参数名字 上下文 任何的方法参数名称。如果由于某些名称的原因而不可用(例如:没有调试信息),参数名称也会存在#a<#arg>, #arg代表参数索引开始的地方(从0开始) #blEvent 或 #a0(也可以使用#p0 or #p<#arg> 作为别名)

注意,#root.event允许你访问底层的时间,即使你的方法签名实际上是指已发布的任意对象。

如果您需要发布一个事件作为处理另一个事件的结果,只需更改方法签名来返回应该被发布的事件,如下所示:

1
2
3
4
5
@EventListener 
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}

异步监听器不支持这个特性

这个新方法将对上述方法处理的每个BlackListEvent都会发布一个新的ListUpdateEvent。如果需要发布多个时间,只需要返回事件集合即可。

异步监听器

如果你希望一个特定的监听器去异步处理事件,只需要重新使用常规的@Async支持:

1
2
3
4
5
@EventListener 
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}

当使用异步事件的时候有下面两个限制:

1、 如果事件监听器抛出异常,则不会将其传播给调用者,查看AsyncUncaughtExceptionHandler获取详细信息;
2、 此类事件监听器无法发送回复如果你需要将处理结果发送给另一个时间,注入ApplicationEventPublisher里面手动发送事件;

顺序的监听器

如果你需要一个监听器在另一个监听器调用前被调用,只需要在方法声明上添加@Order注解:

1
2
3
4
5
@EventListener 
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}

泛型事件

你可以使用泛型来进一步的定义事件的结构。考虑EntityCreatedEvent,T的类型就是你要创建的真实类型。你可以创建下面的监听器定义,它只接受Person类型的EntityCreatedEvent:

1
2
3
4
@EventListener 
public void onPersonCreated(EntityCreatedEvent<Person> event) {
...
}

触发了事件解析泛型参数,
由于类型擦除,只有在触发了事件解析事件监听过器滤的泛型参数(类似于PersonCreatedEvent继承了EntityCreatedEvent { … }),此操作才会起作用。

在某些情况下,如果所有的时间都遵循相同的结果(上述事件应该是这样),这可能有点冗余。在这种情况下,你可以实现ResolvableTypeProvider来引导超出框架运行是环境提供的范围:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class EntityCreatedEvent<T> 
extends ApplicationEvent implements ResolvableTypeProvider {

public EntityCreatedEvent(T entity) {
super(entity);
}

@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(),
ResolvableType.forInstance(getSource()));
}
}

这不仅适用于ApplicationEvent,还可以作为时间发送的任意对象。

关于beanFactory

BeanFactory为Spring的IoC功能提供了底层的基础,但是它仅仅被用于和第三方框架的集成,现在对于大部分的Spring用户来说都是历史了。BeanFactory及其相关的接口,例如:BeanFactoryAware,InitializingBean,DisposableBean,在Spring中仍然有所保留,目的就是为了让大量的第三方框架和Spring集成时保持向后兼容。

BeanFactory or ApplicationContext?

尽量使用ApplicationContext除非你有更好的理由不用它。
因为ApplicationContext包括了BeanFactory的所有功能,通常也优于BeanFactory,除非一些少数的场景,例如:在受资源约束的嵌入式设备上运行一个嵌入式应用,它的内存消耗可能至关重要,并且可能会产生字节。然而,对于大多数典型的企业级应用和系统来说,ApplicationContext才是你想使用的。Spring大量使用了BeanPostProcessor扩展点(以便使用代理等)。如果你仅仅只使用简单的BeanFactory,很多的支持功能将不会有效,例如:事务和AOP,但至少不会有额外的步骤。

特性矩阵

Feature BeanFactory ApplicationContext
Bean实例化/装配
BeanPostProcessor自动注册
BeanFactoryPostProcessor自动注册
MessageSource便捷访问(针对i18n)
ApplicationEvent 发布

spring 的资源管理(Resource)

Resource抽象在Spring本身被广泛使用,作为需要资源的许多方法签名中的参数类型。 某些Spring API中的其他方法(例如各种ApplicationContext实现的构造函数)采用一个String,它以未安装或简单的形式用于创建适用于该上下文实现的资源,或者通过String路径上的特殊前缀,允许调用者 以指定必须创建和使用特定的资源实现。

Resource 接口

Resource 接口(实现)不仅可以被 spring 大量的应用,其也非常适合作为你编程中访问资源的辅助工具类。当你仅需要使用到 Resource 接口实现时,可以直接忽略 spring 的其余部分。单独使用 Rsourece 实现,会造成代码与 spring 的部分耦合,可也仅耦合了其中一小部分辅助类,而且你可以将 Reource 实现作为 URL 的一种访问底层更为有效的替代,与你引入其他库来达到这种目的是一样的。

需要注意的是 Resource 实现并没有去重新发明轮子,而是尽可能地采用封装。举个例子,UrlResource 里就封装了一个 URL 对象,在其内的逻辑就是通过封装的 URL 对象来完成的。

spring 直接提供了多种开箱即用的 Resource 实现。

UrlResource

UrlResource 封装了一个 java.net.URL 对象,用来访问 URL 可以正常访问的任意对象,比如文件、an HTTP target, an FTP target, 等等。所有的 URL 都可以用一个标准化的字符串来表示。如通过正确的标准化前缀,可以用来表示当前 URL 的类型,当中就包括用于访问文件系统路径的 file:,通过 http 协议访问资源的 http:,通过 ftp 协议访问资源的 ftp:,还有很多……

可以显式化地使用 UrlResource 构造函数来创建一个 UrlResource,不过通常我们可以在调用一个 api 方法是,使用一个代表路径的 String 参数来隐式创建一个 UrlResource。对于后一种情况,会由一个 javabean PropertyEditor 来决定创建哪一种 Resource。如果路径里包含某一个通用的前缀(如 classpath:),PropertyEditor 会根据这个通用的前缀来创建恰当的 Resource;反之,如果 PropertyEditor 无法识别这个前缀,会把这个路径作为一个标准的 URL 来创建一个 UrlResource。

ClassPathResource

ClassPathResource 可以从类路径上加载资源,其可以使用线程上下文加载器、指定加载器或指定的 class 类型中的任意一个来加载资源。

当类路径上资源存于文件系统中,ClassPathResource 支持以 java.io.File 的形式访问,可当类路径上的资源存于尚未解压(没有 被Servlet 引擎或其他可解压的环境解压)的 jar 包中,ClassPathResource 就不再支持以 java.io.File 的形式访问。鉴于上面所说这个问题,spring 中各式 Resource 实现都支持以 jave.net.URL 的形式访问。

可以显式使用 ClassPathResource 构造函数来创建一个 ClassPathResource ,不过通常我们可以在调用一个 api 方法时,使用一个代表路径的 String 参数来隐式创建一个 ClassPathResource。对于后一种情况,会由一个 javabean PropertyEditor 来识别路径中 classpath: 前缀,从而创建一个 ClassPathResource。

FileSystemResource

这是针对 java.io.File 提供的 Resource 实现。显然,我们可以使用 FileSystemResource 的 getFile() 函数获取 File 对象,使用 getURL() 获取 URL 对象。

ServletContextResource

这是为了获取 web 根路径的 ServletContext 资源而提供的 Resource 实现。

ServletContextResource 完全支持以流和 URL 的方式访问,可只有当 web 项目是已解压的(不是以 war 等压缩包形式存在)且该 ServletContext 资源存于文件系统里,ServletContextResource 才支持以 java.io.File 的方式访问。至于说到,我们的 web 项目是否已解压和相关的 ServletContext 资源是否会存于文件系统里,这个取决于我们所使用的 Servlet 容器。若 Servlet 容器没有解压 web 项目,我们可以直接以 JAR 的形式的访问,或者其他可以想到的方式(如访问数据库)等。

InputStreamResource

这是针对 InputStream 提供的 Resource 实现。建议,在确实没有找到其他合适的 Resource 实现时,才使用 InputSteamResource。如果可以,尽量选择 ByteArrayResource 或其他基于文件的 Resource 实现来代替。

与其他Resource 实现已比较,InputStreamRsource 倒像一个已打开资源的描述符,因此,调用 isOpen() 方法会返回 true。除了在需要获取资源的描述符或需要从输入流多次读取时,都不要使用 InputStreamResource 来读取资源。

ByteArrayResource

这是针对字节数组提供的 Resource 实现。可以通过一个字节数组来创建 ByteArrayResource。

当需要从字节数组加载内容时,ByteArrayResource 是一个不错的选择,使用 ByteArrayResource 可以不用求助于 InputStreamResource。

ResourceLoader 接口

ResourceLoader 接口是用来加载 Resource 对象的,换句话说,就是当一个对象需要获取 Resource 实例时,可以选择实现 ResourceLoader 接口。

1
2
3
4
5
public interface ResourceLoader { 

Resource getResource(String location);

}

spring 里所有的应用上下文都是实现了 ResourceLoader 接口,因此,所有应用上下文都可以通过 getResource() 方法获取 Resource 实例。

当你在指定应用上下文调用 getResource() 方法时,而指定的位置路径又没有包含特定的前缀,spring 会根据当前应用上下文来决定返回哪一种类型 Resource。举个例子,假设下面的代码片段是通过 ClassPathXmlApplicationContext 实例来调用的,

1
Resource template = ctx.getResource("some/resource/path/myTemplate.txt"); 

那spring 会返回一个 ClassPathResource 对象;

类似的,如果是通过实例 FileSystemXmlApplicationContext 实例调用的,返回的是一个 FileSystemResource 对象;如果是通过 WebApplicationContext 实例的,返回的是一个 ServletContextResource 对象…… 如上所说,你就可以在指定的应用上下中使用 Resource 实例来加载当前应用上下文的资源。

还有另外一种场景里,如在其他应用上下文里,你可能会强制需要获取一个 ClassPathResource 对象,这个时候,你可以通过加上指定的前缀来实现这一需求,如:

1
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt"); 

类似的,你可以通过其他任意的 url 前缀来强制获取 UrlResource 对象:

1
2
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt"); 
Resource template = ctx.getResource("http://myhost.com/resource/path/myTemplate.txt");

下面,给出一个表格来总结一下 spring 根据各种位置路径加载资源的策略:

Table 4.1. Resource strings

前缀 例子 解释
classpath: classpath:com/myapp/config.xml 从类路径加载
file: file:///data/config.xml 以URL形式从文件系统加载
http: http://myserver/logo.png 以URL形式加载
(none) /data/config.xml 由底层的ApplicationContext实现决定

spring表达式语言(SpEL)

Spring Expression Language(简称SpEL)是一种功能强大的表达式语言、用于在运行时查询和操作对象图;语法上类似于Unified EL,但提供了更多的特性,特别是方法调用和基本字符串模板函数。

SpEL支持以下的一些特性:

  • 字符表达式
  • 布尔和关系操作符
  • 正则表达式
  • 类表达式
  • 访问properties,arrays,lists,maps等集合
  • 方法调用
  • 关系操作符
  • 赋值
  • 调用构造器
  • Bean对象引用
  • 创建数组
  • 内联lists
  • 内联maps
  • 三元操作符
  • 变量
  • 用户自定义函数
  • 集合投影
  • 集合选择
  • 模板表达式

spring框架下的Test

单元测试以及spring提供的注解

Spring测试注解

Spring框架提供以下Spring特定的注解集合,你可以在单元和集成测试中协同TestContext框架使用它们。请参考相应的JAVA帮助文档作进一步了解,包括默认的属性,属性别名等等。、

@BootstrapWith

@BootstrapWith是一个用于配置Spring TestContext框架如何引导的类级别的注解。具体地说,@BootstrapWith用于指定一个自定义的TestContextBootstrapper。请查看引导TestContext框架作进一步了解。

@ContextConfiguration

@ContextConfiguration定义了类级别的元数据来决定如何为集成测试来加载和配置应用程序上下文。具体地说,@ContextConfiguration声明了用于加载上下文的应用程序上下文资源路径和注解类。

资源路径通常是类路径中的XML配置文件或者Groovy脚本;而注解类通常是使用@Configuration注解的类。但是,资源路径也可以指向文件系统中的文件和脚本,解决类也可能是组件类等等。

1
2
3
4
5
6
7
8
@ContextConfiguration("/test-config.xml") 
public class XmlApplicationContextTests {
// class body...
}
@ContextConfiguration(classes = TestConfig.class)
public class ConfigClassApplicationContextTests {
// class body...
}

作为声明资源路径或注解类的替代方案或补充,@ContextConfiguration可以用于声明ApplicationContextInitializer类。

1
2
3
4
@ContextConfiguration(initializers = CustomContextIntializer.class) 
public class ContextInitializerTests {
// class body...
}

@ContextConfiguration偶尔也被用作声明ContextLoader策略。但注意,通常你不需要显示的配置加载器,因为默认的加载器已经支持资源路径或者注解类以及初始化器。

1
2
3
4
@ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) 
public class CustomLoaderXmlApplicationContextTests {
// class body...
}

@ContextConfiguration默认对继承父类定义的资源路径或者配置类以及上下文初始化器提供支持。

参阅[Section 11.5.4, 上下文管理](http://section 11.5.xn–4%2C context management-278prb/)和@ContextConfiguration帮助文档作进一步了解。

@WebAppConfiguration

@WebAppConfiguration是一个用于声明集成测试所加载的ApplicationContext须是WebApplicationContext的类级别的注解。测试类的@WebAppConfiguration注解只是为了保证用于测试的WebApplicationContext会被加载,它使用”file:src/main/webapp”路径默认值作为web应用的根路径(即,资源基路径)。资源基路径用于幕后创建一个MockServletContext作为测试的WebApplicationContext的ServletContext。

1
2
3
4
5
@ContextConfiguration 
@WebAppConfiguration("classpath:test-web-resources")
public class WebAppTests {
// class body...
}

注意@WebAppConfiguration必须和@ContextConfiguration一起使用,或者在同一个测试类,或者在测试类层次结构中。请参阅@WebAppConfiguration帮助文档作进一步了解。

@ContextHierarchy

@ContextHierarchy是一个用于为集成测试定义ApplicationContext层次结构的类级别的注解。@ContextHierarchy应该声明一个或多个@ContextConfiguration实例列表,其中每一个定义上下文层次结构的一个层次。下面的例子展示了在同一个测试类中@ContextHierarchy的使用方法。但是,@ContextHierarchy一样可以用于测试类的层次结构中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ContextHierarchy({ 
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
public class ContextHierarchyTests {
// class body...
}
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = AppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
public class WebIntegrationTests {
// class body...
}

如果你想合并或者覆盖一个测试类的层次结构中的应用程序上下文中指定层次的配置,你就必须在类层次中的每一个相应的层次通过为@ContextConfiguration的name属性提供与该层次相同的值的方式来显示地指定这个层次。请参阅上下文层次关系和@ContextHierarchy帮助文档来获得更多的示例。

@ActiveProfiles

@ActiveProfiles是一个用于当集成测试加载ApplicationContext的时候声明哪一个bean definition profiles被激活的类级别的注解。

1
2
3
4
5
6
7
8
9
10
@ContextConfiguration 
@ActiveProfiles("dev")
public class DeveloperTests {
// class body...
}
@ContextConfiguration
@ActiveProfiles({"dev", "integration"})
public class DeveloperIntegrationTests {
// class body...
}

@ActiveProfiles默认为继承激活的在超类声明的 bean definition profiles提供支持。通过实现一个自定义的 ActiveProfilesResolver并通过@ActiveProfiles的resolver属性来注册它的编程的方式来解决激活bean definition profiles问题也是可行的。

参阅使用环境profiles来配置上下文和@ActiveProfiles帮助文档作进一步了解。

参阅使用环境profiles来配置上下文和@ActiveProfiles帮助文档作进一步了解。

@TestPropertySource

@TestPropertySource是一个用于为集成测试加载ApplicationContext时配置属性文件的位置和增加到Environment中的PropertySources集中的内联属性的类级别的注解。

测试属性源比那些从系统环境或者Java系统属性以及通过@PropertySource或者编程方式声明方式增加的属性源具有更高的优先级。而且,内联属性比从资源路径加载的属性具有更高的优先级。

下面的例子展示了如何从类路径中声明属性文件。

1
2
3
4
5
@ContextConfiguration 
@TestPropertySource("/test.properties")
public class MyIntegrationTests {
// class body...
}

面的例子展示了如何声明内联属性。

1
2
3
4
5
@ContextConfiguration 
@TestPropertySource(properties = { “timezone = GMT”, “port: 4242” })
public class MyIntegrationTests {
// class body…
}

@DirtiesContext

@DirtiesContext指明测试执行期间该Spring应用程序上下文已经被弄脏(也就是说通过某种方式被更改或者破坏——比如,更改单例bean的状态)。当应用程序上下文被标为”脏”,它将从测试框架缓存中被移除并关闭。因此,Spring容器将为随后需要同样配置元数据的测试而被重建。

@DirtiesContext可以在同一个类或者类层次结构中的类级别和方法级别中使用。在这个场景下,应用程序上下文将在任意此注解的方法之前或之后以及当前测试类之前或之后被标为“脏”,这取决于配置的methodMode和classMode。

下面的例子解释了在多种配置场景下什么时候上下文会被标为“脏”。

  • 当在一个类中声明并将类模式设为BEFORE_CLASS,则在当前测试类之前。
1
2
3
4
@DirtiesContext(classMode = BEFORE_CLASS) 
public class FreshContextTests {
// some tests that require a new Spring container
}
  • 当在一个类中声明并将类模式设为AFTER_CLASS(也就是,默认的类模式),则在当前测试类之后。
1
2
3
4
@DirtiesContext 
public class ContextDirtyingTests {
// some tests that result in the Spring container being dirtied
}
  • 当在一个类中声明并将类模式设为BEFORE_EACH_TEST_METHOD,则在当前测试类的每个方法之前。
1
2
3
4
@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) 
public class FreshContextTests {
// some tests that require a new Spring container
}
  • 当在一个类中声明并将类模式设为AFTER_EACH_TEST_METHOD,则在当前测试类的每个方法之后。
1
2
3
4
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) 
public class ContextDirtyingTests {
// some tests that result in the Spring container being dirtied
}
  • 当在一个方法中声明并将方法模式设为BEFORE_METHOD,则在当前方法之前。
1
2
3
4
5
@DirtiesContext(methodMode = BEFORE_METHOD) 
@Test
public void testProcessWhichRequiresFreshAppCtx() {
// some logic that requires a new Spring container
}
  • 当在一个方法中声明并将方法模式设为AFTER_METHOD(也就是说,默认的方法模式),则在当前方法之后。
1
2
3
4
5
@DirtiesContext 
@Test
public void testProcessWhichDirtiesAppCtx() {
// some logic that results in the Spring container being dirtied
}

如果@DirtiesContext被用于上下文被配置为通过@ContextHierarchy定义的上下文层次中的一部分的测试中,则hierarchyMode标志可用于控制如何声明上下文缓存。默认将使用一个穷举算法用于清除包括不仅当前层次而且与当前测试拥有共同祖先的其它上下文层次的缓存。所有在拥有共同祖先上下文的子层次的应用程序上下文都会从上下文中被移除并关闭。如果穷举算法对于特定的使用场景显得有点威力过猛,那么你可以指定一个更简单的当前层算法来代替,如下所。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@ContextHierarchy({ 
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
public class BaseTests {
// class body...
}

public class ExtendedTests extends BaseTests {

@Test
@DirtiesContext(hierarchyMode = CURRENT_LEVEL)
public void test() {
// some logic that results in the child context being dirtied
}
}

参阅DirtiesContext.HierarchyMode帮助文档以获得穷举和当前层算法更详细的了解。

@TestExecutionListeners

@TestExecutionListeners定义了一个类级别的元数据,用于配置需要用TestContextManager进行注册的TestExecutionListener实现。通常,@TestExecutionListeners与@ContextConfiguration一起使用。

1
2
3
4
5
@ContextConfiguration 
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class})
public class CustomTestExecutionListenerTests {
// class body...
}

@TestExecutionListeners默认支持继承监听器。参阅帮助文档获得示例和更详细的了解。

@Commit

@Commit指定事务性的测试方法在测试方法执行完成后对事务进行提交。@Commit可以用作@Rollback(false)的直接替代,以更好的传达代码的意图。和@Rollback一样,@Commit可以在类层次或者方法层级声明。

1
2
3
4
5
@Commit 
@Test
public void testProcessWithoutRollback() {
// ...
}

@Rollback

@Rollback指明当测试方法执行完毕的时候是否对事务性方法中的事务进行回滚。如果为true,则进行回滚;否则,则提交(请参加@Commit)。在Spring TestContext框架中,集成测试默认的Rollback语义为true,即使你不显示的指定它。

当被声明为方法级别的注解,则@Rollback为特定的方法指定回滚语义,并覆盖类级别的@Rollback@Commit语义。

1
2
3
4
5
@Rollback(false) 
@Test
public void testProcessWithoutRollback() {
// …
}

@BeforeTransaction

@BeforeTransaction指明通过Spring的@Transactional注解配置为需要在事务中执行的测试方法在事务开始之前先执行注解的void方法。从Spring框架4.3版本起,@BeforeTransaction方法不再需要为public并可能被声明为基于Java8的接口的默认方法。

1
2
3
4
@BeforeTransaction 
void beforeTransaction() {
// logic to be executed before a transaction is started
}

@AfterTransaction

@AfterTransaction指明通过Spring的@Transactional注解配置为需要在事务中执行的测试方法在事务结束之后执行注解的void方法。从Spring框架4.3版本起,@AfterTransaction方法不再需要为public并可能被声明为基于Java8的接口的默认方法。

1
2
3
4
@AfterTransaction 
void afterTransaction() {
// logic to be executed after a transaction has ended
}

@Sql

@Sql用于注解测试类或者测试方法,以让在集成测试过程中配置的SQL脚本能够在给定的的数据库中得到执行。

1
2
3
4
5
@Test 
@Sql({"/test-schema.sql", "/test-user-data.sql"})
public void userTest {
// execute code that relies on the test schema and test data
}

请参阅通过@sql声明执行的SQL脚本作进一步了解。

@SqlConfig

@SqlConfig定义了用于决定如何解析和执行通过@Sql注解配置的SQL脚本。

1
2
3
4
5
6
7
8
@Test 
@Sql(
scripts = "/test-user-data.sql",
config = @SqlConfig(commentPrefix = "", separator = "@@")
)
public void userTest {
// execute code that relies on the test data
}

@SqlGroup

@SqlGroup是一个用于聚合几个@Sql注解的容器注解。@SqlGroup可以直接使用,通过声明几个嵌套的@Sql注解,也可以与Java8的可重复注解支持协同使用,即简单地在同一个类或方法上声明几个@Sql注解,隐式地产生这个容器注解。

1
2
3
4
5
6
7
8
@Test 
@SqlGroup({
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "")),
@Sql("/test-user-data.sql")
)}
public void userTest {
// execute code that uses the test schema and test data
}

11.4.2 标准注解支持

以下注解为Spring TestContext 框架所有的配置提供标准语义支持。注意这些注解不仅限于测试,可以用在Spring框架的任意地方。

在Spring TestContext 框架中,@PostConstruct@PreDestroy 可以通过标准语义在配置于应用程序上下文的任意应用程序组件中使用; 但是, 这些生命周期注解在实际测试类中只有很有限的作用。如果一个测试类的方法被注解为@PostConstruct,这个方法将在test框架中的任何before方法(也就是被JUnit中的@Before注解方法)调用之前被执行, 这个规则将被应用于测试类的每个方法。另一方面,如果一个测试类的方法被注解为 @PreDestroy,这个方法将永远不会被执行。因为建议在测试类中使用test 框架的测试生命周期回调来代替使用@PostConstruct and @PreDestroy

spring 对于数据的访问

DAO支持

在Spring中数据访问对象(DAO)旨在使JDBC,Hibernate,JPA或JDO等数据访问技术有一致的处理方法,并且方法尽可能简单。
这样就可以很容易地切换上述持久化技术,并且切换过程无需担心每种技术的特有异常。

使用@Repository注解是数据访问对象(DAOs)或库能提供异常转换的最好方式,这个注解还允许组件扫描,查找并配置你的 DAOs 和库,并且不需要为它们提供 XML 配置文件。

1
2
3
4
@Repository 
public class SomeMovieFinder implements MovieFinder {
// ...
}

任何DAO或库实现都需要访问持久的源,依赖于持久化技术的使用;例如:一个基于JDBC的库需要访问一个JDBC DataSource,一个基于JPA的库需要访问一个 EntityManager,最简单的方式就是使用 @Autowired, @Inject, @Resource@PersistenceContext 这些注解中的一个完成资源的依赖注入,这是一个JPA库的例子:

1
2
3
4
5
6
7
8
9
@Repository 
public class JpaMovieFinder implements MovieFinder {

@PersistenceContext
private EntityManager entityManager;

// ...

}

如果你使用传统的Hibernate API,你可以注入SessionFactory:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Repository 
public class HibernateMovieFinder implements MovieFinder {

private SessionFactory sessionFactory;

@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}

// ...

}

最后一个例子我们将在这里展示典型的JDBC支持,你将会在初始化方法中注入 DataSource ,在初始化方法中,你将使用这个DataSource创建一个JdbcTemplate 和其他与SimpleJdbcCall相似的数据访问支持类。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Repository 
public class JdbcMovieFinder implements MovieFinder {

private JdbcTemplate jdbcTemplate;

@Autowired
public void init(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

// ...

}

对JDBC的支持

表格很清楚的列举了Spring框架针对JDBC操作做的一些抽象和封装。里面区分了哪些操作Spring已经帮你做好了、哪些操作是应用开发者需要自己负责的.

Spring JDBC – 框架和应用开发者各自分工

操作 Spring 开发者
定义连接参数 X
打开连接 X
指定SQL语句 X
声明参数和提供参数值 X
准备和执行语句 X
返回结果的迭代(如果有) X
具体操作每个迭代 X
异常处理 X
事务处理 X
关闭连接、语句和结果集 X

一句话、Spring帮你屏蔽了很多JDBC底层繁琐的API操作、让你更方便的开发

JdbcTemplate 是经典的Spring JDBC访问方式,也是最常用的。这是“最基础”的方式、其他所有方式都是在 JdbcTemplate的基础之上封装的。

JdbcTemplate 使用示例

这一节提供了JdbcTemplate类的一些使用例子。这些例子没有囊括JdbcTemplate可提供的所有功能;全部功能和用法请详见相关的javadocs.

查询 (SELECT)

下面是一个简单的例子、用于获取关系表里面的行数

1
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class); 

使用绑定变量的简单查询:

1
2
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject( 
"select count(*) from t_actor where first_name = ?", Integer.class, "Joe");

String查询:

1
2
3
String lastName = this.jdbcTemplate.queryForObject( 
"select last_name from t_actor where id = ?",
new Object[]{1212L}, String.class);

查询和填充领域模型:

1
2
3
4
5
6
7
8
9
10
11
Actor actor = this.jdbcTemplate.queryForObject( 
"select first_name, last_name from t_actor where id = ?",
new Object[]{1212L},
new RowMapper<Actor>() {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
});

查询和填充多个领域对象:

1
2
3
4
5
6
7
8
9
10
List<Actor> actors = this.jdbcTemplate.query( 
"select first_name, last_name from t_actor",
new RowMapper<Actor>() {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
});

如果上面的两段代码实际存在于相同的应用中,建议把RowMapper匿名类中重复的代码抽取到单独的类中(通常是一个静态类),方便被DAO方法引用。例如,上面的代码例子更好的写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public List<Actor> findAllActors() { 
return this.jdbcTemplate.query( "select first_name, last_name from t_actor", new ActorMapper());
}

private static final class ActorMapper implements RowMapper<Actor> {

public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
}

使用jdbcTemplate实现增删改

你可以使用update(..)方法实现插入,更新和删除操作。参数值可以通过可变参数或者封装在对象内传入。

1
2
3
4
5
6
7
8
9
this.jdbcTemplate.update( 
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling");
this.jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L);
this.jdbcTemplate.update(
"delete from actor where id = ?",
Long.valueOf(actorId));

其他jdbcTemplate操作

你可以使用execute(..)方法执行任何SQL,甚至是DDL语句。这个方法可以传入回调接口、绑定可变参数数组等。

1
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))"); 

下面的例子调用一段简单的存储过程。更复杂的存储过程支持文档后面会有描述。

1
2
3
this.jdbcTemplate.update( 
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
Long.valueOf(unionId));

JdbcTemplate 最佳实践
JdbcTemplate实例一旦配置之后是线程安全的。这点很重要因为这样你就能够配置JdbcTemplate的单例,然后安全的将其注入到多个DAO中(或者repositories)。JdbcTemplate是有状态的,内部存在对DataSource的引用,但是这种状态不是会话状态。

使用JdbcTemplate类的常用做法是在你的Spring配置文件里配置好一个DataSource,然后将其依赖注入进你的DAO类中(NamedParameterJdbcTemplate也是如此)。JdbcTemplate在DataSource的Setter方法中被创建。就像如下DAO类的写法一样:

1
2
3
4
5
6
7
8
9
10
public class JdbcCorporateEventDao implements CorporateEventDao { 

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}

相关的配置是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao">
<property name="dataSource" ref="dataSource"/>
</bean>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<context:property-placeholder location="jdbc.properties"/>

</beans>

另一种替代显式配置的方式是使用component-scanning和注解注入。在这个场景下需要添加@Repository注解(添加这个注解可以被component-scanning扫描到),同时在DataSource的Setter方法上添加@Autowired注解:

1
2
3
4
5
6
7
8
9
10
11
12
@Repository 
public class JdbcCorporateEventDao implements CorporateEventDao {

private JdbcTemplate jdbcTemplate;

@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}

相关的XML配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!-- Scans within the base package of the application for @Component classes to configure as beans -->
<context:component-scan base-package="org.springframework.docs.test" />

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<context:property-placeholder location="jdbc.properties"/>

</beans>

如果你使用Spring的JdbcDaoSupport类,许多JDBC相关的DAO类都从该类继承过来,这个时候相关子类需要继承JdbcDaoSupport类的setDataSource方法。当然你也可以选择不从这个类继承,JdbcDaoSupport本身只是提供一些便利性。

无论你选择上面提到的哪种初始方式,当你在执行SQL语句时一般都不需要重新创建JdbcTemplate 实例。JdbcTemplate一旦被配置后其实例都是线程安全的。当你的应用需要访问多个数据库时你可能也需要多个JdbcTemplate实例,相应的也需要多个DataSources,同时对应多个JdbcTemplates配置。

DataSouce

Spring用DataSource来保持与数据库的连接。DataSource是JDBC规范的一部分同时是一种通用的连接工厂。它使得框架或者容器对应用代码屏蔽连接池或者事务管理等底层逻辑。作为开发者,你无需知道连接数据库的底层逻辑;这只是创建datasource的管理员该负责的模块。在开发测试过程中你可能需要同时扮演双重角色,但最终上线时你不需要知道生产数据源是如何配置的。

当使用Spring JDBC时,你可以通过JNDI获取数据库数据源、也可以利用第三方依赖包的连接池实现来配置。比较受欢迎的三方库有Apache Jakarta Commons DBCP 和 C3P0。在Spring产品内,有自己的数据源连接实现,但仅仅用于测试目的,同时并没有使用到连接池。

1
2
3
4
5
DriverManagerDataSource dataSource = new DriverManagerDataSource(); 
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");

接下来是相关的XML配置:

1
2
3
4
5
6
7
8
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<context:property-placeholder location="jdbc.properties"/>

下面的例子展示的是DBCP和C3P0的基础连接配置。如果需要连接更多的连接池选项、请查看各自连接池实现的具体产品文档

DBCP配置:

1
2
3
4
5
6
7
8
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<context:property-placeholder location="jdbc.properties"/>

C3P0配置:

1
2
3
4
5
6
7
8
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> 
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<context:property-placeholder location="jdbc.properties"/>

批量处理

大多数JDBC驱动在针对同一SQL语句做批处理时能够获得更好的性能。批量更新操作可以节省数据库的来回传输次数。

使用JdbcTemplate来进行基础的批量操作

通过JdbcTemplate 实现批处理需要实现特定接口的两个方法,BatchPreparedStatementSetter,并且将其作为第二个参数传入到batchUpdate方法调用中。使用getBatchSize提供当前批量操作的大小。使用setValues方法设置语句的Value参数。这个方法会按getBatchSize设置中指定的调用次数。下面的例子中通过传入列表来批量更新actor表。在这个例子中整个列表使用了批量操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class JdbcActorDao implements ActorDao { 
private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public int[] batchUpdate(final List<Actor> actors) {
int[] updateCounts = jdbcTemplate.batchUpdate("update t_actor set first_name = ?, " +
"last_name = ? where id = ?",
new BatchPreparedStatementSetter() {
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, actors.get(i).getFirstName());
ps.setString(2, actors.get(i).getLastName());
ps.setLong(3, actors.get(i).getId().longValue());
}

public int getBatchSize() {
return actors.size();
}
});
return updateCounts;
}

// ... additional methods
}

如果你需要处理批量更新或者从文件中批量读取,你可能需要确定一个合适的批处理大小,但是最后一次批处理可能达不到这个大小。在这种场景下你可以使用InterruptibleBatchPreparedStatementSetter接口,允许在输入流耗尽之后终止批处理,isBatchExhausted方法使得你可以指定批处理结束时间。

对象列表的批量处理

JdbcTemplate和NamedParameterJdbcTemplate都提供了批量更新的替代方案。这个时候不是实现一个特定的批量接口,而是在调用时传入所有的值列表。框架会循环访问这些值并且使用内部的SQL语句setter方法。你是否已声明参数对应API是不一样的。针对已声明参数你需要传入qlParameterSource数组,每项对应单次的批量操作。你可以使用SqlParameterSource.createBatch方法来创建这个数组,传入JavaBean数组或是包含参数值的Map数组。

下面是一个使用已声明参数的批量更新例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class JdbcActorDao implements ActorDao { 
private NamedParameterTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int[] batchUpdate(final List<Actor> actors) {
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray());
int[] updateCounts = namedParameterJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
batch);
return updateCounts;
}

// ... additional methods
}

对于使用“?”占位符的SQL语句,你需要传入带有更新值的对象数组。对象数组每一项对应SQL语句中的一个占位符,并且传入顺序需要和SQL语句中定义的顺序保持一致。

下面是使用经典JDBC“?”占位符的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class JdbcActorDao implements ActorDao { 

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public int[] batchUpdate(final List<Actor> actors) {
List<Object[]> batch = new ArrayList<Object[]>();
for (Actor actor : actors) {
Object[] values = new Object[] {
actor.getFirstName(),
actor.getLastName(),
actor.getId()};
batch.add(values);
}
int[] updateCounts = jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
batch);
return updateCounts;
}

// ... additional methods

}

上面所有的批量更新方法都返回一个数组,包含具体成功的行数。这个计数是由JDBC驱动返回的。如果拿不到计数。JDBC驱动会返回-2。

多个批处理操作
上面最后一个例子更新的批处理数量太大,最好能再分割成更小的块。最简单的方式就是你多次调用batchUpdate来实现,但是可以有更优的方法。要使用这个方法除了SQL语句,还需要传入参数集合对象,每次Batch的更新数和一个ParameterizedPreparedStatementSetter去设置预编译SQL语句的参数值。框架会循环调用提供的值并且将更新操作切割成指定数量的小批次。

下面的例子设置了更新批次数量为100的批量更新操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class JdbcActorDao implements ActorDao { 

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public int[][] batchUpdate(final Collection<Actor> actors) {
int[][] updateCounts = jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
actors,
100,
new ParameterizedPreparedStatementSetter<Actor>() {
public void setValues(PreparedStatement ps, Actor argument) throws SQLException {
ps.setString(1, argument.getFirstName());
ps.setString(2, argument.getLastName());
ps.setLong(3, argument.getId().longValue());
}
});
return updateCounts;
}

// ... additional methods

}

这个调用的批量更新方法返回一个包含int数组的二维数组,包含每次更新生效的行数。第一层数组长度代表批处理执行的数量,第二层数组长度代表每个批处理生效的更新数。每个批处理的更新数必须和所有批处理的大小匹配,除非是最后一次批处理可能小于这个数,具体依赖于更新对象的总数。每次更新语句生效的更新数由JDBC驱动提供。如果更新数量不存在,JDBC驱动会返回-2

spring 集成ORM

Spring框架在实现资源管理、数据访问对象(DAO)层,和事务策略等方面,支持对Java持久化API(JPA)以及原生Hibernate的集成。以Hibernate举例来说,Spring有非常赞的IoC功能,可以解决许多典型的Hibernate配置和集成问题。开发者可以通过依赖注入来配置O-R(对象关系)映射组件支持的特性。Hibernate的这些特性可以参与Spring的资源和事务管理,并且符合Spring的通用事务和DAO层的异常体系。因此,Spring团队推荐开发者使用Spring集成的方式来开发DAO层,而不是使用原生的Hibernate或者JPA的API。

spring 集成 OXM

Spring对于 对象/XML 映射的支持。对象/XML 映射,或 O/X 映射,是指将 XML 文档与 XML 文档对象进行互相转换的操作。这一转换操作也被称作 XML 编组,或 XML 序列化。在本章中,这几个概念都指的是同一个东西。
在O/X 映射中,将一组对象序列化为 XML 的操作是由一个编组器负责的。与之相对,一个反编组器则被用于将 XML 反序列化为一组对象。而这些操作中的 XML 文件来源可能是一份 DOM 文档,一个输入/输出流,或一个 SAX 管理器。

Spring 的 O/X 映射通过两个全局的接口来执行操作:Marshaller 和 Unmarshaller。这一结构让用户可以在几乎不需要修改编组操作类的前提下,轻易地在不同的 O/X 映射框架之间进行切换。这一结构的另一优势是可以以一种非侵入的方式在代码中混合多种 XML 编组方法(比如有一些编组实现使用 JAXB,而另一些则使用 Castor),从而将各种技术的优势在应用中加以综合利用。

web 集成

过去的web框架正在被淘汰,struts2也销声匿迹

相对较流行的 spring自己的 springweb(SpringMVC) 也正在被 spring-boot 和大前端技术(全栈技术)所追赶,后端项目越来越倾向于提供数据接口服务。 spring-cloud、 spring-boot 如火如荼。

Cache 在spring中的使用

文档1