Spring高级装配要学习的内容包括:
- Spring profile
- 条件化的bean声明
- 自动装配与歧义性
- bean的作用域
- Spring表达式语言
以上属于高级一点的bean装配技术,如果你没有啥特别的需求的话用的还比较少。但是用于解决变态一点的需求还是要学一下留个备份。
环境与Profile
直接上情形吧,一个项目现在有三个阶段,不同阶段使用的dataSource的来源不一样,分别是:
- 开发阶段:使用嵌入式的Hypersonic数据库
- QA阶段:使用不同DataSource配置,比如Common DBCP连接池
- 生产阶段:从JNDI容器中获取一个DataSource
这三种DataSource bean的生成代码分别是:
嵌入式的Hypersonic数据库:
1 @Bean(destroyMethod="shutdown") 2 public DataSource dataSource() { 3 return new EmbeddedDataSourceBuilder() 4 .addScript("classpath:schema.sql") 5 .addScript("classpath:test-data.sql") 6 .build(); 7 }
JNDI:
1 @Bean 2 public DataSource dataSource() { 3 JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean(); 4 jndiObjectFactoryBean.setJndiName("jdbc/myDS"); 5 jndiObjectFactoryBean.setResourceRef(true); 6 jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class); 7 return (DataSource) jndiObjectFactoryBean.getObject(); 8 }
Common DBCP:
1 @Bean(destroyMethod="close") 2 public DataSource dataSource() { 3 BasicDataSource dataSource = new BasicDataSource(); 4 dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test"); 5 dataSource.setDriverClassName("org.h2.Driver"); 6 dataSource.setUserName("sa"); 7 dataSource.setPassword("password"); 8 dataSource.setInitialSize(20); 9 dataSource.setMaxActive(30); 10 return dataSource; 11 }
也就是说每个阶段都是用了完全不同的策略来生成DataSource的bean。现在有一个需求是:如何优雅地切换这三种DataSource?
如果只用到基础的Spring bean的装配知识的话,我们必须每次手动的加上要转入的阶段对应的DataSource bean定义代码。这样的话容易引入bug,而且不优雅。这种情况其实可以抽象一下:根据不同的情况,生成不同的bean。
Spring针对这种根据环境来决定创建哪个bean和不创建哪个bean提供了了一种解决方案:profile。profile使用的大致流程:
配置profile bean
Spring利用profile来感觉环境决定创建哪个bean和不创建哪个bean,并不是在构建的时候做出决策,而是在运行时再决定。这样的话代码就可以适用于所有的环境,而不是需要额外重构。
在使用profile的时候(since 3.1),首先要把不同的bean定义整理到一个或者多个profile中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)状态。
在Java配置中使用@Profile指定某个bean属于哪个profile。先来一个直接一点的例子:
1 @Configuration 2 @Profile("dev") 3 public class DevelopmentProfileConfig { 4 5 @Bean(destroyMethod="shutdown") 6 public DataSource dataSource() { 7 return new EmbeddedDataSourceBuilder() 8 .addScript("classpath:schema.sql") 9 .addScript("classpath:test-data.sql") 10 .build(); 11 } 12 }
解释说明:
- @Profile应用在了类级别上
- 这个配置类中的bean只有在dev profile被激活的时候才会被创建。
- 如果dev profile没有被激活,那么带有@Bean注解的方法都会被忽略。
在给出一个适用于生产环境的配置:
1 @Configuration 2 @Profile("prod") 3 public class ProductionProfileConfig { 4 @Bean 5 public DataSource dataSource() { 6 JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean(); 7 jndiObjectFactoryBean.setJndiName("jdbc/myDS"); 8 jndiObjectFactoryBean.setResourceRef(true); 9 jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class); 10 return (DataSource) jndiObjectFactoryBean.getObject(); 11 } 12 }
在Spring 3.1中只能在类级别上使用@Profile注解,3.2开始,也可以在方法级别上使用@Profile注解,与@Bean注解一同使用;这样的话可以把这两个bean的声明放到同一个配置类中:
1 @Configuration 2 public class DataSourceConfig { 3 @Bean(destroyMethod="shutdown") 4 @Profile("dev") 5 public DataSource dataSource() { 6 return new EmbeddedDataSourceBuilder() 7 .addScript("classpath:schema.sql") 8 .addScript("classpath:test-data.sql") 9 .build(); 10 } 11 12 @Bean 13 @Profile("prod") 14 public DataSource dataSource() { 15 JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean(); 16 jndiObjectFactoryBean.setJndiName("jdbc/myDS"); 17 jndiObjectFactoryBean.setResourceRef(true); 18 jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class); 19 return (DataSource) jndiObjectFactoryBean.getObject(); 20 } 21 }
这样一来每个DataSource的bean都被声明在配置类中,并且只有当规定的profile激活时,相应的bean才会被创建;没有指定profile的bean始终都会被创建,与激活哪个profile没有关系。
在XML中配置profile
通过
1 <beans profile="dev"> 2 <jdbc:embedded-database id="dataSource"> 3 <jdbc:script location="classpath:schema.sql" /> 4 <jdbc:script location="classpath:test-data.sql" /> 5 jdbc:embedded-database> 6 beans>
同理,可以通过把profile设置为prod,创建适用于生产环境的从JNDI获取的DataSource bean;也可以创建基于连接池定义的dataSource bean,将其放在另一个XML文件中,并标注为qa profile。所有的配置文件都会放在部署单元之中(如WAR文件),但是只有profile属性与当前激活的profile相匹配的配置文件才会被用到。
如果觉得定义的配置文件太多,你可以在根
1 <beans> 2 3 <beans profile="dev"> 4 <jdbc:embedded-database id="dataSource"> 5 <jdbc:script location="classpath:schema.sql" /> 6 <jdbc:script location="classpath:test-data.sql" /> 7 jdbc:embedded-database> 8 beans> 9 10 <beans profile="qa"> 11 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" 12 p:url="jdbc:h2:tcp://dbserver/~/test" 13 p:driverClassName="org.h2.Driver" 14 p:username="sa" 15 p:password="password" 16 p:initialSize="20" 17 p:maxActive="30" /> 18 beans> 19 20 <beans profile="prod"> 21 <jee:jndi-lookup id="dataSource" jndi-name="jdbc/myDatabase" 22 resource-ref="true" proxy-interface="javax.sql.DataSource" /> 23 beans> 24 25 beans>
激活profle
把profile配置好了之后,问题是怎么激活这些profile?
Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:
- spring.profiles.active
- spring.profiles.default
如果设置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile是激活的。
如果没有设置spring.profiles.active属性的话,那Spring将会查找spring.profiles.default的值。
如果active和default都没有设置,那么就没有激活的profile,因此只会激活那些没有定义在profile中的bean。
设置激活属性的方法:
- 作为DispatcherServlet的初始化参数
- 作为Web应用的上下文参数
- 作为JNDI条目
- 作为环境变量
- 作为JVM的系统属性
- 在集成测试类上,使用@ActiveProfiles注解设置
推荐的方式使用时DispatcherServlet的参数将spring.profiles.default设置为开发环境的profile,会在Servlet上下文中进行设置,在web.xml中:
1 <web-app> 2 3 4 <context-param> 5 <param-name>spring.profiles.defaultparam-name> 6 <param-value>devparam-value> 7 context-param> 8 9 10 <servlet> 11 <servlet-name>appServletservlet-name> 12 <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class> 13 <init-param> 14 <param-name>spring.profiles.defaultparam-name> 15 <param-value>devparam-value> 16 init-param> 17 <load-on-startup>1load-on-startup> 18 web-app>
可以通过列出多个profile名称并以逗号分隔来同时激活多个profile。不过同时启用dev和prod可能没有太大的意义,但是可以同时设置多个彼此不相关的profile。
集成测试时使用@ActiveProfiles注解来指定测试时要激活的profile。
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes={PersistenceTestConfig.class}) @ActiveProfiles("dev") public class PersistenceTest { }
Spring的profile提供了一种很好的条件化创建bean的方法,这里的条件是基于哪个profile处于激活状态来判断。Spring 4.0提供了一种更为通用的机制来实现条件化的bean定义。