《spring实战》学习笔记-第三章:高级装配
本章内容:Spring profile、条件化的bean声明、自动装配与歧义性、bean的作用域 、Spring表达式语言。
3.1 环境与profile
3.1.1 配置profile bean
3.1.2 激活profile
3.2 条件化的bean
例如,假设有一个名为CDPlayer的类,我们希望只有设置了magic环境属性的时候,Spring才会实例化这个类。如果环境中没有这个属性,那么CDPlayer将会被忽略。在程序清单3.4所展现的配置中,使用@Conditional注解条件化地配置了CDPlayer。
程序清单1 条件化地配置bean
设置给@Conditional的类可以是任意实现了Condition接口的类型。可以看出来,这个接口实现起来很简单直接,只需提供matches()方法的实现即可。如果matches()方法返回true,那么就会创建带有@Conditional注解的bean。如果matches()方法返回false,将不会创建这些bean。
程序清单2 在Condition中检查是否存在magic属性
在上面的程序清单中,matches()方法很简单但功能强大。它通过给定的ConditionContext对象进而得到Environment对象,并使用这个对象检查环境中是否存在名为magic的环境属性。在本例中,属性的值是什么无所谓,只要属性存在即可满足要求。如果满足这个条件的话,matches()方法就会返回true。所带来的结果就是条件能够得到满足,所有@Conditional注解上引用MagicExistsCondition的bean都会被创建。话说回来,如果这个属性不存在的话,就无法满足条件,matches()方法会返回false,这些bean都不会被创建。
conditionContext是一个接口,通过ConditionContext,我们可以做到如下几点:
(1)借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;
(2)借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;
(3)借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;
(4)读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
(5)借助getClassLoader()返回的ClassLoader加载并检查类是否存在。
AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上还有什么其他的注解。像ConditionContext一样,AnnotatedTypeMetadata也是一个接口。
非常有意思的是,从Spring 4开始,@Profile注解进行了重构,使其基于@Conditional和Condition实现。作为如何使用@Conditional和Condition的例子,我们来看一下在Spring 4中,@Profile是如何实现的。
注意:@Profile本身也使用了@Conditional注解,并且引用ProfileCondition作为Condition实现。如下所示,ProfileCondition实现了Condition接口,并且在做出决策的过程中,考虑到了ConditionContext和AnnotatedTypeMetadata中的多个因素。
程序清单3 ProfileCondition检查某个bean profile是否可用
我们可以看到,ProfileCondition通过AnnotatedTypeMetadata得到了用于@Profile注解的所有属性。借助该信息,它会明确地检查value属性,该属性包含了bean的profile名称。然后,它根据通过ConditionContext得到的Environment来检查[借助acceptsProfiles()方法]该profile是否处于激活状态。
3.3 处理自动装配的歧义性
3.3.1 标示首选的bean
在声明bean的时候,通过将其中一个可选的bean设置为首选(primary)bean能够避免自动装配时的歧义性。当遇到歧义性的时候,Spring将会使用首选的bean,而不是其他可选的bean。
@Primary能够与@Component组合用在组件扫描的bean上,也可以与@Bean组合用在Java配置的bean声明中。
如果你使用XML配置bean的话,同样可以实现这样的功能。<bean>元素有一个primary属性用来指定首选的bean:
3.3.2 限定自动装配的bean
@Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean。例如,我们想要确保要将IceCream注入到setDessert()之中:
为@Qualifier注解所设置的参数就是想要注入的bean的ID。所有使用@Component注解声明的类都会创建为bean,并且bean的ID为首字母变为小写的类名。因此,@Qualifier("iceCream")指向的是组件扫描时所创建的bean,并且这个bean是IceCream类的实例。
值得一提的是,当通过Java配置显式定义bean的时候,@Qualifier也可以与@Bean注解一起使用。
3.4 bean的作用域
在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。
Spring定义了多种作用域,可以基于这些作用域创建bean,包括:
(1)单例(Singleton):在整个应用中,只创建bean的一个实例。
(2)原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
(3)会话(Session):在Web应用中,为每个会话创建一个bean实例。
(4)请求(Rquest):在Web应用中,为每个请求创建一个bean实例。
单例是默认的作用域,但对于易变的类型,这并不合适。如果选择其他的作用域,要使用@Scope注解,它可以与@Component或@Bean一起使用。
3.4.1 使用会话和请求作用域
就购物车bean来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。要指定会话作用域,我们可以使用@Scope注解。
3.4.2 在XML中声明作用域代理
如果你需要使用XML来声明会话或请求作用域的bean,那么就不能使用@Scope注解及其proxyMode属性了。<bean>元素的scope属性能够设置bean的作用域,但是该怎样指定代理模式呢?
要设置代理模式,我们需要使用Spring aop命名空间的一个新元素:
<bean id="jDShoppingCart" class="soundSystem.JDShoppingCart" scope="session">
<aop:scoped-proxy/>
</bean>
<aop:scoped-proxy>是与@Scope注解的proxyMode属性功能相同的Spring XML配置元素。它会告诉Spring为bean创建一个作用域代理。默认情况下,它会使用CGLib创建目标类的代理。但是我们也可以将proxy-target-class属性设置为false,进而要求它生成基于接口的代理。
为了使用<aop:scoped-proxy>元素,我们必须在XML配置中声明Spring的aop命名空间。
3.5 运行时值注入
有的时候,我们可能会希望避免硬编码值,而是想让这些值在运行时再确定。为了实现这些功能,Spring提供了两种在运行时求值的方式:
属性占位符(Property placeholder)。
Spring表达式语言(SpEL)。
3.5.1 注入外部的值
在Spring中,处理外部值的最简单方式就是声明属性源并通过Spring的Environment来检索属性。
程序清单1 使用@PropertySource注解和Environment
在本例中,@PropertySource引用了类路径中一个名为app.properties的文件。它大致会如下所示:
这个属性文件会加载到Spring的Environment中,稍后可以从这里检索属性。同时,在disc()方法中,会创建一个新的BlankDisc,它的构造器参数是从属性文件中获取的,而这是通过调用getProperty()实现的。
深入学习Spring的Environment
解析属性占位符
3.5.2 使用Spring表达式语言进行装配
需要了解的第一件事情就是SpEL表达式要放到“#{ ... }”之中,这与属性占位符有些类似,属性占位符需要放到“${ ... }”之中。
3.6 小结
首先,我们学习了Spring profile,它解决了Spring bean要跨各种部署环境的通用问题。在运行时,通过将环境相关的bean与当前激活的profile进行匹配,Spring能够让相同的部署单元跨多种环境运行,而不需要进行重新构建。
Profile bean是在运行时条件化创建bean的一种方式,但是Spring 4提供了一种更为通用的方式,通过这种方式能够声明某些bean的创建与否要依赖于给定条件的输出结果。结合使用@Conditional注解和Spring Condition接口的实现,能够为开发人员提供一种强大和
灵活的机制,实现条件化地创建bean。
我们还看了两种解决自动装配歧义性的方法:首选bean以及限定符。尽管将某个bean设置为首选bean是很简单的,但这种方式也有其局限性,所以我们讨论了如何将一组可选的自动装配bean,借助限定符将其范围缩小到只有一个符合条件的bean。除此之外,我们还看到了如何创建自定义的限定符注解,这些限定符描述了bean的特性。
尽管大多数的Spring bean都是以单例的方式创建的,但有的时候其他的创建策略更为合适。Spring能够让bean以单例、原型、请求作用域或会话作用域的方式来创建。在声明请求作用域或会话作用域的bean的时候,我们还学习了如何创建作用域代理,它分为基于类的代理和基于接口的代理的两种方式。
最后,我们学习了Spring表达式语言,它能够在运行时计算要注入到bean属性中的值。对于bean装配,我们已经掌握了扎实的基础知识,现在我们要将注意力转向面向切面编程(aspect-oriented programming ,AOP)了。依赖注入能够将组件及其协作的其他组件解耦,
与之类似,AOP有助于将应用组件与跨多个组件的任务进行解耦。在下一章,我们将会深入学习在Spring中如何创建和使用切面。