装配 Bean

1 Spring 进行装配 Bean 的方式

1.1 组件扫描和自动装配

通过在 Bean 的定义类上加上注解 @Component 向 Spring 表明这是一个 Bean 组件;接着在 Spring 的配置类中用注解 @ComponentScan 让 Spring在启动的时候自动扫面有 @Component 注解的 Bean, 并在 Spring 上下文中创建他们的实例。Spring会根据类名为其指定一个ID,将类名的第一个字母变为小写,也可以传一个 bean id 给 @Component

Bean 类

1
2
3
4
5
6
@Component("enyaAlbum")
public class EnyaAlbum implements CompactDisk{
public void play() {
System.out.println("Playing..." + "\nTitle: May It Be" + "\nSinger: Enya");
}
}

Configuration 类

1
2
3
4
5
@Configuration
//指定扫描的基础包
@ComponentScan(basePackages={"sugar.cdsystem", "sugar.player"})
public class CDConfiguration {
}

basePackages 指定组件扫描的基础包

1
@ComponentScan(basePackages={"sugar.cdsystem", "sugar.player"})

basePackageClasses属性所设置的数组中包含了类。这些类所在的包将会作为组件扫描的基础包

1
@ComponentScan(basePackageClasses = {CompateDisc.class})

在依赖此对象的 Class 中,通过 @Autowired (Spring 特有的注解)或者 @Inject (Java 依赖注入规范) 注解用于构造器、setter 方法或任意方法,进行注入。

1.2 Java 配置类中配置

@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。

所有 Bean 的声明都可以在配置类中进行,而 Bean 的代码中无需添加任何代码,每一个 Bean 的声明都通过一个 ==Bean== 进行注解的方法返回一个实例。

带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例。构造器和Setter方法只是@Bean方法的两个简单样例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class CDConfiguration {

//默认 bean id 为方法名
//也可以用 name 指定
@Bean
public CompactDisk enyaAlbum() {
return new EnyaAlbum();
}

@Bean
public Player cdPlayer(CompactDisk compactDisk) {
return new CDPlayer(compactDisk);
}
}

1.3 XML 文件装配

XML时,需要在配置文件的顶部声明多个XML模式(XSD)文件,这些文件定义了配置Spring的XML元素。用来装配bean的最基本的XML元素包含在spring-beans模式之中

在XML中声明 DI 时,会有多种可选的配置方案和风格。具体到构造器注入,有两种基本的配置方案可供选择:

  • <constructor-arg>元素
  • 使用Spring 3.0所引入的c-命名空间

在装配集合方面,<constructor-arg>比c-命名空间的属性更有优势。

1
2
xmlns:c="http://www.springframework.org/schema/c"
<bean id="electricalPlayer" class="com.something.learn.bean.ElectricalPlayer" c:_0-ref="mp3Song"/>

属性注入时,既可以使用 <property> 元素,也可以使用 p-命名空间

1
2
xmlns:p="http://www.springframework.org/schema/p"
<bean id="blankCD" class="com.something.learn.bean.BlankCD" p:remark="音乐启迪人生">

无论是构造器注入还是属性注入, c-命名空间与 p-命名空间都不支持列表等数据结构,只支持引用及字面常量,但是可以使用 util-命名空间,声明 List、Set、Map 等结构,并且像 bean 一样可以在 c-命名空间或 p-命名空间中使用其引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<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"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<util:list id="trackList">
<value>突然的自我</value>
<value>晚风</value>
<value>浪人情歌</value>
</util:list>

<bean id="blankCD" class="com.something.learn.bean.BlankCD"
c:artist="伍佰"
c:albumName="冬之火 九重天演唱会特选录音专辑"
c:tracks-ref="trackList"
p:remark="音乐启迪人生"/>

2 Java 配置与 XML 配置的引用

2.1 在 JavaConfig 引用 xml 中声明的 bean

  1. Java 配置文件之间的引用

    使用 @Import 注解导入到另一个配置文件中

1
2
3
4
5
6
7
@Configuration
public class CDConfiguration {
@Bean
public CompactDisk enyaAlbum() {
return new EnyaAlbum();
}
}
1
2
3
4
5
6
7
8
@Configuration
@Import(CDConfiguration.class)
public class PlayerConfiguration {
@Bean
public Player cdPlayer(CompactDisk compactDisk) {
return new CDPlayer(compactDisk);
}
}
  1. 创建一个总的配置类,根配置类,将所有 Java 配置类导入
1
2
3
4
@Configuration
@Import({CDConfiguration.class, PlayerConfiguration.class})
public class Configuration {
}
  1. 在 Java 配置文件中引用 XML 的配置
1
2
3
4
5
6
7
8
9
/*
* 假设Player配置在Java文件中
* CD配置在XML文件中
*/
@Configuration
@Import(PlayerConfiguration.class)
@ImportResource("classpath:CDConfiguration.xml")
public class Configuration {
}

2.2 在 xml 中引用 JavaConfig 声明的 bean

  1. 在 XML 文件中引用另一个 XML 配置文件
