环境与profile
配置profile bean
Spring为环境相关的bean所提供的解决方案其实与构建时的方案没有太大的差别。当然,在这个过程中需要根据环境决定该创建哪个bean和不 创建哪个bean。不过Spring并不是在构建的时候做出这样的决策,而是等到运行时再来确定。这样的结果就是同一个部署单元(可能会是 WAR文件)能够适用于所有的环境,没有必要进行重新构建。
在3.1版本中,Spring引入了bean profile的功能。要使用profile,你首先要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署 到每个环境时,要确保对应的profile处于激活(active)的状态。
在javaConfig中配置profile
1.@Profile注解在类上
在Java配置中,可以使用@Profile注解指定某个bean属于哪一个profile。例如,在配置类中,嵌入式数据库的DataSource可能会配置成如 下所示:


- 1.@Profile注解在方法上
在Spring 3.1中,只能在类级别上使用@Profile注解。不过,从Spring 3.2开始,你也可以在方法级别上使用@Profile注解,与@Bean注解 一同使用。这样的话,就能将这两个bean的声明放到同一个配置类之中,如下所示:

在XML中配置profile
1.分别制定多个profile(多个xml配置文件) 可以通过元素的profile属性,在XML中配置profile bean

根元素中嵌套定义元素,而不是为每个环境都创建一个profile XML文件。这能够将所有的profile bean定义放到 同一个XML文件中:

激活profile
Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。
1.如果设置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile是激活的。
2.但如果没有设置spring.profiles.active属性的话,那Spring将会查找spring.profiles.default的值。
3.如果spring.profiles.active和spring.profiles.default均没有设置的话,那就没有激活的profile,因此只会创建那些没有定义在 profile中的bean。
有多种方式来设置这两个属性:
- 作为DispatcherServlet的初始化参数;
- 作为Web应用的上下文参数;
- 作为JNDI条目;
- 作为环境变量;
- 作为JVM的系统属性;
- 在集成测试类上,使用@ActiveProfiles注解设置。
在spring.profiles.active和spring.profiles.default中,profile使用的都是复数形式。这意味着你可以 同时激活多个profile,这可以通过列出多个profile名称,并以逗号分隔来实现。当然,同时启用dev和prod profile可能也没有太大的意义,不 过你可以同时设置多个彼此不相关的profile。
使用profil进行测试
Spring提供了@ActiveProfiles注解,我们可以使用它来指定运行测试时要激活哪个profile。

条件化的bean
你可能希望:
1.一个或多个bean只有在应用的类路径下包含特定的库时才创建。
2.或者我们希望某个bean只有当另外某个特定的bean也声明了之后
才会创建。
3.还可能要求只有某个特定的环境变量设置之后,才会创建某个bean。
在Spring 4之前,很难实现这种级别的条件化配置,但是Spring4引入了一个新的@Conditional注解,它可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否则的话,这个bean会被忽略。
@Conditional注解的使用
例子: 例如,假设有一个名为MagicBean的类,我们希望只有设置了magic环境属性的时候,Spring才会实例化这个类。如果环境中没有这个属 性,那么MagicBean将会被忽略。


2.创建Condition接口的实现(MagicExistsCondition)
在本例中,我们需要创建Condition的实现并根据环境中是否存在magic属性来做出决策

接口说明
ConditionContext:

通过ConditionContext,我们可以做到如下几点:
- 借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;
- 借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;
- 借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;
- 读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
- 借助getClassLoader()返回的ClassLoader加载并检查类是否存在。
AnnotatedTypeMetadata接口:
AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上还有什么其他的注解。

借助isAnnotated()方法,我们能够判断带有@Bean注解的方法是不是还有其他特定的注解。借助其他的那些方法,我们能够检 查@Bean注解的方法上其他注解的属性。
处理自动装配的歧义性
在spring自动装配时,如果不止一个bean匹配,此时就发生了歧义性。会引发spring抛出NoUniqueBeanDefinitionException异常:

标示首选的bean
在声明bean的时候,通过将其中一个可选的bean设置为首选(primary)bean能够避免自动装配时的歧义性。当遇到歧义性的时候,Spring将 会使用首选的bean,而不是其他可选的bean。
通过@Primary来表示首选,该注解的用法:
1、和@Component注解一起用在类上。



