Spring Bean 管理

在 Spring 中通过配置文件描述 Bean 和 Bean 之间的依赖关系,利用 Java 语言的反射功能实例化 Bean 并建立 Bean 之间的依赖关系。

Spring 的工厂类

Bean 工厂(org.springframework.beans.factory.BeanFactory)是 Spring 中最核心的接口,BeanFactory 是类的通用工厂能够创建并管理各种类的对象。ApplicationContext 是在 BeanFactory 基础上派生而来的,提供了更多面向实际应用的功能。

BeanFactory 是在工厂实例化后调用 getBean() 时才创建类的实例,ApplicationContext 是在加载配置文件时就会将配置文件中单例模式生成的类全部实例化。

BeanFactory 和 ApplicationContext 关系如图:

ApplicationContext 的主要实现类是 ClassPathXmlApplicationContext(默认从类路径中加载配置文件)和 FileSystemXmlApplicationContext(默认从文件系统中装载配置文件),本文使用 ClassPathXmlApplicationContext。

加载组件

借助 maven 建立一个工程,在 pom.xml 文件中配置以下依赖:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<properties>
<spring.version>4.3.15.RELEASE</spring.version>
...
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>

<!-- Bean 管理的注解方式所必须的 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>

Bean 管理的 XML 方式

Spring 的 Bean 管理有两种方式:XML 方式和注解方式,先来看看 XML 方式。

src/main/resources 目录下添加 XML 配置文件 applicationContext.xml

1
2
3
4
5
6
<?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>

Bean 的实例化

在 XML 方式中对于 Bean 的实例化有三种方式:类构造器实例化(默认无参数)、静态工厂方法实例化(简单工厂模式)、实例工厂方法实例化(工厂方法模式)。

类构造器实例化

首先定义一个类,该类只有一个无参构造方法:

1
2
3
4
5
6
7
package com.imtt.ioc.demo1;

public class Bean1 {
public Bean1() {
System.out.println("Bean1 实例化");
}
}

在配置文件 applicationContext.xml 中添加 Bean 的配置片段:

1
<bean id="bean1" class="com.imtt.ioc.demo1.Bean1"/>

使用 Junit 进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringDemo1 {
@Test
public void demo1() {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");

Bean1 bean1 = (Bean1) applicationContext.getBean("bean1");
}
}

getBean() 方法获取 Bean 的实例,其中的参数参数就是 applicationContext.xml 中注入的 Bean 的 id。

单元测试打印输出:

1
Bean1 实例化

静态工厂方法实例化

首先定义一个类 Bean2 以及 Bean2 的静态工厂,这个静态工厂中有一个用于返回 Bean2 实例的静态方法:

1
2
3
4
package com.imtt.ioc.demo1;

public class Bean2 {
}
1
2
3
4
5
6
7
8
package com.imtt.ioc.demo1;

public class Bean2Factory {
public static Bean2 createBean2() {
System.out.println("Bean2 工厂执行");
return new Bean2();
}
}

在配置文件 applicationContext.xml 中添加 Bean 的配置片段,其中 class 的值是静态工厂类,factory-method 指定返回实例的静态方法:

1
<bean id="bean2" class="com.imtt.ioc.demo1.Bean2Factory" factory-method="createBean2"/>

使用 Junit 进行测试:

1
2
//other codes here。
Bean2 bean2 = (Bean2) applicationContext.getBean("bean2");

单元测试打印输出:

1
Bean2 工厂执行

实例工厂方法实例化

首先定义一个类 Bean3 以及 Bean3 的静态工厂,这个静态工厂中有一个用于返回 Bean3 实例的实例方法:

1
2
3
4
package com.imtt.ioc.demo1;

public class Bean3 {
}
1
2
3
4
5
6
7
8
package com.imtt.ioc.demo1;

public class Bean3Factory {
public Bean3 createBean3() {
System.out.println("Bean3 实例工厂执行");
return new Bean3();
}
}

