【Core Spring】二、装配beans

  在Spring中,对象不负责寻找和创建它们需要的其他对象。创建两个应用对象之间关联的动作是依赖注入的核心功能,通常称为装配。

  创建beans和构建它们之间的关系是Spring的责任,但是告诉Spring哪些bean需要被创建并且怎样将它们装装配到一起是开发者的责任。Spring提供了三种基本的装配机制。

      •  显式地通过XML配置
      •    显式地通过Java配置
      •    隐式地发现bean并且自动装配

 

自动装配beans

  Spring从两个角度解决自动装配

      •    Component scanning----Spring自动发现beans并在容器中创建
      •    Autowiring-----Spring自动构建bean的依赖
组件扫描

  组件扫描不是默认开启项,需要显式配置以便Spring寻找带有@Component注解的类并创建它们。@Component注解可以在它的属性中标注bean的ID。例如,@Component("id"),如果没有显式标注ID,那么Spring会将这个bean的ID设为类名并将首字母小写。基于Java配置如下:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class CDPlayerConfig {
}

除此之外,可以用Java Dependency Injection specification(JSR-330)中的注解@Named为bean提供ID。Spring支持将@Named替换@Component。

  如果没有更多配置@ComponentScan将会默认扫描与配置类所在包相同的类。但是,应该显式地设置扫描的base package,因为那样你可以将所有的配置代码归置到一个包中,区别于应用代码。例如这样@ComponentScan("basepackagename")、或者@ComponentScan(basePackages="soundsystem"),多个包的情况,@ComponentScan(basePackages={"soundsystem", "video"})。用上述String形式配置base package时,并不是类型安全的,如果重构包名,指定的base package将会出错,因此@ComponentScan可以通过base package中的类或者接口定义base package,如:@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})

  基于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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="soundsystem" />
</beans>
自动装配

  如果应用程序中所有的对象都是独立的,没有依赖的,那么组建扫描就能完成所需功能。但是如果对象包含对其他对象的依赖,那么自动装配就能派上用场了。

  @Autowired注解可以用在构造函数、属性的setter方法甚至任何方法上。无论构造函数、setter方法或其他任何方法,Spring将会尝试构建参数的依赖。仅当有且仅有一个bean匹配,才会将这个bean装配。如果没有bean匹配,Spring会在容器创建时抛出一个异常。为了避免这个异常你可以设置@Autowired的required属性为false,@Autowired(required=false)。当required属性为false时,Spring将会进行自动装配,但是如果没有bean匹配,会使需要注入依赖关系的bean未装配。你应该十分谨慎地设置required属性为false。bean的属性未被装配,有可能发生空指针异常。如果有多个bean匹配,Spring将会抛出异常表示出现歧义现象。

  同样的,可以用Java Dependency Injection specification中的注解@Inject代替@Autowired。

通过Java装配bean

  尽管通过自动装配的方式构造依赖很方便,但是有时我们必须显式地配置Spring。比如,需要从第三方类库中装配一些组件进入你自己的应用。因为你无法获取源码,所以没有机会在类上标记@Component和@Autowired注解。

  Java方式配置优于XML方式的配置,它更强大,类型安全,方便重构。

  创建一个JavaConfig类的关键在于用@Configuration注解标记它。通过编写一个创建所需类型实例的方法并且用@Bean标记它的方式在JavaConfig中声明一个bean。

@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}

  默认地,bean的ID会被设置为同@Bean注解标识的方法名称一样。也可以指定ID,@Bean(name="lonelyHeartsClubBand")。

  如果一个bean需要依赖其他对象,最简单的装配方法是引用被依赖对象的方法,如下:

@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}

  cdPlayer方法与sgtPeppers方法有着微妙的不同,CDPlayer对象是通过调用接受CompactDisc对象作为参数的构造方法来创建的,而不是通过默认构造方法。貌似CompactDisc对象是通过调用sgtPeppers来构造的,但并不是这样。因为sgtPeppers()方法被注解了@Bean,Spring将会拦截任何对它的调用,以确保由sgtPeppers()方法生成的对象已经被返回了,而不是允许sgtPeppers()又一次被调用。例如:

@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
@Bean
public CDPlayer anotherCDPlayer() {
return new CDPlayer(sgtPeppers());
}

  如果对sgtPeppers()方法的调用与其他Java方法一样,那么每一个CDPlayer将会拥有自己的SgtPeppers实例。默认情况下,Spring的beans都是单例的。所以Spring拦截对sgtPeppers()方法的调用以确保返回的是由Spring自己调用sgtPeppers()方法生成的CompactDisc Bean。因此,两个CDPlayer Bean拥有相同的SgtPeppers实例。

  这样做法很迷惑,下面是一种更简单和容易理解的方法。

