前言:
在上一章装配Bean中,我们看到了一些最为核心的bean装配技术。你可能会发现上一章学到的知识有很大的用处。但是,bean装配所涉及的领域并不仅仅局限于上一章 所学习到的内容。Spring提供了多种技巧,借助它们可以实现更为高级的bean装配功能。在本章中,我们将会深入介绍一些这样的高级技术。
使用情景:
在开发软件的时候,有一个很大的挑战就是将应用程序从一个环境迁移到另外一个环境。开发阶段中,某些环境相关做法可能并不适合迁移到生产环境中,甚至即便迁移过去也无法正常工作。数据库配置、加密算法以及与外部系统的集成是跨环境部署时会发生变化的几个典型例子。
一、配置profile bean
Spring需要根据环境决定该创建哪个bean和不创建哪个bean。不过Spring并不是在构建的时候做出这样的决策,而是等到运行时再来确定。这样的结果就是同一个部署单元(可能会是WAR文件)能够适用于所有的环境,没有必要进行重新构建。
使用profile步骤:
- 你首先要将所有不同的bean定义整理到一个或多个profile之中,在Java配置中,可以使用@Profile注解指定某个bean属于哪一个profile。
- 在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态,设置spring.profiles.active。
开发环境和生产环境通常采用不同的数据库连接方式,例如在开发环境可以采用嵌入式,而生产环境中采用jndi连接池,所以要根据不同环境配置不同的bean,Spring中提供了profile来实现动态生成相应的bean:
1、@Profile配置在类级别上
嵌入式DataSource:
它会告诉Spring这个配置类中的bean只有在dev profile激活时才会创建。如果dev profile没有激活的话,那么带有@Bean注解的方法都会被忽略掉。
同时,你可能还需要有一个适用于生产环境的配置,如下所示:
在本例中,只有prod profile激活的时候,才会创建对应的bean;
2、@Profile配置在类方法级别上
在Spring 3.1中,只能在类级别上使用@Profile注解。不过,从Spring 3.2开始,你也可以在方法级别上使用@Profile注解,与@Bean注解一同使用。这样的话,就能将这两个bean的声明放到同一个配置类之中,如下所示:
这里有个问题需要注意,尽管每个DataSource bean都被声明在一个profile中,并且只有当规定的profile激活时,相应的bean才会被创建,但是可能会有其他的bean并没有声明在一个给定的profile范围内。没有指定profile的bean始终都会被创建,与激活哪个profile没有关系。
3、在XML中配置profile
我们也可以通过<beans>元素的profile属性,在XML中配置profile bean。例如,为了在XML中定义适用于开发阶段的嵌入式数据库DataSourcebean,我们可以创建如下所示的XML文件:
你还可以在根<beans>元素中嵌套定义<beans>元素,而不是为每个环境都创建一个profile XML文件。这能够将所有的profile bean定义放到同一个XML文件中,如下所示:
除了所有的bean定义到了同一个XML文件之中,这种配置方式与定义在单独的XML文件中的实际效果是一样的。这里有三个bean,类型都是javax.sql.DataSource,并且ID都是dataSource。但是在运行时,只会创建一个bean,这取决于处于激活状态的是哪个profile。
二、激活profile
通过profile标记不同的环境,但是如何激活它呢,可以通过设置spring.profiles.active和spring.profiles.default。如果设置了active,default便失去了作用。如果没有设置active就会去找default的值。如果两个都没有设置,那么那些定义在profiles的bean都不会生成。
有多种方式来设置这两个属性:
- 作为DispatcherServlet的初始化参数;
- 作为web应用的上下文参数;
- 作为JNDI条目;
- 作为环境变量;(指linuxwindow系统中的环境配置修改)
- 作为JVM的系统属性;
- 在集成测试类上,使用@ActiveProfiles注解配置。
以上面的前两种方式举例,设置spring.profiles.default的web.xml文件会如下所示:
按照这种方式设置spring.profiles.default,使用开发环境的设置(如嵌入式数据库)运行代码,而不需要任何额外的配置。
当应用程序部署到QA、生产或其他环境之中时,负责部署的人根据情况使用系统属性、环境变量或JNDI设置spring.profiles.active即可。当设置spring.profiles.active以后,至于spring.profiles.default置成什么值就已经无所谓了;系统会优先使用spring.profiles.active中所设置的profile。
补充:在系统的环境变量里的设置的优先级高于application.properties里的spring.profiles.active的设置:
使用profile进行测试
当运行集成测试时,通常会希望采用与生产环境(或者是生产环境的部分子集)相同的配置进行测试。但是,如果配置中的bean定义在了profile中,那么在运行测试时,我们就需要有一种方式来启用合适的profile。
Spring提供了@ActiveProfiles注解,我们可以使用它来指定运行测试时要激活哪个profile。在集成测试时,通常想要激活的是开发环境的profile。例如,下面的测试类片段展现了使用@ActiveProfiles激活dev profile:
三、条件化的bean
通过活动的profile,我们可以获得不同的Bean。Spring 4提供了一个更通用的基于条件的Bean的创建方式,即使用@Conditional注解。
@Conditional根据满足某个特定的条件创建一个特定的Bean。比如,当某一个jar包在一个类路径下时,自动配置一个或者多个Bean。或者只有一个Bean创建时,才会创建另一个Bean。总的来说,就是根据特定条件来控制Bean的创建行为,这样我们可以利用这个特性进行一些自动配置。
下面的示例将以“环境中是否存在magic属性”作为条件,我们将通过实现Condition接口,并重写其matches方法来构造判断条件。
1、条件化地配置bean
可以看到,@Conditional中给定了一个Class,它指明了条件,也就是MagicExistsCondition。
2、判断条件定义(实现Condition接口)
在上面的程序清单中,matches()方法很简单但功能强大。它通过给定的ConditionContext对象进而得到Environment对象,并使用这个对象检查环境中是否存在名为magic的环境属性。如果满足这个条件的话,matches()方法就会返回true。所有@Conditional注解上引用MagicExistsCondition的bean都会被创建。如果这个属性不存在的话,就无法满足条件,matches()方法会返回false,这些bean都不会被创建。
介绍matches()方法
提供ConditionContext和AnnotatedTypeMetadata对象;
1.1、ConditionContext接口如下:
通过ConditionContext,我们可以做到如下几点:
- 借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;
- 借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;
- 借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;
- 读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
- 借助getClassLoader()返回的ClassLoader加载并检查类是否存在。
1.2、AnnotatedTypeMetadata
AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上还有什么其他的注解。像ConditionContext一样,AnnotatedTypeMetadata也是一个接口。它如下所示:
借助isAnnotated()方法,我们能够判断带有@Bean注解的方法是不是还有其他特定的注解。借助其他的那些方法,我们能够检查@Bean注解的方法上其他注解的属性。
3、使用@Conditional重构@Profile
从Spring 4开始,@Profile注解进行了重构,使其基于@Conditional和Condition实现。我们来看一下在Spring 4中,@Profile是如何实现的。
@Profile注解如下所示:
注意:@Profile本身也使用了@Conditional注解,并且引用ProfileCondition作为Condition实现了Condition接口,并且在做出决策的过程中,考虑到了ConditionContext和AnnotatedTypeMetadata中的多个因素。
条件:ProfileCondition检查某个bean profile是否可用
我们可以看到,ProfileCondition通过AnnotatedTypeMetadata得到了用于@Profile注解的所有属性。借助该信息,它会明确地检查value属性,该属性包含了bean的profile名称。然后,它根据通过ConditionContext得到的Environment来检查[借助acceptsProfiles()方法]该profile是否处于激活状态。