在配置文件 applicationContext.xml 中添加 Bean 的配置片段,先添加一个工厂的注入,再指定一个注入,其中 factory-bean 的值为工厂注入的 id,factory-method 的值为工厂的实例方法:

1
2
<bean id="bean3Factory" class="com.imtt.ioc.demo1.Bean3Factory"/>
<bean id="bean3" factory-bean="bean3Factory" factory-method="createBean3"/>

使用 Junit 进行测试:

1
2
//other codes here。
Bean3 bean3 = (Bean3) applicationContext.getBean("bean3");

单元测试打印输出:

1
Bean3 实例工厂执行

Bean 的配置

前面的 Bean 配置,如:

1
<bean id="bean1" class="com.imtt.ioc.demo1.Bean1"/>

有两个参数 id(或者 name,有细微差别)和 class,含义如下:

  • id 和 name
    • 一般情况下,装配一个 Bean 时,通过指定一个 id 属性作为 Bean 的名称;
    • id 属性在 IOC 容器中必须是唯一的;
    • 如果 Bean 的名称中含有特殊字符,就需要使用 name 属性。
  • class
    • class 用于设置一个类的完全路径名称,主要作用是 IOC 容器生成类的实例。

属性注入

对于类成员变量的注入,有以下几种方式。

构造函数注入

通过构造方法注入 Bean 的属性值或依赖的对象,保证了 Bean 实例在实例化后就可以使用。

  • 构造器注入在 <constructor-arg> 元素里声明的属性

首先定义一个类 User,包含构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.imtt.ioc.demo2;

public class User {
private String username;
private Integer age;

public User(String username, Integer age) {
this.username = username;
this.age = age;
}

@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
}

在配置文件 applicationContext.xml 中添加包含构造器的 Bean 配置片段:

1
2
3
4
<bean id="user" class="com.imtt.ioc.demo2.User">
<constructor-arg name="username" value="Twu"/>
<constructor-arg name="age" value="20"/>
</bean>

使用 Junit 进行测试:

1
2
//other codes here.
User user = (User) applicationContext.getBean("user");

单元测试打印输出:

1
User{username='Twu', age=20}

属性 set 方法注入

使用set方法注入,在Spring配置文件中,通过 <property> 设置注入的属性。

首先定义一个类 Person,包含 getter/setter 方法,以及一个依赖类 Cat:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.imtt.ioc.demo2;