1
<import resource="CDConfiguration.xml" />
  1. 在 XML 文件中引用 Java 的配置

    为了将 JavaConfig 类导入到 XML 配置中,我们可以这样声明bean:

1
2
3
4
5
6
7
 <!--CDConfiguration是Java中的配置类-->
<bean class="sugar.configuration.CDConfiguration" />

<!--cdPlayer是在XML文件中配置的-->
<bean id="cdPlayer"
class="sugar.player.CDPlayer"
c:cd-ref="compactDisc" />
  1. 使用第三个配置文件组合, 也称之为==根配置==
1
2
<bean class="sugar.configuration.CDConfiguration" />
<import resource="playerConfiguration.xml" />

根配置类或根配置文件中启用组件扫描,通过<context:component-scan>@ComponentScan

@Autowired 可以使用在构造器上(构造器注入),也可以使用在 setter 方法上(方法注入)

3 总结

Spring 中多种装配方式、多个配置文件可能看起来很是杂乱无章,因此最好有一个根配置,将所有的配置导入进来,进行声明。

尽量选择自动化配置,接着是 Java 配置(类型安全、易于重构、自定义Bean的实例化方式),最后采用 xml 方式的配置。 显示配置越少越好。

JNDI

二 高级装配

2.1 为环境相关的 Bean 启用 profile 配置

有连个参数可以设置激活 active 的 profile:

  • spring.profiles.active

  • spring.profiles.default

有多种方式来设置这两个属性:

  • 作为DispatcherServlet的初始化参数;

  • 作为Web应用的上下文参数;

  • 作为JNDI条目;

  • 作为环境变量;

  • 作为JVM的系统属性;

  • 在集成测试类上,使用@ActiveProfiles注解设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
@ActiveProfiles("word")
public class JunitTest {
@Autowired private DataSource dataSource;

@Test
public void dataSourceTest() {
assertNotNull(dataSource);
try {
DatabaseMetaData metaData = dataSource.getConnection().getMetaData();
System.out.println(metaData.getDatabaseProductName());
} catch (SQLException e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
org.springframework.context.annotation @Retention(RetentionPolicy.RUNTIME) 
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
public interface Profile
extends annotation.Annotation
Indicates that a component is eligible for registration when one or more specified profiles are active.
A profile is a named logical grouping that may be activated programmatically via ConfigurableEnvironment.setActiveProfiles or declaratively through setting the spring.profiles.active property, usually through JVM system properties, as an environment variable, or for web applications as a Servlet context parameter in web.xml.
The @Profile annotation may be used in any of the following ways:
as a type-level annotation on any class directly or indirectly annotated with @Component, including @Configuration classes
as a meta-annotation, for the purpose of composing custom stereotype annotations
as a method-level annotation on any @Bean method
If a @Configuration class is marked with @Profile, all of the @Bean methods and @Import annotations associated with that class will be bypassed unless one or more of the specified profiles are active. This is very similar to the behavior in Spring XML: if the profile attribute of the beans element is supplied e.g., <beans profile="p1,p2">, the beans element will not be parsed unless profiles 'p1' and/or 'p2' have been activated. Likewise, if a @Component or @Configuration class is marked with @Profile({"p1", "p2"}), that class will not be registered/processed unless profiles 'p1' and/or 'p2' have been activated.
If a given profile is prefixed with the NOT operator (!), the annotated will be registered if the profile is not active. e.g., for @Profile({"p1", "!p2"}), registration will occur if profile 'p1' is active or if profile 'p2' is not active.
If the @Profile annotation is omitted, registration will occur, regardless of which (if any) profiles are active.
When defining Spring beans via XML, the "profile" attribute of the <beans> element may be used. See the documentation in spring-beans XSD (version 3.1 or greater) for details.

2.2 条件化的 bean

2.3 歧义性

当有多个 bean 满足自动装配所需的类型时,如何消除歧义

  • @primary 注解设置首选 bean

    各种配置方式中都可以指定

    多个类型相同 bean 都指定 @primary, 注入的时候(使用类型)也会带来歧义性

  • @qualifier 限定某个 bean

    @Component 、JavaConfig 的 @bean 组合使用

    每个 bean 都可以指定一个限定符,如果没有指定,限定符与 bean id 相同,但如果类的名称(组件扫描)或 bean id 变化时,需要修改注入时指定的限定符;为了避免这种不一致性,可以为 bean 通过 @qualifier("qua1") 指定限定符,注入的时候,也指定相同的限定符即可,这样类的名字和 bean id 就可以随意修改;

    可以为每个 bean 指定多个限定符,例如,可以按属性、特性等为 bean 指定限定符,多个 bean 可以拥有相同的限定符,这时,注入的时候,也会产生歧义,但是可以指定更多的限定符,将范围缩小至一个,但 Java 不支持同时指定多个相同的注解,可以通过自定义注解实现(创建自定义注解,同时在该注解的定于上使用 @qualifier 注解)。

1
2
3
4
5
6
7
8
9
10
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy { }

@Component
@Cold
@Creamy
public class IceCream implements Dessert { ... }