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

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

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

环境与Profile

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

  1. 开发阶段:使用嵌入式的Hypersonic数据库
  2. QA阶段:使用不同DataSource配置,比如Common DBCP连接池
  3. 生产阶段:从JNDI容器中获取一个DataSource

这三种DataSource bean的生成代码分别是:

嵌入式的Hypersonic数据库:

 @Bean(destroyMethod="shutdown")
public DataSource dataSource() {
return new EmbeddedDataSourceBuilder()
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}

JNDI:

 @Bean
public DataSource dataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}

Common DBCP:

 @Bean(destroyMethod="close")
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUserName("sa");
dataSource.setPassword("password");
dataSource.setInitialSize(20);
dataSource.setMaxActive(30);
return dataSource;
}

也就是说每个阶段都是用了完全不同的策略来生成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。先来一个直接一点的例子:

 @Configuration
@Profile("dev")
public class DevelopmentProfileConfig { @Bean(destroyMethod="shutdown")
public DataSource dataSource() {
return new EmbeddedDataSourceBuilder()
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
}

解释说明:

  • @Profile应用在了类级别上
  • 这个配置类中的bean只有在dev profile被激活的时候才会被创建。
  • 如果dev profile没有被激活,那么带有@Bean注解的方法都会被忽略。

在给出一个适用于生产环境的配置:

 @Configuration
@Profile("prod")
public class ProductionProfileConfig {
@Bean
public DataSource dataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}

在Spring 3.1中只能在类级别上使用@Profile注解,3.2开始,也可以在方法级别上使用@Profile注解,与@Bean注解一同使用;这样的话可以把这两个bean的声明放到同一个配置类中:

 @Configuration
public class DataSourceConfig {
@Bean(destroyMethod="shutdown")
@Profile("dev")
public DataSource dataSource() {
return new EmbeddedDataSourceBuilder()
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
} @Bean
@Profile("prod")
public DataSource dataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}

这样一来每个DataSource的bean都被声明在配置类中,并且只有当规定的profile激活时,相应的bean才会被创建;没有指定profile的bean始终都会被创建,与激活哪个profile没有关系。

在XML中配置profile

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

 <beans profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql" />
<jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>
</beans>

同理,可以通过把profile设置为prod,创建适用于生产环境的从JNDI获取的DataSource bean;也可以创建基于连接池定义的dataSource bean,将其放在另一个XML文件中,并标注为qa profile。所有的配置文件都会放在部署单元之中(如WAR文件),但是只有profile属性与当前激活的profile相匹配的配置文件才会被用到。

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

 <beans>

     <beans profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql" />
<jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>
</beans> <beans profile="qa">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
p:url="jdbc:h2:tcp://dbserver/~/test"
p:driverClassName="org.h2.Driver"
p:username="sa"
p:password="password"
p:initialSize="20"
p:maxActive="30" />
</beans> <beans profile="prod">
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/myDatabase"
resource-ref="true" proxy-interface="javax.sql.DataSource" />
</beans> </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中:

 <web-app>

     <!-- 为上下文设置默认的profile -->
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</context-param> <!-- 为Servlet设置默认的profile -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</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定义。

最新文章

  1. STM32时钟数
  2. JS的prototype和__proto__ Constructor
  3. 最牛X的GCC 内联汇编
  4. SpringMVC、Struts1、Struts2和SSH2框架中单例与多例的解析
  5. HUST 1010 The Minimum Length(KMP,最短循环节点,即i-Next[i])
  6. .net mvc通过ucenter和 discuz的整合,nopcommerce ucenter 插件的方式实现
  7. 移植net-snmp到开发板(mini210)
  8. jdbc 连接 mysql 获取 数据集 条数
  9. (转)利用ant在Mac 下自动化打包签名Android程序
  10. 关于响应式、媒体查询和media的关系 、流媒体布局flex 和em rem像素的使用 我有一些废话要讲.....
  11. Javascript 递归函数
  12. utf8 文件 错误保存为gbk 中文乱码 解决方法
  13. MinGW GCC 8.3.1 2019年2月23日 出炉啦
  14. VS调式时出现异常,在输入法是中文状态下,输入框输入字母再回车,会造成页面关闭,vs退出调式
  15. 一、Ansible安装
  16. 4.HTTP入门.md
  17. maven util 类 添加 service
  18. SSM搭建Spring单元测试环境
  19. CF464C-Substitutes in Number
  20. 微信view类型的菜单获取openid范例

热门文章

  1. iconv 中文截断问题的解决方法
  2. SQL Server中关于跟踪(Trace)那点事(转载)
  3. Ubuntu18.04 修改DNS
  4. mybatis与mysql中的Date和String之间转换
  5. django -- verbose_name的对数据库层面的影响
  6. talend 将hbase中数据导入到mysql中
  7. 怎么运行 ASP.NET Core控制台程序
  8. Android:UI 沉浸式体验,适合第一屏的引导图片、预览图片。
  9. 启动 angular-phonecat 项目时出现这玩意 。(&#39;The header content contains invalid characters&#39;);
  10. 【Unity】6.5 Time类、Mathf类、Coroutine类