Spring高级装配(一) profile

Spring高级装配要学习的内容包括:

  • Spring profile
  • 条件化的bean声明
  • 自动装配与歧义性
  • bean的作用域
  • Spring表达式语言

以上属于高级一点的bean装配技术,如果你没有啥特别的需求的话用的还比较少。但是用于解决变态一点的需求还是要学一下留个备份。

环境与Profile

直接上情形吧,一个项目现在有三个阶段,不同阶段使用的dataSource的来源不一样,分别是:

  1. 开发阶段:使用嵌入式的Hypersonic数据库
  2. QA阶段:使用不同DataSource配置,比如Common DBCP连接池
  3. 生产阶段:从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使用的大致流程:

Spring高级装配(一) profile_第1张图片

 

配置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

通过元素的profile属性,在XML中配置profile bean:

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相匹配的配置文件才会被用到。

如果觉得定义的配置文件太多,你可以在根中嵌套定义元素,而是不是为每个环境创建一个profile XML文件,配置代码如下:

 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定义。

你可能感兴趣的:(Spring高级装配(一) profile)