实现一个业务需要多个组件相互协作,创建组件之间关联关系的传统方法通常会导致结构复杂的代码,这些代码很难被复用和单元测试。在Spring中,对象不需要自己寻找或创建与其所关联的其它对象,Spring容器负责把需要相互协作的对象引用赋予各个对象。创建对象之间协作关系的行为称为装配,这也是依赖注入的本质。Spring为装配bean提供了三种主要的装配机制。
1.自动化装配bean
Spring从两个角度来实现自动化装配:
- 组件扫描:Spring会自动发现应用上下文中所创建的bean
- 自动装配:Spring自动满足bean之间的依赖。
组件扫描和自动装配组合起来能够将显式配置降低到最少。
@Component注解表明该类会作为组件类,并告知Spring要为这个类创建bean。
@Component
public class SgtPeppers implements CompactDisc {
...
}
不过,组件扫描默认是不启用的,我们还需要显式配置一下Spring,命令它去寻找带有@Component注解的类,并为其创建bean。我们可以通过JavaConfig定义Spring的装配规则,创建一个类,只需加上注解@Configuration表明它是一个JavaConfig,再使用@ComponentScan注解启用组件扫描。如果没有其它配置的话,@ComponentScan默认扫描与配置类相同的包及其子包,查找带有@Component注解的类,在Spring中自动为其创建一个bean。
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
也可以使用XML的方案启用组件扫描,<context:component-scan>元素会有与@ComponentScan注解相对应的属性和子元素。
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:component-scan base-package="soundsystem"/>
</beans>
Spring应用上下文中所有的bean都会给定一个ID,如果没有明确地为bean设置ID,Spring会根据类名为其指定一个ID,如果想为这个bean设置不同的 ID,将ID作为值传递给@Component注解。
@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
...
}
还有另一种为bean命名的方式,这种方式使用Java依赖注入规范中提供的@Named注解来为bean设置ID。
@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
...
}
@Named和@Component在大多数场景中可以互相替换,但是@Component更清楚地表明它是做什么的。
@ComponentScan默认扫描该类所在的包及其子包,但是我们想要将配置类放在单独的包中,使其与其它的应用代码区分开来。此时我们需要扫描多个包,将包名传给@ComponentScan可以指明扫描的包的名称。
@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig {
}
如果想更清晰表明所设置的是基础包,可以通过basePackages属性配置。
@Configuration
@ComponentScan(basePackages="soundsystem")
public class CDPlayerConfig {
}
也可以同时配置多个基础包,只需将basePackages属性设置为要扫描包的数组。
@Configuration
@ComponentScan(basePackages={"soundsystem", "video"})
public class CDPlayerConfig {
}
在上面的例子中,所设置的基础包是以String类型表示的,这种方法类型不安全,如果重构代码,所指定的基础包可能会出现错误。@ComponentScan还提供了另外一种方法,将其指定为包中所包含的类或接口。这些类所在的包会作为组件扫描的基础包。可以在包中创建一个用来进行扫描的空标记接口,因为在稍后重构中,应用代码可能会被移除掉。
@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {
}
Spring自动配置的另外一方面内容就是自动装配。自动装配是让Spring自动满足bean依赖的一种方法,在满足依赖过程中,会在Spring应用上下文中寻找匹配某个bean需求的其它bean。为了声明自动装配,Spring提供了@Autowired注解,它可以在构造器上添加,表明当Spring创建该类的bean时,通过这个构造器进行实例化并传入可设置给构造器参数类型的bean。
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd; @Autowired
public CDPlayer(CompactDisc cd){
this.cd = cd;
}
...
}
@Autowired注解不仅能够用在构造器上,还可以用在属性的Setter方法上,在Spring初始化bean之后,它会尽可能的去满足bean的依赖。
@Autowired
public void setCompactDisc(CompactDisc cd){
this.cd = cd;
}
实际上,Setter方法没有什么特殊之处,@Autowired注解可以用在类的任何方法上。无论使用以上哪种方法,Spring都会尝试满足方法参数上声明的依赖,如果只有一个bean匹配依赖需求的话,这个bean将会被装配进来。如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常,为了避免异常的出现,可以@Autowired(required=false)。
@Autowired(required=false)
public CDPlayer(CompactDisc cd){
this.cd = cd;
}
将required属性设置为false时,Spring会尝试自动装配,没有匹配的bean时,Spring将会让这个bean处于未装配的状态。但是这个处于未装配状态的属性可能会在接下来的代码运行中出现NullPointerException。如果有多个bean都能满足依赖关系,Spring将会抛出一个异常,表明没有明确选择哪个bean进行装配。如果不愿使用Spring特有注解@Autowired,可以使用来源于Java依赖注入规范的@Inject注解替换。
@Inject
public CDPlayer(CompactDisc cd){
this.cd = cd;
}
2.通过Java代码装配bean
有时候自动化配置的方案行不通,如需要将第三方库中的组件装配到自己的应用中时,无法在它的类上添加@Component和@Autowired注解,这时必须要采用显式装配的方式。进行显式装配时,JavaConfig是更好的方案,因为它更强大、类型安全并且对重构友好。因为它就是Java代码,和应用中的其它Java代码一样,同时它与其它的Java代码又有区别,它不应该包含业务逻辑,也不应该侵入到业务逻辑代码中。通常会将JavaConfig放到单独的包中,使它与其它的应用程序逻辑分离开。
创建JavaConfig类的关键在于为其添加@Configuration注解,表明这是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。
@Configuration
public class CDPlayerConfig {
}
要在JavaConfig中声明bean,我们需要编写一个方法,这个方法创建所需类型的实例,然后给这个方法加上@Bean注解。
@Bean
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
bean的ID与带有@Bean注解的方法名是一样的,也可以通过name属性指定一个不同的名字。
@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
方法体可以使用Java提供的所有功能,只要最终返回一个实例即可。
在JavaConfig中装配bean的最简单方式是引用创建bean的方法。
@Bean
public CDPlayer cdPlayer(){
return new CDPlayer(sgtPeppers());
}
看起来,CompactDisc是通过调用sgtPeppers()得到,但因为sgtPeppers()方法上添加了@Bean注解,实际上Spring将会拦截所有对它的调用,确保直接返回该方法所创建的bean,而不是每次都对其进行实际调用。默认情况下,Spring中的bean都是单例的。
还有一种更为简单的方式,当Spring调用该方法创建bean的时候,会自动装配一个CpmpactDisc到配置方法之中,不用明确引用CompactDisc的@Bean方法,这种方式引用其它的bean通常是最佳的选择,因为它不会要求将CompactDisc声明到同一个配置类中,甚至它可以通过组件扫描自动发现或XML来进行配置。
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
不管CompactDisc是什么方式创建出来的,Spring都会将其传入到配置方法中,用来创建CDPlayer bean。
另外,除了使用构造器实现DI功能,也可以使用其它风格的DI配置,如通过Setter方法注入。
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
CDPlayer cdPlayer = new CDPlayer();
cdPlayer.setCompactDisc(compactDisc);
return cdPlayer;
}
总之,带有@Bean注解的方法可以采用任何必要的Java功能产生bean实例。
3.通过XML装配bean
在XML配置中,要创建一个XML文件,并且以<beans>元素为根。最为简单的Spring XML配置如下所示。
<?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
http://www.springframework.org/schema/context" >
</beans>
基本的XML配置已经比同等功能的JavaConfig类复杂得多,在JavaConfig中所需要的只是@Configuration,但在使用XML时,需要在配置文件的顶部声明多个XML模式(XSD)文件,这些文件定义了配置Spring的XML元素。创建和管理Spring XML配置文件的一种简便方式是使用Spring Tool Suite。
要在基于XML的Spring配置中声明一个bean,要使用spring-beans模式的另外一个元素<bean>,它类似于JavaConfig中的@Bean注解。
<bean class="soundsystem.SgtPeppeers"/>
这个bean 的类通过class属性指定,它会根据全限定类名进行命名,这里bean的ID将会是"soundsystem.SgtPeppeers#0"。其中#0是一个计数形式,用来区分相同类型的其它bean。通常来讲更好的办法是借助id属性,为每个bean设置名字。
<bean id="compactDisc" class="soundsystem.SgtPeppeers"/>
当这个bean需要装配到其它bean时,会用到这个具体的名字,通常只对这些需要按名字引用的bean进行明确地命名。
声明DI可以使用构造器注入,<constructor-arg>元素提供了DI配置的方案。
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>
Spring会创建一个CDPlayer实例,将ID为 compactDisc的bean引用传递到CDPlayer的构造器中。
当我们使用一个字面量值配置对象时,可以使用constructor-arg的value属性。
<bean id="compactDisc" class="soundsystem.SgtPeppers">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
</bean>
装配集合时,使用<constructor-arg>的子元素<list>,这表明一个包含值的列表将会传递到构造器中。其中<value>元素用来指定列表中的每个元素。
<constructor-arg>
<list>
<value>Sgt.Peppers</value>
<value>With a Little</value>
</list>
</constructor-arg>
与之类似,我们也可以使用<ref>代替<value>,实现bean引用列表的装配。
<constructor-arg>
<list>
<ref bean="sgtPeppers" />
<ref bean="whiteAlbum" />
</list>
</constructor-arg>
可以按照同样的方式使用<set>元素,它们也可以装配数组。
以上完全是通过构造器注入,我们也可以使用属性的Setter方法实现属性注入。通常来说,对强依赖使用构造器注入,对可选性的依赖使用属性注入。
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<property name="compactDisc" ref="compactDisc" />
</bean>
<property>元素为属性的Setter方法提供的功能与<constructor-arg>元素为构造器提供的功能一样,上例中,它引用了ID为compactDisc的bean,通过setCompactDisc()方法将其注入到compactDisc属性中。
属性也可以注入字面量和集合,这与构造器参数非常相似。
<bean id="compactDisc" class="soundsystem.SgtPeppers">
<property name="title" value="Sgt. Pepper's Lonely Hearts Club Band" />
<property name="artist" value="The Beatles" />
<property name="tracks" >
<list>
<value>Sgt.Peppers</value>
<value>With a Little</value>
</list>
</property>
</bean>
4.导入和混合配置
我们可能会同时使用自动化和显式配置,在Spring中支持混合配置。
在自动装配时它并不在意要装配的bean来自哪里,无论它是在JavaConfig或XML中声明的还是通过组件扫描获取到的。
在JavaConfig中引用XML配置
JavaConfig类可以使用@Import注解引入另一个JavaConfig类。
@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig {
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
}
或者采用一个更好的办法,创建一个更高级别的JavaConfig类,在这个类中使用@Import注解将两个配置类组合在一起。
@Configuration
@Import({CDPlayerConfig.class, CDConfig.class})
public class SoundSystemConfig {
}
我们也可以使用@ImportResource注解在JavaConfig中引入XML配置。
@Configuration
@Import({CDPlayerConfig.class, CDConfig.class})
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig {
}
在XML配置中引用JavaConfig
在XML中我们可以使用<import>元素来拆分XML配置。
<import resoune="cd-config.xml"/>
如果要是使XML元素能够导入JavaConfig类,可以这样声明bean。
<bean class="soundsystem.CDConfig"/>
同样可以创建一个更高层次的XML文件,负责将多个配置组合起来。
<bean class="soundsystem.CDConfig"/>
<import resoune="cd-config.xml"/>
无论使用JavaConfig还是使用XML进行装配,通常都会创建一个根配置,将更多的装配类和XML文件组合起来,同时也会在根配置中启用组件扫描(<context:component-scan>或@ComponentScan)。
5.小结
Spring的配置风格是可以互相搭配的,建议尽可能地使用自动配置的机制。当你必须要显式配置bean的时候,推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。