public class Person {
private String username;
private Integer age;
private Cat cat;

public Cat getCat() {
return cat;
}

public void setCat(Cat cat) {
this.cat = cat;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Override
public String toString() {
return "Person{" +
"username='" + username + '\'' +
", age=" + age +
", cat=" + cat +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.imtt.ioc.demo2;

public class Cat {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}

在配置文件 applicationContext.xml 中添加 Bean 的配置片段,依赖类使用 ref 参数:

1
2
3
4
5
6
7
8
<bean id="person" class="com.imtt.ioc.demo2.Person">
<property name="username" value="Twu"/>
<property name="age" value="19"/>
<property name="cat" ref="cat"/>
</bean>
<bean id="cat" class="com.imtt.ioc.demo2.Cat">
<property name="name" value="ketty"/>
</bean>

使用 Junit 进行测试:

1
2
3
//other codes here.
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);

单元测试打印输出:

1
Person{username='Twu', age=19, cat=Cat{name='ketty'}}

p名称空间的属性注入

使用 p 命名空间,为了简化 XML 文件配置,Spring从2.5开始引入一个新的 p 名称空间,语法:

  • p: <属性名>="xxx" :引入常量值;
  • p: <属性名>-ref="xxx" :引用其它Bean对象;

以上面的 User 和 Cat 类为例,

在配置文件 applicationContext.xml 中引入p名称空间:

1
2
3
4
5
6
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:p="http://www.springframework.org/schema/beans"<!--引入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">
<!-- other codes here. -->
</beans>

接着添加 Bean 的配置片段:

1
2
3
<bean id="person" class="com.imtt.ioc.demo2.Person"
p:username="Twu" p:age="20" p:cat-ref="cat"/>
<bean id="cat" class="com.imtt.ioc.demo2.Cat" p:name="ketty"/>

SpEL 注入

SpEL:spring expression language ,spring表达式语言,对依赖注入进行简化 ,语法:

  • # {表达式}
  • <bean id="" value="#{表达式}">

SpEL表达式语言,语法:

  • #{}
  • #{‘hello’}:使用字符串;
  • #{topicId}:使用另一个 bean ;
  • #{topicId.方法()}:使用指定名属性,并使用方法 ;
  • #{T(java.lang.Math).PI}:使用静态字段或方法。

以上面的 User 和 Cat 类为例,在配置文件 applicationContext.xml 中添加 Bean 的配置片段:

1
2
3
4
5
6
7
8
<bean id="person" class="com.imtt.ioc.demo2.Person">
<property name="username" value="#{'Twu'}"/>
<property name="age" value="#{19}"/>
<property name="cat" value="#{cat}"/>
</bean>
<bean id="cat" class="com.imtt.ioc.demo2.Cat">
<property name="name" value="#{'ketty'}"/>
</bean>

复杂类型的属性注入

复杂类型的属性注入包括:

  • 数组类型的属性注入
  • List集合类型的属性注入
  • Set集合类型的属性注入
  • Map集合类型的属性注入
  • Properties类型的属性注入

定义一个包含多个复杂类型成员的类:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.imtt.ioc.demo3;

import java.util.*;

public class CollectBean {
private String[] attrs;
private List<String> list;
private Set<String> set;
private Map<String, Integer> map;
private Properties properties;

public String[] getAttrs() {
return attrs;
}

public void setAttrs(String[] attrs) {
this.attrs = attrs;
}

public List<String> getList() {
return list;
}

public void setList(List<String> list) {
this.list = list;
}

public Set<String> getSet() {
return set;
}

public void setSet(Set<String> set) {
this.set = set;
}

public Map<String, Integer> getMap() {
return map;
}

public void setMap(Map<String, Integer> map) {
this.map = map;
}

public Properties getProperties() {
return properties;
}

public void setProperties(Properties properties) {
this.properties = properties;
}

@Override
public String toString() {
return "CollectBean{" +
"attrs=" + Arrays.toString(attrs) +
", list=" + list +
", set=" + set +
", map=" + map +
", properties=" + properties +
'}';
}
}

在配置文件 applicationContext.xml 中添加 Bean 的配置片段,数组、List 和 Set 都适用 list 标签,Map 使用 map 标签,Properties 使用 props 标签:

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
26
27
28
29
30
31
32
33
34
35
36
<bean id="collectionBean" class="com.imtt.ioc.demo3.CollectBean">
<property name="attrs">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
<property name="list">
<list>
<value>111</value>
<value>222</value>
<value>333</value>
</list>
</property>
<property name="set">
<set>
<value>ddd</value>
<value>eee</value>
<value>fff</value>
</set>
</property>
<property name="map">
<map>
<entry key="aaa" value="111"/>
<entry key="bbb" value="222"/>
<entry key="ccc" value="333"/>
</map>
</property>
<property name="properties">
<props>
<prop key="username">root</prop>
<prop key="password">1234</prop>
</props>
</property>
</bean>

使用 Junit 进行测试:

1
2
3
//other codes here。
CollectBean collectBean = (CollectBean) applicationContext.getBean("collectionBean");
System.out.println(collectBean);

单元测试打印输出:

1
CollectBean{attrs=[aaa, bbb, ccc], list=[111, 222, 333], set=[ddd, eee, fff], map={aaa=111, bbb=222, ccc=333}, properties={password=1234, username=root}}

Bean 管理的注解方式

添加依赖

src/main/resources 目录下添加一个新的 XML 配置文件 applicationContextAnn.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"
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"> <!-- bean definitions here -->
</beans>

使用注解定义 Bean

Spring2.5 引入使用注解去定义 Bean:

  • @Component 描述 Spring 框架中 Bean;

除了@Component 外,Spring 提供了3个功能基本等效的注解:

  • @Repository 用于对 DAO 实现类进行标注;
  • @Service 用于对 Service 实现类进行标注;
  • @Controller 用于对 Controller实现类进行标注。

定义一个 UserService 类:

1
2
3
4
5
6
7
8
9
package com.imtt.demo4;
import org.springframework.stereotype.Component;

@Component("UserService")
public class Component {
public String sayHello(String name) {
return "Hello " + name;
}
}

@Component(“UserService”) 即为注解,括号中的参数就相当于 XML 方式中 Bean 的 id。

但是要使注解生效,需要进行扫描,在 applicationContextAnn.xml 中开启注解扫描:

1
<context:component-scan base-package="com.imtt.demo4"/>

使用 Junit 进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.imtt.demo4;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringDemo1 {
@Test
public void demo1() {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContextAnn.xml");

UserService userService = (UserService) applicationContext.getBean("userService");
String s = userService.sayHello("Twu");
System.out.println(s);
}
}

单元测试打印输出:

1
Hello Twu

属性注入

普通成员属性

修改 UserService 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.imtt.demo4;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Value;

@Component("UserService")
public class Component {
@Value("米饭")
private String something;

public void eat() {
System.out.println("eat:" + something);
}
}

@Value("米饭") 自动为成员变量注入值。

使用 Junit 进行测试:

1
2
3
4
//other codes here.
UserService userService = (UserService) applicationContext.getBean("userService");
userService.eat();
...

单元测试打印输出:

1
eat:米饭

注入 Bean 对象

使用@Autowired 进行自动注入:

  • @Autowired 默认按照类型进行注入
    • 如果存在两个相同 Bean 类型相同,则按照名称注入;
    • @Autowired 注入时可以针对成员变量或者 set 方法;
    • 按照类型注入表示与自定义的参数名无关,不相同也能完成注入;
    • 通过 @Autowired 的 required 属性,设置一定要找到匹配的 Bean。
  • 使用@Autowired@Qualifier 一起则按名称注入
    • 使用 @Qualifier 指定 Bean 名称后,注解 Bean 必须指定相同名称。

Spring 提供对 JSR-250 中定义 @Resource 标准注解的支持:

  • @Resource@Autowired 注解功能相似,相当于@Autowired@Qualifier 一起。

新建一个 UserDao 类:

1
2
3
4
5
6
7
8
9
10
package com.imtt.demo4;

import org.springframework.stereotype.Repository;

@Repository("userDao")
public class UserDao {
public void save() {
System.out.println("Dao 保存");
}
}

修改 UserService 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.imtt.demo4;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import javax.annotation.Resource;

@Component("UserService")
public class Component {
//@Autowired
//@Qualifier("userDao")
@Resource(name = "userDao")
private UserDao userDao;

public void save() {
System.out.println("Service 保存");
userDao.save();
}
}

使用 Junit 进行测试:

1
2
3
//other codes here.
UserService userService = (UserService) applicationContext.getBean("userService");
userService.save();

单元测试打印输出:

1
2
Service 保存
Dao 保存

Bean 作用域

XML 方式

<bean .../> 片段中有一个 scope 参数,表示 Bean 的作用域,有四个可选值:

类别 说明
singleton(默认值) 在 Spring IOC 容器中仅存在一个 Bean 实例,Bean 以单实例的方式存在。
prototype 每次调用 getBean() 时都会返回一个新的实例。
request 每次 HTTP 请求都会创建一个新的 Bean,该作用域仅适用于 WebApplicationContext 环境。
session 同一个 HTTP Session 共享一个 Bean,不同的 HTTP Session 使用不同的 Bean,该作用域仅适用于 WebApplicationContext 环境。

注解方式

和 XML 方式一样,默认作用范围都是 singleton,可以添加注解进行修改:

  • @Scope 指定Bean的作用范围。

如:

1
2
3
4
@Repository("userDao")
@Scope("prototype")
public class UserDao {
}
坚持原创技术分享,您的支持将鼓励我继续创作!