第二章 装配bean
声明bean
构造器注入和setter方法注入
装配bean
控制bean的创建和销毁
一、Spring配置的可选方案
主要的装配机制:
1、在xml中进行显式配置
2、在Java中进行显式配置
3、隐式的bean发现机制和自动装配
尽可能的用自动配置的机制。
二、自动化装配bean
1、创建可被发现的bean
Spring从两个角度来实现自动化装配:
组件扫描:spring会自动发现应用上下文中所创建的bean。
自动装配:spring自动满足bean之间的依赖。
组件扫描和自动装配组合在一起就能发挥出强大的威力,它们能够将你的显示装配降到最低。
① 因为使用了@Component注解,所以spring会为你把事情处理妥当。
② @ComponentScan注解启用了组件扫描
③ 通过xml启用组件扫描
2、为组件扫描的bean命名
强烈推荐@Component命名
3、设置组件扫描的基础包
@Configuration
@ComponentScan("soundsystem")
public class CDPlayConfig{
}
设置组件扫描的基础包
@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class,DVDPlayer.class})
public class CDPlayerConfig{
}
4、通过为bean添加注解实现自动装配
@Autowired
将required属性设置为false时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,spring将会让这个bean处于未装配的状态。但是此时要进行null值检查,否则会出现空指针异常。
@Inject注解来源于Java依赖注入规范
spring同时支持@Autowired和@Inject。
5、验证自动装配
三、通过Java代码装配bean
当要将第三方库中的组件装配到程序中时,这种情况下是没办法加@Component和@Autowired注解的,因此不能自动化装配。
显式配置时,JavaConfig是更好的方案,JavaConfig是配置代码,JavaConfig要放到单独的包中。
1、创建配置类
创建JavaConfig类的关键在于为其添加@Configuration注解,@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。
2、声明简单的bean
3、借助JavaConfig实现注入
@Bean注解表明这个方法会创建一个bean实例并将其注册到Spring应用上下文中。
4、通过xml装配bean
① 创建XML配置规范
② 声明一个简单的bean
③ 借助构造器注入初始化bean
④ 设置属性
推荐构造器注入
5、导入和混合配置
① 在JavaConfig中引用XML配置
② 在XML配置中引用JavaConfig
6、小结
Spring框架的核心是Spring容器。容器负责管理应用中组件的生命周期,它会创建这些组件并保证他们的依赖能够得到满足,这样的话,组件才能完成预定的任务。在本章中,我们看到了在Spring中装配bean的三种主要方式,这些技术描述了Spring应用中的组件以及这些组件之间的关系。
尽可能使用自动化配置,以避免显示配置所带来的维护成本。但是,如果你确实需要显示配置Spring的话,应该优先选择基于Java的配置,它比基于CML的配置更加强大、类型安全并且易于重构。
第三章 高级装配
Spring profile
条件化的bean声明
自动装配与歧义性
bean的作用域
Spring表达式语言
一、环境与profile
1、spring中的profile是什么?
profile可以理解为我们在Spring容器中所定义的Bean的逻辑组名称,只有当这些Profile被激活的时候,才会将Profile中所对应的Bean注册到Spring容器中。举个更具体的例子,我们之前定义bean的时候,当spring容器以启动的时候,就会一股脑的全部加载这些信息完成对bean的创建;而使用profile后,它会将bean的定义进行更细粒度的划分,将这些定义的bean划分为几个不同的组,当spring容器加载信息的时候,首先查找激活的profile,然后只会去加载被激活的组中所定义的bean信息,而不被激活的profile中所定义的bean是不会加载用于创建bean的。
2、为什么要是用profile
因为需要啥就加载啥,不需要的就不用了加载了
3、配置spring profile
在3.1版本中,spring引入了bean profile的功能。要使用profile,首先要将所有不同的bean定义整理到一个或多个profile中,将应用部署到每个环境中,确保对应的profile处于激活(active)的状态。
在java配置中,可以使用@profile注解指定某个bean属于哪一个profile。
4、激活profile
spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。如果配置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile是激活的。但是如果没有配置spring.profiles.active,那spring将会查找spring.profiles.default的值。
有两种方式来设置这两个属性:
- 作为DispatcherServlet的初始化参数
- 作为web应用的上下文参数
- 作为环境变量
- 作为JVM的系统属性
- 在继承测试类上,使用@ActiveProfiles注解配置。
我所喜欢的一种方式是使用DispatcherServlet的参数将spring.profiles.default设置为开发环境的profile,我会在servlet上下文中进行设置(为了兼顾到ContextLoaderListener)。例如,在web应用中,设置spring.profiles.default的web.xml文件会如下所示:
在web应用的web.xml文件中设置默认的profile
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<context-param>
<param-name>spring.profiles.default</param-name><!--为上下文设置默认的profile-->
<param-value>dev</param-value>
</context-param>
<listen>
<listen-class>
org.springframework.web.context.ContextLoaderListenr
</listen-class>
</listen>
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>spring.profiles.default</param-name><!--为servlet设置默认的profile-->
<param-value>dev</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</beans>
按照这种方式设置spring.profiles.default,所有开发人员都能从版本控制软件中获得应用程序源码,并使用开发环境的设置运行代码,而不需要额外的配置。
系统会优先使用spring.profiles.active中所设置的profile。
spring提供了@ActiveProfiles注解,我们可以使用它来指定运行测试时要激活哪个profile。在集成测试时,通常想要激活的是开发环境的profile。
在条件化创建bean的时候,spring的profile机制是一种很棒的方法,这里的条件要基于哪个profile处于激活状态来判断。
二、条件化的bean
spring4引入了一个新的@Conditional注解,它可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否者,这个bean就会被忽略。
例如,假设有一个名为MagicBean的类,我们希望只有设置了magic环境属性的时候,Spring才会实例化这个类。如果环境中没有这个属性,那么MagicBean将会被忽略。
条件化的创建bean:
@Bean
@Conditional(MagicExistsCondition.class)//条件化地创建bean
public MagicBean magicBean(){
return new MagicBean();
}
可以看到,@Conditional中给定了一个Class,它指明了条件,在本例中,也就是MagicExistsCondition。@Conditional将会通过Condition接口进行条件对比:
public interface Condition{
boolean matches(ConditionContext ctxt,AnnoatedTypeMetadata metadata);
}
设置给@Conditional的类可以是任意实现了Condition接口的类。可以看出,这个接口实现起来很简单直接,只需提供matches()方法的实现即可。如果matches()方法返回true,那么就会创建带有@Conditional注解的bean。如果matches()方法返回false,将不会创建bean。
在condition中检查是否存在magic属性
public class MagicExistsCondition implements Condition{
public boolean matches(ConditionContext ctxt,AnnoatedTypeMetadata metadata){
Environment env = context.getEnvironment();
return env.containsProperty("magic");//检查magic属性
}
}
在上面的程序中,matches()方法很简单但功能很强大。它通过给定的ConditionContext对象进而得到Environment对象,并使用这个对象检查环境中是否存在名为magic的环境属性。如果返回true,就表示@Conditional注解上引用MagicExistsCondition的bean都会被创建。
ConditionContext是一个接口,大致如下所示:
public interface ConditionContext{
//检查bean定义
BeanDefinitionRegistry getRegistry();
//检查bean是否存在,检查bean的属性
ConfigurableListableBeanFactory getBeanFactory();
//检查环境变量是否存在以及它的值是什么
Environment getEnvironment();
//读取ResourceLoader加载的资源
ResourceLoader getResourceLoader();
//加载并检查类是否存在
ClassLoader getClassLoader();
}
AnnotatedTypeMetadata则能够让我们检查带有@bean注解的方法上是否还有其他的注解。像ConditionContext一样,AnnotatedTypeMetadata也是一个接口。
public interface AnnotatedTypeMetadata{
//借助isAnnotated()方法,判断带有@bean注解的方法是否还有其他特定的注解。
boolean isAnnotated(String annotationType);
//检查@bean注解的方法上其他注解的属性
Map<String,Object> getAnnotationAttributes(String annotationType);
Map<String,Object> getAnnotationAttributes(String annotationType,boolean classValuesAsString);
MultiValueMap<String,Object> getAllAnnotationAttributes(String annotationType);
MultiValueMap<String,Object> getAllAnnotationAttributes(String annotationType,boolean classValuesAsString);
}
从spring4开始,@profile注解进行了重构,使其基于@Conditional和Condition实现。来看一下,spring4中@profile是如何实现的。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile{
String[] value();
}
@Profile本身也使用了@Condition注解,并且引用ProfileCondition作为Condition实现。如下所示,ProfileCondition实现了Condition接口,并且考虑到了ConditionContext和AnnotatedTypeMatadata中的多个元素。
ProfileCondition检查某个bean profile是否可用:
class ProfileCondition implements Condition{
public boolean matches(ConditionContext context,AnnotatedTypeMetadata){
if(context.getEnvironment()!=null){
MultiValueMap<String,Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if(attrs!=null){
for(Object value:attrs.get("value")){
if(context.getEnvironment().acceptsProfiles(String[] value)){
return true;
}
}
return false;
}
}
return true;
}
}
我们可以看到,ProfileCondition通过AnnotatedTypeMetadata得到了用于@Profile注解的所有属性,借助这些信息,它明确的检查value属性,该属性包含了bean的Profile名称,然后,它通过ConditionContext得到的Environment来检查[借助accpetsProfiles()方法]该profile是否处于激活状态。
三、处理自动装配的歧义性
如果不仅有一个bean能够匹配结果的话,这种歧义性会阻碍spring自动装配属性、构造器参数或方法参数。
如果三个实现类均使用了@Component注解,在组件扫描的时候,能够发现他们并将其创建为Spring应用上下文里面的bean,然后,当spring自动装配的时候,它并没有唯一、无歧义的可选值。此时,spring会抛出异常。
就算歧义性是个问题,但实际开发中很少遇见,给定的类型只有一个实现类就好了嘛,自动装配能够很好地运行。
但是,当歧义性确实发生时,spring提供了多种可选方案来解决这样的问题。你可以将可选bean中的某一个设为首选(primary)的bean,或者使用限定符(qualifier)来帮助spring将可选的bean的范围缩小到只有一个bean。
1、标识首选的bean
在声明bean的时候,通过将其中一个可选的bean设置为首选(primary)bean能够避免自动装配时的歧义性。当遇到歧义性的时候,Spring将会使用首选的bean,而不是其他可选的bean。
@Bean
@Primary
public class IceCrean implements Dessert{
}
如果XML配置bean的话,同样可以实现此功能。<bean>元素有一个primary属性来指定首选的bean。
2、限定自动装配的bean
@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
@Qualifier注解所设置的参数就是想要注入对的bean的ID。所有使用@Component注解声明的类都会创建bean,并且bean的ID为首字母变为小写的类名。因此,@Qualifier(“iceCream”)指向的是组件扫描时锁创建的bean,并且这个bean时IceCream类的实例。
@Component--把这些类纳入进spring容器中管理。
创建自定义的限定符
我们可以为bean设置自己的限定符,而不是依赖将beanID作为限定符。在这里需要加上@Qualifier,与@Component配合使用
@Component
@Qualifier("sexy")
public class IceCream implements Dessert{...}
在这种情况下,sexy限定符分配给了IceCreambean。因为它没有耦合类名,因此你可以随意重构IceCream的类名,而不必担心会破坏自动装配。在注入的地方,引用sexy限定符即可:
@Component
@Qualifier("sexy")
public class setDessert(Dessert dessert){
this.dessert = dessert;
}
当配置显式bean的时候,@Qualifier也可以与@bean注解一起使用:
@Bean
@Qualifier("sexy")
public Dessert iceCream{
return new IceCream();
}
当使用@Qualifier值时,最佳实践是为bean选择特征性或描述性的术语,而不是使用随便的名字。
通过声明自定义的限定符注解,我们可以同时使用多个限定符,不会再有Java编译器的限制或错误。与此同时,相对于使用原始的@Qualifier并借助string类型来指定限定符,自定义的注解更为安全。
为了创建自定义的条件化注解,我们创建了一个新的注解并在这个注解上添加了@Conditional。为了创建自定义的限定符注解,我们创建了一个新的注解并在这个注解上添加了@Qualifier。这种技术可以用到很多spring注解中,从而能够将它们组合在一起形成特定目标的自定义注解。
现在我们来看一下如何在不同的作用域中声明bean。
四、bean的作用域
默认情况下,spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其它bean多少次,每次所注入的都是同一个实例。
spring定义了多种作用域,可以基于这些作用域创建bean,包括:
- 单例(singleton):在整个应用中,只创建bean的一个实例。
- 原型(prototype):每次注入或者通过spring应用上下文获取的时候,都会创建一个新的bean实例。
- 会话(session):在web应用中,为每个会话创建一个bean实例。
- 请求(request):在web应用中,为每个请求创建一个bean实例。
单例是默认的作用域,对于易变的类型,这并不合适。如果选择其他的作用域,要使用@Scope注解,它可以与@Component或@bean一起使用。
@Component
@Scope(ConfigurableBeanFacory.SCOPE_PROTOTYPE)
public class Notepad{
}
使用ConfigurableBeanFacory类的SCOPE_PROTOTYPE常量设置了原型作用域。你当然也可以使用@Scope(“prototype”),但是使用SCOPE_PROTOTYPE常量更加安全并且不易出错。
同样也可以使用XML来配置bean:
<bean id="notepad" class="com.oschina.Notepad" scope="prototype"></bean>
不管你使用哪种方式来声明原型作用域,每次注入或从spring应用上下文中检索该bean的时候,都会创建新的实例。这样所导致的结果就是每次操作都能得到自己的notepad实例。
1、使用会话和请求作用域
在web应用中,如果能够实例化在会话和请求范围内共享的bean,那将时非常有价值的事情。例如,在典型的电子商务应用中,可能会有一个bean代表用户的购物车。如果购物车是单例的,那么将会导致所有的用户都会向同一个购物车中添加商品。另一方面,如果购物车是原型作用域的,那么在应用中某一个地方网购物车中添加物品,在应用的另外一个地方可能就不可用了,因为这里注入的是另一个原型作用域的购物车。
就购物车bean来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。要指定会话作用域,我们可以使用@Scope注解,它的使用方式与指定原型作用域是相同的:
@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopeProxyMode.INTERFACES)
public ShoppingCart cart(){
}
我们将value设置成了WebApplicationContext中的SCOPE_SESSION常量,这回告诉spring为web应用中每一个会话创建一个ShoppingCart。这会创建多个ShoppingCart的bean实例,但是对于给定的会话只是创建了一个实例,在当前会话中,这个bean相当于单例的。
要注意的是,@Scope同时还有一个proxyMode属性,它被设置成了ScopeProxyMode.INTERFACES。这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。在描述proxyMode属性之前,我们先来看一下ProxyMode所解决的场景。
假设我们将shoppingCart bean注入到单例StoreService bean的setter方法中,如下所示:
@Component
public class StoreService{
@Autowired
public void setShoppingCart(ShoppingCart shoppingCart){
this.shoppingCart=shoppingCart;
}
...
}
@Autowired是用在Javabean中的注解,通过byType形式,用来给指定的字段或方法注入所需的外部资源。
先看一下bean实例化和@Autowired装配过程:
1、一切都是从bean工厂的getBean方法开始的,一旦该方法调用总会返回一个bean实例 。
2、实例化和装配过程中会多次递归调用getBean方法来解决类之间的依赖。
3、@Autowired是根据类型来查找和装配的,但是我们设置了<beans default-autowired="byName"/>后会影响最终的类型匹配查找。因为在前面有根据BeanDefinition的autowire类型设置PropertyValue值的一步,其中会有新实例的创建和注册。就是那个autowireByName方法
因为StoreService是一个单例bean,会在spring应用上下文加载的时候创建,当它创建的时候,spring会试图将ShoppingCart bean注入到setShoppingCart()方法中。 但ShoppingCart bean是会话作用域的,此时并不存在。直到某个用户进入系统,创建了会话之后,才会出现ShoppingCart实例。
另外,系统中将会有多个ShoppingCart实例,每个用户一个,我们并不想让spring注入某个固定的ShoppingCart实例到storeService中。我们希望的是当StoreService处理购物车功能时,它所使用的ShoppingCart实例恰好是当前会话所对应的那一个。
2、在xml中声明作用域代理
如果你需要使用XML来声明会话或请求作用域的bean,那么就不能使用@Scope注解及其ProxyMode属性了。<bean>元素的scope属性能够设置bean的作用域,但是该怎样指定代理模式呢?
要设置代理模式,我们需要使用Spring aop命名空间的一个新元素:
<bean id="cart" class="com.oschina.ShoppingCart" scope="session">
<aop:scoped-proxy/>
</bean>
<aop:scoped-proxy/>是与@Scope注解的ProxyMode属性功能相同的Spring XML配置元素。它会告诉spring为bean创建一个作用域代理。默认情况下,它会使用CGLib创建目标类的代理。但是我们也可以将proxy-target-class属性设置为false,进而要求它生成基于接口的代理:
<bean id="cart" class="com.oschina.ShoppingCart" scope="session">
<aop:scoped-proxy proxy-target-class="false" />
</bean>
为了使用<aop:scoped-proxy>元素,我们必须在XML配置中声明spring的aop命名空间:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
...
</beans>
在第4章中,我们使用spring和切面编程的时候,会讨论spring aop命名空间的更多知识,现在我们来看一下spring高级配置的另外一个可选方案:spring表达式语言(spring expression language)。
五、运行时值注入
1、注入外部的值
在Spring中,处理外部值最贱的方式就是声明属性源并通过spring的environment来检索属性。
package com.oschina
@Configuration
//声明属性源
@PropertySource("classpath:/com/oschina/app.properties")
public class ExpressiveConfig{
@Autowired
Environment env;
@Bean
public BlankDisc disc(){
//检索属性值
return new BlankDisc(env.getProperty("disc.title"),env.getProperty("disc.artist"));
}
}
深入学习spring的environment
直接从environment中检索属性是非常方便的,尤其是在Java配置中装配bean的时候。但是spring也提供了通过占位符装配属性的方法,这些占位符的值会来源于一个属性源。
解析占位符
在spring装配中,占位符的形式为使用“${...}”包装的属性名称。
<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:_title="${disc.title}" c:_artist="${disc.artist} />"
2、使用spring表达式语言进行装配
spring3引入了spring表达式语言(spring expression language ,SpEL),它能够以一种强大和简洁的方式将值装配到bean属性和构造器参数中,在这个过程中使用的表达式会在运行时计算的得到值,使用SpEL,你可以实现超乎想象的装配效果,这是使用其他的装配技术难以做到的。
SpEL拥有很多特性,包括:
- 使用bean的ID来引用bean
- 调用方法和访问对象的属性
- 对值进行算术、关系和逻辑运算
- 正则表达式匹配
- 集合操作
SpEL能够用在依赖注入以外的其他地方,例如,spring Security支持使用SpEL表达式定义安全限制规则。另外,如果你在spring MVC应用中使用Thymeleaf模板作为视图的话,那么这些模板可以使用SpEL表达式引用模板数据。
SpEL样例
SpEL是一种非常灵活的表达式语言,所以本书中不可能面面俱到的介绍它的各种用法。但是我们可以展示几个基本的例子,这些例子会激发你的灵感,有助于编写自己的表达式。
SpEL表达式使用"#{...}",这与属性占位符有些类似,属性占位符是"${...}"。
除去"#{...}"标记之后,剩下的就是SpEL表达式体了,也就是一个数字常量。这个表达式的计算结果就是数字1,这恐怕不会让你感到丝毫惊讶。
#{T(System).currentTimeMillis()}
它的最终结果是计算表达式的那一刻的当前时间的毫秒数。T()表达式会将java.lang.System视为Java中对应的类型,因此可以调用其static修饰的currentTimeMillis()方法。
SpEL表达式也可以应用其他的bean或其他bean的属性。例如,如下的表达式会计算得到ID为sgtPeppers的bean的artist属性:
#{sgtPeppers.artist}
我们还可以通过systemProperties对象引用系统属性:
#{systemProperties['disc.title']}
这只是SpEL的几个基础样例。
如果通过组件扫描创建bean的话,在注入属性和构造器参数时,我们可以使用@Value注解,这与之前看到的属性占位符非常相似。不过,在这里我们所使用的不是占位符表达式,而是SpEL表达式。例如,下面的样例展示了BlankDisc,它会从系统属性中获取专辑名称和艺术家的名字:
public BlankDisc(@Value{'#systemProperties['disc.title']'} string title,
@Value{'#systemProperties['disc.artist']'} string artist){
this.title = title;
this.artist=artist;
}
在XML配置中,你可以将SpEL表达式传入<property>或<constructor-arg>的value属性中,或者将其作为p-命名空间或c-命名空间条目的值。例如,在如下BlankDisc bean的XML声明中,构造器参数就是通过SpEL表达式设置的:
<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:title="#systemProperties['disc.title']"
c:_artist="#[systemProperties['disc.artist']]">
现在我们来学一下SpEL所支持的基础表达式:
表示字面值
实际上可以用来表示浮点值、string值以及Boolean值。
下面的SpEL表达式样例所表示的就是浮点数:
#{3.141592657}
....
SpEL所能做到的另外一件基础的事情就是通过ID引用其他的bean。例如,你可以使用SpEL将一个bean装配到另一个bean的属性中,此时要是用bean ID作为SpEL表达式
SpEL运算符
例子:#{2*T(java.lang.Math).PI*circle.radius}
这不仅是使用SpEL中乘法运算符的绝佳样例,它也为你展示了如何将简单的表达式组合为更为复杂得表达式。
SpEL还提供了三元运算符
六、小结
我们在本章介绍了许多背景知识,在第二章所介绍的基本bean装配基础之上,又学习了一些强大的高级装配技巧。
首先,我们学习了spring profile,它解决了spring bean要跨各种部署环境的通用问题。在运行时,通过将环境相关的bean与当前激活的profile进行匹配,spring能够让相同的部署单元跨多种环境运行,而不需要进行重新构建。
Profile bean是在运行时条件化创建bean的一种方式,但是spring4提供了一种更为通用的方式,通过这种方式能能够声明某些bean的创建与否要依赖于给定条件的输出结果。结合使用@Conditional注解和Spring Condition接口的实现,能够为开发人员提供一种强大和灵活的机制,实现条件化的创建bean。
我们还看了两种解决自动装配歧义性的方法:首选bean以及限定符。尽量将某个bean设置为首选bean是很简单的,单这种方式也有其局限性,所以我们讨论了如何将一组可选的自动装配bean,借助限定符将其范围缩小到只有一个符合条件的bean。除吃之外,我们还看到了如何创建自定义的限定符注解,这些限定符描述了bean的特性。
尽管大多数的spring bean都是单例的方式创建的,但有的时候其它的创建策略更为合适。spring能够让bean以单例、原型、请求作用域或会话作用域的方式来创建。在声明请求作用域或会话作用域的bean的时候,我们还学习了如何创建作用域代理,它分为基于类的代理和基于接口的代理两种方式。
最后我们还学习了spring表达式语言,他能够在运行时计算要注入到bean属性中的值。
对于bean装配,我们已经掌握了扎实的基础知识,现在我们将注意力转向面向切面编程(AOP)。依赖注入能够将组建及其协作的其他组件解耦,与之类似,AOP有助于将应用组件与跨多个组件的任务进行解耦。在下一章,我们将学习在spring中如何创建和使用切面。