Spring-装配Bean

  • 声明bean
  • 构造器注入和Setter方法注入
  • 装配bean
  • 控制bean的创建和销毁

创建应用对象之间协作关系的行为通常称之为装配(wiring),这也是依赖注入(DI)的本质.

Spring配置的可选方案

Spring有三种主要的装配机制:

  • 在XML中进行显示配置
  • 在Java中进行显示配置
  • 隐式的bean发现机制和自动装配

同时Spring的配置风格是可以互相搭配的,推荐使用自动配置,其次使用JavaConfig,最后是XML方式.

自动化装配bean

Spring从两个角度来实现的自动化装配:

  1. 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean.
  2. 自动装配(autowiring):Spring自动满足bean之间的依赖.

两者组合一起就可发挥强大的威力.

创建可被发现的bean

CD接口

1
2
3
4
5
6
7
8
9
10
public interface CompactDisc{
void play();
}
@Component//声明后可被扫描到
public class SgtPeppers implements CompactDisc{
private String msg = "sgtpepers";
public void play(){
System.out.println(msg);
}
}

默认组件扫描是不启动的,所以要手动启动它.

  1. JavaConfig方式

    默认扫描JavaConfig类同包下的类,也可以给 @ComponentScan(basePackages = "包路径") 参数来指定某个包.

    1
    2
    3
    4
    5
    @Configuration
    @ComponentScan//启动扫描
    public class CDPlayerConfig{
    //...
    }
  2. XML方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    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-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="包路径"/>
    </beans>

测试

1
2
3
4
5
6
7
8
9
10
11
@RunWith(SpringJUnit4ClassRunner.class)//用来在测试类中加载配置
@ContextConfiguration(classes = CDPlayerConfig.class)//加载配置
public class MainTest {
@Autowired//自动装载
private CompactDisc cd;
@Test
public void test(){
System.out.println(cd);
Assert.assertNotNull(cd);
}
}

为组件扫描的bean命名

当有多个CompactDisc需要被声明时,自动装载并不知道装载哪一个,所以需要给bean命名.

1
2
3
4
5
@Component("cd001")//声明后可被扫描到
//@Named("cd001")是它的代替方案,推荐使用Component注解
public class SgtPeppers implements CompactDisc{
//...上文有,省略
}

设置组件扫描的基础包

自动扫描注解 ComponentScan 有参数可以指定包,当扫描时它就仅仅在指定包内进行扫描.

1
2
@ComponentScan(basePackage="com.yuda.one")//单包
@ComponentScan(basePackage={"com.yuda.one","com.yuda.two"})//多包

也可以指定类,注意:指定类后该类同包下的所有类(兄弟类)都可以被扫描到

1
2
@ComponentScan(basePackageClasses=CDPlayer.class)//CDPlayer类同包的类
@ComponentScan(basePackageClasses={CDPlayer.class,DVDPlayer.class})//CDPlayer类和DVDPlayer类同包的类

技巧: 使用basePackageClasses扫描可以扫描该类的兄弟类(同一包下的类),我们可以设置一个空标记接口,让JavaConfig能扫描到标记接口的兄弟类,好处自己想.

1
2
3
@Component//Marker的兄弟都可以被扫描到
public interface Marker {
}

通过bean添加注解实现自动装配

现在组件已经有啦,就需要考虑把组件自动装配到哪一个对象上面去,这里用到了 @Autowired 标签,可以设置到构造器上,Setter方法上,字段上,甚至以组件为参数的任何方法上.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired//对构造器配置
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
@Override
public void play() {
System.out.println("CDPlayer开始播放");
cd.play();
}
}

还可以作用于Setter方法

1
2
3
4
@Autowired
public void setCd(CompactDisc cd) {
this.cd = cd;
}

甚至可以作用于其他方法和字段

1
2
3
4
5
6
7
@Autowired
private CompactDisc cd;
//----------------------//
@Autowired
public void a(CompactDisc cd) {
this.cd = cd;
}

