实现一个业务需要多个组件相互协作,创建组件之间关联关系的传统方法通常会导致结构复杂的代码,这些代码很难被复用和单元测试。在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。

SpringInAction读书笔记--第2章装配Bean的更多相关文章

  1. SpringInAction读书笔记--第1章Spring之旅

    1.简化Java开发 Spring是一个开源框架,它的根本使命在于简化java开发.为了降低java开发的复杂性,Spring采取了以下4种关键策略: 基于POJO的轻量级和最小侵入性编程      ...

  2. SpringInAction读书笔记--第4章面向切面

    1.什么是面向切面编程 在软件开发中,散布于应用中多处的功能被称为横切关注点,这些横切关注点从概念上是与应用的业务逻辑相分离的,但往往分直接嵌入到应用的业务逻辑之中,把这些横切关注点与业务逻辑相分离正 ...

  3. 《Linux内核设计与实现》第八周读书笔记——第四章 进程调度

    <Linux内核设计与实现>第八周读书笔记——第四章 进程调度 第4章 进程调度35 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间,进程调度程序可看做在可运行态进程之间分配 ...

  4. 《Linux内核设计与分析》第六周读书笔记——第三章

    <Linux内核设计与实现>第六周读书笔记——第三章 20135301张忻估算学习时间:共2.5小时读书:2.0代码:0作业:0博客:0.5实际学习时间:共3.0小时读书:2.0代码:0作 ...

  5. 《Linux内核设计与实现》 第八周读书笔记 第四章 进程调度

    20135307 张嘉琪 第八周读书笔记 第四章 进程调度 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间,进程调度程序可看做在可运行态进程之间分配有限的处理器时间资源的内核子系统.只有 ...

  6. 《Linux内核分析》读书笔记(四章)

    <Linux内核分析>读书笔记(四章) 标签(空格分隔): 20135328陈都 第四章 进程调度 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间,进程调度程序可看做在可运行 ...

  7. 《LINUX内核设计与实现》第三周读书笔记——第一二章

    <Linux内核设计与实现>读书笔记--第一二章 20135301张忻 估算学习时间:共2小时 读书:1.5 代码:0 作业:0 博客:0.5 实际学习时间:共2.5小时 读书:2.0 代 ...

  8. 《Linux内核设计与实现》第四周读书笔记——第五章

    <Linux内核设计与实现>第四周读书笔记--第五章 20135301张忻 估算学习时间:共1.5小时 读书:1.0 代码:0 作业:0 博客:0.5 实际学习时间:共2.0小时 读书:1 ...

  9. 《Linux内核设计与实现》第五周读书笔记——第十一章

    <Linux内核设计与实现>第五周读书笔记——第十一章 20135301张忻 估算学习时间:共2.5小时 读书:2.0 代码:0 作业:0 博客:0.5 实际学习时间:共3.0小时 读书: ...

随机推荐

  1. 让asp.net web api同时支持[AcceptVerbs("GET","POST")]

    在使用第三方接口时,有时候会看到接口同时支持GET和POST,当时想想webapi有AcceptVerbs特性,没有细想便想当然肯定会支持,后来项目中需要用到,当时在没有参数传入下确实支持,直到早几天 ...

  2. 有关字符串作为URL的 UTF8编码和解码的问题。

    当字符串要作为url访问的时候,我们对字符串中的中文非常头疼,这时候需就需要使用 UTF8来编码: //使用 stringByAddingPercentEscapesUsingEncoding 方法来 ...

  3. 从零开始学android开发-字符如何转换整形 string 转化为int

    int i = Integer.parseInt(string);

  4. 飘逸的python - 理解打开文件的模式

    当我们用open()函数去打开文件的时候,有好几种打开的模式.   'r'->只读 'w'->只写,文件已存在则清空,不存在则创建. 'a'->追加,写到文件末尾 'b'->二 ...

  5. Vs 2008 解决方案的目录结构设置和管理(转)

    http://blog.csdn.net/lcj_cjfykx/article/details/8632459 MS的这个IDE,实在庞杂得恐怖.从大学开始,我就一直用VC的各个版本写程序至今,细细想 ...

  6. Mechanism of Loading Resources

    Mechanism of Loading Resources 1. Distributed strategy 1.1. Developer guilde 1.2. Notes 2. Centraliz ...

  7. mysql聚合函数

    1.统计一下插入的数据总数 SELECT COUNT(giftCertificateId) AS number FROM gift_certificate WHERE giftCertificateN ...

  8. qt-vs-addin-版本支持

    qt-vs-addin-1.2.0-opensource.exe         VS200X qt-vs-addin-1.2.1-opensource.exe         VS200X qt-v ...

  9. Java基础知识强化之IO流笔记67:Properties的特殊功能使用

    1. Properties的特殊功能 public Object setProperty(String key,String value):添加元素 public String getProperty ...

  10. Azure PowerShell 创建虚拟机

    # 指定订阅名称$subscriptionName="订阅名称"# 指定云服务名称$serviceName="云服务名称"# 指定用来保存虚拟机VHD的存储$s ...