@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}

  cdPlayer方法接受一个CompactDisc对象作为参数。当Spring调用cdPlayer方法生成CDPlayer Bean时,将会自动装配一个CompactDisc对象进入cdPlayer方法。

  如果需要通过setter方法的方式实现DI,如下:

@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
CDPlayer cdPlayer = new CDPlayer(compactDisc);
cdPlayer.setCompactDisc(compactDisc);
return cdPlayer;
}

 

通过XML装配bean

<?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">
<!-- configuration details go here -->
</beans>

  相当于@Configuration

<bean class="soundsystem.SgtPeppers" />

  相当于@Bean,如果没有指定ID会被默认命名为全称限定名,此例中为soundsystem.SgtPeppers#0#0是用来区分类型相同bean的编号。最好指定ID

<bean id="compactDisc" class="soundsystem.SgtPeppers" />

  首先值得注意的是,不像在JavaConfig中,你直接负责创建一个SgtPeppers实例。当Spring看见<bean>元素,它会通过默认构造函数帮你创建一个SgtPeppers bean。用XML配置Bean创建更加被动。

 

  另外一件值得注意的是,bean的类型通过class属性的字符串设置。但是谁能保证class属性指向一个真的类?SpringXML配置无法在编译期验证指向的Java类型。甚至于,如果你重命名了这个类,它将指向一个不存在的类。

  在SpringXML配置中,只有一种方法声明bean,通过<bean>元素。但是当通过XML声明DI,将会有几种方式和风格。具体到构造器注入,主要有两种选择:  

      • <constructor-arg>元素
      • 使用Spring3.0中引入的c-namespace

  两者之间的差异是非常复杂的。如你所见,<constructor-arg>元素通常比c-namespace更冗长,在XML中也更难阅读。另一方面,<constructor-arg>元素可以做一些c-namespace做不到的。在构造器注入中,我们将两者作为并列选项。

<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>

  Spring容器见到<bean>元素,它会创建一个CDPlayer实例。<constructor-arg>元素告诉它传递一个IDcompactDiscbean的引用给CDPlayer的构造器。当然也可以使用Springc-namespacec-namespaceSpring3.0中引入的一种更简洁的XML中构造器注入方式。如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
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">
    <bean id="cdPlayer" class="soundsystem.CDPlayer"
c:cd-ref="compactDisc" />
</beans>

  c代表命名空间,cd代表构造器参数名称,-ref是一种命名约定,引入一个名为compactDiscbean。但是通过参数名称引用bean会产生问题,指向参数位置会更好。

<bean id="cdPlayer" class="soundsystem.CDPlayer"
c:_0-ref="compactDisc" />

  这个比上一个XML看起来还古怪,由于XML不允许数字作为属性的第一个字符,所以用了_作为前缀。如果只有一个参数,可以根本不制定参数

<bean id="cdPlayer" class="soundsystem.CDPlayer"
c:_-ref="compactDisc" />

  通过构造器注入基本类型如下

<bean id="compactDisc"
class="soundsystem.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
</bean>

  c-namespace方式

<bean id="compactDisc"
class="soundsystem.BlankDisc"
c:_title="Sgt. Pepper's Lonely Hearts Club Band"
c:_artist="The Beatles" /><bean id="compactDisc"
class="soundsystem.BlankDisc"
c:_0="Sgt. Pepper's Lonely Hearts Club Band"
c:_1="The Beatles" />

  <constructor-arg>元素可以做但是c-namespace方式无法实现的是注入容器

<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
<constructor-arg>
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
<!-- ...other tracks omitted for brevity... -->
</list>
</constructor-arg>
</bean><constructor-arg>
<list>
<ref bean="sgtPeppers" />
<ref bean="whiteAlbum" />
<ref bean="hardDaysNight" />
<ref bean="revolver" />
...
</list>
</constructor-arg>

  setter方法注入

<bean id="cdPlayer"
class="soundsystem.CDPlayer">
<property name="compactDisc" ref="compactDisc" />
</bean>

  同样的,Spring提供p-namespace代替<property>元素。需要引入如下命名空间。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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">
...
</bean>

  用p-namespace装配属性,具体含义同c-namespace相同。

<bean id="cdPlayer"
class="soundsystem.CDPlayer"
p:compactDisc-ref="compactDisc" />

  p-namespace无法装配容器。

posted on 2016-04-05 17:50  路灯Evan  阅读(141)  评论(0编辑  收藏  举报