如果没有匹配的组件,Spring就会抛出一个异常,为了避免异常的出现可以给 @Autowired 添加required属性设置为false.那么及时没有匹配的组件,也不会报异常了,Spring会让这个bean处于为装配状态,配置该属性,很容易出现NullPointerException.

通过Java代码装配bean

尽管推荐使用自动装配的方式,但是有些情况是无法进行自动装配的,比如我想把第三方的组件添加到我的应用中,我总不能去更改别人写的代码,给它加上注解吧.所以啊通过Java代码来显式装配是极好的.与XML相比下,Java代码方式更为强大,类型安全并且对重构友好.但是JavaConfig是配置代码,它与其他的java代码不一样,它里面没有任何的业务逻辑.通常会把JavaConfig放到一个固定的包里面.

创建配置类

配置类需要@Configuration 来声明:这是一个配置类,然后使用@ComponentScan 来启动组件扫描,否则这个JavaConfig将无法获取任何组件.

1
2
3
4
5
@Configuration
@ComponentScan(basePackageClasses = Marker.class)//如果用到注解就必须要写这个,而且扫描位置要正确.
public class CDPlayerConfig{
//...
}

声明简单的bean

要在JavaConfig中显式声明Bean,我们需要编写一个方法,这个方法就代表了一个bean,格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Bean(name = "s1")
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
//随机
@Bean(name = "s2")
public CompactDisc randomBeatlesCD(){
int choice = (int) Math.floor(Math.random()*2);
if (choice==0){
return new SgtPeppers2();
} else {
return new SgtPeppers();
}
}

这里声明了两个组件

借助JavaConfig实现注入

上面是一个简单的bean,如果有一个复杂的bean,要如何装配呢?

第一种简单的方式是通过构造器来导入:

1
2
3
4
5
6
7
8
9
10
@Bean
public CDPlayer cdPlayer(){
return new CDPlayer(sgtPeppers());
}
//其他
@Bean
public CDPlayer annotherCdPlayer(){
return new CDPlayer(sgtPeppers());
}
//这两个的构造器参数会得到相同的sgtPeppers实例

还有一种方式是这样的:

1
2
3
4
@Bean	//Qualifier,如果有很多相同返回类型的组件,就需要制定装配哪个组件
public CDPlayer cdPlayer(@Qualifier("s2") CompactDisc cd){
return new CDPlayer(cd);
}

使用第二种方式更加灵活,比如可以这样:

1
2
3
4
5
6
7
@Bean
public CDPlayer cdPlayer(@Qualifier("s2") CompactDisc cd){
//可以内部进行set方式的注入
CDPlayer cdPlayer = new CDPlayer();
cdPlayer.setCd(cd);
return cdPlayer;
}

通过XML装配bean

推荐使用JavaConfig但是对于老项目的维护,就需要学习XML方式装配bean.

创建XML配置规范

1
2
3
4
5
6
7
<?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">
<!-- 内容 -->
</beans>

声明一个简单

1
2
<bean id="..." name="..." class="com.yuda.component.SgtPeppers"/>
<!--默认id为com.yuda.component.SgtPeppers#0-->

如果自己没给其命名,Spring会自动给其命名,为”类路径#计数”,如果有相同的类被声明为组件,那么根据后面的计数来分辨装配哪个.最好写上明确的id.name与id一样有一样功效,但是id任何情况不可相同,name有情况是可以相同的(但不建议).

XML中是不需要自行创建实例的,Spring发现这个时,他会调用默认构造器来创建bean.而在JavaConfig中,就必须使用new来创建实例.

注意:如果没有代码提示工具,class的错误可能会带来一些麻烦,所以class必须保证可以找到

借助构造器注入初始化bean

构造器注入有两种方式:

  1. 元素,写得多,但是灵活.
  2. 使用Spring3.0引入的c-命名空间,写得少,但是不灵活.

构造器注入bean引用

1
2
3
4
5
<bean id="peppers" class="com.yuda.component.SgtPeppers"/>

<bean id="cdplayer" class="com.yuda.component.CDPlayer">
<constructor-arg ref="peppers"/><!-- 因为构造器里有个SgtPeppers类型的参数,就需要注入进去 -->
</bean>

还可以使用c-标签,需要先声明c的命名空间

1
2
<bean id="cdplayer" class="com.yuda.component.CDPlayer" c:cd-ref="peppers"/><!--cd为参数名-->
<bean id="cdplayer" class="com.yuda.component.CDPlayer" c:_0-ref="peppers"/><!--0为参数索引-->

讲字面量注入到构造器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class BlankDisc implements CompactDisc {

private String title;
private String artist;
public BlankDisc(String title, String artist) {
this.title = title;
this.artist = artist;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
@Override
public void play() {
System.out.println(title + "\t" + artist);
}
}

声明bean

1
2
3
4
<bean id="blank" class="com.yuda.component.BlankDisc">
<constructor-arg value="title is aaa"/><!--注入字面量-->
<constructor-arg value="a is bbb"/><!--注入字面量-->
</bean>

使用c

1
2
3
4
5
6
7
<bean id="blank" class="com.yuda.component.BlankDisc"
c:title="aaa"
c:artist="bbb"/>
<!--或者-->
<bean id="blank" class="com.yuda.component.BlankDisc"
c:_0="aaa"
c:_1="bbb"/>

装配集合

如果构造器需要注入一个集合.集合内为String类型数据

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="blank" class="com.yuda.component.BlankDisc">
<constructor-arg value="title is aaa"/><!--注入字面量-->
<constructor-arg value="a is bbb"/><!--注入字面量-->
<constructor-arg>
<list>
<value>1</value>
<value>1</value>
<value>1</value>
<value>1</value>
</list>
</constructor-arg>
</bean>

集合内为引用类型

1
2
3
4
5
<list>
<ref bean="peppers1"/>
<ref bean="peppers2"/>
<!-- ... -->
</list>

set同理,map如下:

1
2
3
<map>
<entry key="1" value="1"/>
</map>

设置属性

上述的是通过构造器来实现的注入,现在看看如何使用setter方法来实现.

1
2
3
<bean id="cdplayer" class="com.yuda.component.CDPlayer">
<property name="cd" ref="peppers"/>
</bean>

其实也用来构造器,但是是无参的,类没有无参构造器会报错

通过属性注入还可以用p命名空间

1
2
3
4
5
6
7
8
9
<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 id="cdplayer" class="com.yuda.component.CDPlayer" p:cd-ref="peppers"/>

</beans>

根据Setter注入和使用构造器注入类似,但是标签无法做到集合的注入.这里需要用到另外一个命名空间—–util

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

<util:list id="my_list">
<value>1</value>
<value>1</value>
<value>1</value>
<value>1</value>
<value>1</value>
</util:list>

<bean id="blank" class="com.yuda.component.BlankDisc"
c:title="aaa"
c:artist="bbb" c:tracks-ref="my_list">
</bean>

</beans>

通过id注入beans里面声明的list集合.

导入和混合配置

一个工程配置文件会非常多,所以需要导入

一个工程的不同业务使用JavaConfig或者XML,所以需要混合

在JavaConfig中引入XML配置

JavaConfig中导入JavaConfig

1
2
3
4
@Configuration
@Import({CDPlayerConfig1.class,CDPlayerConfi2g.class,....})
public class MainConfig {
}

JavaConfig中导入XML

1
2
3
4
@Configuration
@ImportResource("classpath*:Spring.xml")//*代表通配符,classpath还可以是file或者http
public class MainConfig {
}

在XML配置中引入JavaConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
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/util
http://www.springframework.org/schema/util/spring-util.xsd">

<bean class = "类路径"/><!-- 导入JavaConfig-->

<import resource = "XXX.xml"/><!-- 导入XML-->

</beans>

技巧: 可以有一个专门没有组件声明的XML,专门用来组装各种配置.