注意:如果有标识了多个首选bean,那么就像Spring无法从多个可选的bean中做出选择一样, 它也无法从多个首选的bean中做出选择。
限定自动装配的bean
Spring的限定符能够在所有可选的bean上进行缩小范围的操作,最终能够达到只有一个bean满足所规定的限制条件。如果将所有的 限定符都用上后依然存在歧义性,那么你可以继续使用更多的限定符来缩小选择范围。
@Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean。

创建自定义的限定符
可以为bean设置自己的限定符,而不是依赖于将beanID作为限定符(默认情况下bean的限定符和id一样)。要做的就是在bean声明上添加@Qualifier注解。
1.例 如,它可以与@Component组合使用。

2.当通过Java配置显式定义bean的时候,@Qualifier也可以与@Bean注解一起使用

使用:

使用自定义的限定符注解
面向特性的限定符要比基于bean ID的限定符更好一些。但是,如果多个bean都具备相同特性的话,这种做法也会出现问题。例如,如果引入
了这个新的Dessert bean,会发生什么情况呢:

现在我们有了两个带有“cold”限定符的甜点。在自动装配Dessert bean的时候,我们再次遇到了歧义性的问题,需要使用更多的限定 符来将可选范围限定到只有一个bean。
能想到的解决方案就是在注入点和bean定义的地方同时再添加另外一个@Qualifier注解。IceCream类大致就会如下所示



这里只有一个小问题:Java不允许在同一个条目上重复出现相同类型的多个注解。如果你试图这样做的话,编译器会提示错误。
解决方法:
可以创建自定义的限定符注解,借助这样的注解来表达bean所希望限定的特性。这里所需要做的就是创建一个注解,它本身要使 用@Qualifier注解来标注。
不再使用@Qualifier("cold"),而是使用自定义的@Cold注解





通过声明自定义的限定符注解,我们可以同时使用多个限定符,不会再有Java编译器的限制或错误。
bean的作用域
在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。
Spring定义了多种作用域,可以基于这些作用域创建bean,包括:
- 单例(Singleton):在整个应用中,只创建bean的一个实例。
- 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
- 会话(Session):在Web应用中,为每个会话创建一个bean实例。
- 请求(Rquest):在Web应用中,为每个请求创建一个bean实例。
选择作用域:
使用@Scope注解,可以与@Component或@Bean一起使用。
1.与@Component注解一起使用

2.使用@Scope和@Bean来指定所需的作用域:

3.如果你使用XML来配置bean的话,可以使用元素的scope属性来设置作用域:

不管你使用以上哪种方式来声明原型作用域,每次注入或通过Spring应用上下文中获取该bean的时候,都会创建新的实例
使用会话和请求作用域
例如,在典型的电子商务应用中,可能会有一个 bean代表用户的购物车就购物车bean来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。要指定会话作用域,我们可以使用@Scope注解,它的使用 方式与指定原型作用域是相同的:


因为StoreService是一个单例的bean,会在Spring应用上下文加载的时候创建。当它创建的时候,Spring会试图将ShoppingCartbean注入到setShoppingCart()方法中。但是ShoppingCartbean是会话作用域的,此时并不存在。直到某个用户进入系统,创建了会话之后,才会出现ShoppingCart实例。
另外,系统中将会有多个ShoppingCart实例:每个用户一个。我们并不想让Spring注入某个固定的ShoppingCart实例到StoreService中。我们希望的是当StoreService处理购物车功能时,它所使用的ShoppingCart实例恰好是当前会话所对应的那一 个。
Spring并不会将实际的ShoppingCartbean注入到StoreService中,Spring会注入一个到ShoppingCartbean的代理,如图3.1所示。这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。
现在,我们带着对这个作用域的理解,讨论一下proxyMode属性。如配置所示,proxyMode属性被设置成了ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。
如果ShoppingCart是接口而不是类的话,这是可以的(也是最为理想的代理模式)。但如果ShoppingCart是一个具体的类的话,Spring就没有办法创建基于接口的代理了。此时,它必须使用CGLib来生成基于类的代理。所以,如果bean类型是具体类的话,我们必须要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理。
尽管我主要关注了会话作用域,但是请求作用域的bean会面临相同的装配问题。因此,请求作用域的bean应该也以作用域代理的方式进行注入。
在XML中声明作用域代理
如果你需要使用XML来声明会话或请求作用域的bean,那么就不能使用@Scope注解及其proxyMode属性了。<bean元素的scope属性能够设置bean的作用域,但是该怎样指定代理模式呢?
要设置代理模式,我们需要使用Spring aop命名空间的一个新元素:


