7.2.2 使用Spring管理Bean

7.2.2 使用Spring管理Bean

Spring核心容器的理论很简单:Spring核心容器就是一个超级大工厂,所有的对象(包括数据源、HibernateSession Factory等基础性资源)都会被当成Spring核心容器管理的对象—— Spring容器中的一切对象统称为Bean
Spring容器中的Bean,与以前听过的Java Bean是不同的。不像Java Bean,必须遵守一些特定的规范,而SpringBean没有任何要求。只要是一个Java类, Spring就可以管理该Java类,并将它当成Bean处理
对于Spring框架而言,一切Java对象都是Bean.

程序示例

项目结构

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
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\springQs
├─lib\
│ ├─spring-aop-5.0.1.RELEASE.jar
│ ├─spring-aspects-5.0.1.RELEASE.jar
│ ├─spring-beans-5.0.1.RELEASE.jar
│ ├─spring-context-5.0.1.RELEASE.jar
│ ├─spring-context-indexer-5.0.1.RELEASE.jar
│ ├─spring-context-support-5.0.1.RELEASE.jar
│ ├─spring-core-5.0.1.RELEASE.jar
│ ├─spring-expression-5.0.1.RELEASE.jar
│ ├─spring-instrument-5.0.1.RELEASE.jar
│ ├─spring-jcl-5.0.1.RELEASE.jar
│ ├─spring-jdbc-5.0.1.RELEASE.jar
│ ├─spring-jms-5.0.1.RELEASE.jar
│ ├─spring-messaging-5.0.1.RELEASE.jar
│ ├─spring-orm-5.0.1.RELEASE.jar
│ ├─spring-oxm-5.0.1.RELEASE.jar
│ ├─spring-test-5.0.1.RELEASE.jar
│ ├─spring-tx-5.0.1.RELEASE.jar
│ ├─spring-web-5.0.1.RELEASE.jar
│ ├─spring-webflux-5.0.1.RELEASE.jar
│ ├─spring-webmvc-5.0.1.RELEASE.jar
│ └─spring-websocket-5.0.1.RELEASE.jar
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
└─service\
├─Axe.java
└─Person.java

Axe.java

下面程序先定义一个简单的类。

1
2
3
4
5
6
7
8
package org.crazyit.app.service;
public class Axe
{
public String chop()
{
return "使用斧头砍柴";
}
}

从上面代码可以看出,该Axe类只是一个最普通的Java类,简单到令人难以置信——但这也是前面所介绍的, SpringBean类没有任何要求,只要它是个Java类即可

什么是依赖

下面再定义一个简单的Person类,该Person类的useAxe()方法需要调用Axe对象的chop()方法,**这种A对象需要调用B对象方法的情形,被称为依赖**。下面是依赖Axe对象的Person类的源代码。

Person.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.crazyit.app.service;

public class Person
{
private Axe axe;
// 设值注入所需的setter方法
public void setAxe(Axe axe)
{
this.axe = axe;
}

public void useAxe()
{
System.out.println("我打算去砍点柴火!");
// 调用axe的chop()方法,
// 表明Person对象依赖于axe对象
System.out.println(axe.chop());
}
}

使用Spring框架之后, Spring核心容器是整个应用中的超级大工厂,所有的Java对象都交给Spring容器管理,这些由Spring容器管理的Java对象被统称为Spring容器中的Bean.

Spring如何管理Bean

现在的问题是:Spring容器怎么知道管理哪些Bean呢?答案是XML配置文件(也可用注解,后面会介绍), **Spring使用XML配置文件来管理容器中的Bean**。因此,接下来为该项目增加XML配置文件, SpringXML配置文件的文件名没有任何要求,读者可以随意指定。

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="GBK"?>
<!-- Spring配置文件的根元素,使用spring-beans.xsd语义约束 -->
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置名为person的Bean,其实现类是org.crazyit.app.service.Person类 -->
<bean id="person" class="org.crazyit.app.service.Person">
<!-- 控制调用setAxe()方法,将容器中axe Bean作为传入参数 -->
<property name="axe" ref="axe" />
</bean>
<!-- 配置名为axe的Bean,其实现类是org.crazyit.app.service.Axe类 -->
<bean id="axe" class="org.crazyit.app.service.Axe" />
<!-- 配置名为win的Bean,其实现类是javax.swing.JFrame类 -->
<bean id="win" class="javax.swing.JFrame" />
<!-- 配置名为date的Bean,其实现类是java.util.Date类 -->
<bean id="date" class="java.util.Date" />
</beans>

上面的配置文件很简单,该配置文件的根元素是<beans>,根元素主要就是包括多个<bean>元素,每个<bean>元素定义一个Bean。上面配置文件中一共定义了4个Bean,其中前两个Bean是本示例提供的AxePerson类;而后两个Bean则直接使用了JDK提供的java.util.Datejavax.swing.JFrame.
再次强调:Spring可以把”一切Java对象”当成容器中的Bean,因此不管该Java类是JDK提的,还是第三方框架提供的,抑或是开发者自己实现的类都可以使用Spring管理。只要是个Java类,并将它配置在XML配置文件中, Spring容器就可以管理它

元素说明

实际上,配置文件中的<bean>元素默认以反射方式来调用该类无参数的构造器,以如下元素为例:

1
2
3
4
<bean id="person" class="org.crazyit.app.service.Person">
<!-- 控制调用setAxe()方法,将容器中axe Bean作为传入参数 -->
<property name="axe" ref="axe" />
</bean>

Spring框架解析该<bean>元素后将可以得到两个字符串:

  • 解析<bean>元素的id属性得到的idStr的值为"person"
  • 解析<bean>元素的class属性得到classStr的值为"org.crazyit.app.service Person"

也就是说, Spring底层会执行形如以下格式的代码

1
2
3
4
5
6
String idStr=...;//解析`<bean>`元素的`id`属性得到的`idStr`的值为`"person"`
Sting classStr=...;//解析`<bean>`元素的`class`属性得到`classStr`的值为`"org.crazyit.app.service Person"`
Class clazz=Class.forName(classStr);
Object object=clazz.newInstance();
//container代表Spring容器
container.put(idStr,object);

上面代码就是最基本的反射代码(实际上Spring底层代码会更完善一些), Spring框架通过反射根据<bean>元素的class属性指定的类名创建了一个Java对象,并以<bean>元素的id属性的值为key,将该对象放入Spring容器中—这个Java对象就成为了Spring容器中的Bean.
每个<bean>元素默认驱动Spring调用该类无参数的构造器来创建实例,并将该实例作为Spring容器中的Bean

通过上面的反射代码还可以得到一个结论:在Spring配置文件中配置Bean时,class属性的值必须是Bean实现类的完整类名(必须带包名),不能是接口,不能是抽象类(除非有特殊配置),否则Spring无法使用反射创建该类的实例.

元素说明

上面配置文件中还包括一个<property>子元素,<property>子元素通常用于作为<bean>元素的子元素,它驱动Spring在底层以反射执行一次setter方法。其中:

  • <property>name属性值决定执行哪setter方法,
  • <property>valueref属性决定执行setter方法的传入参数:
    • 如果传入参数是基本类型及其包装类String等类型,则使用value属性指定传入参数
    • 如果以容器中其他Bean作为传入参数,则使用ref属性指定传入参数。

Spring框架只要看到<property>子元素, Spring框架就会在底层以反射方式执行一次setter方法。何时执行这个setter方法呢?该Bean一旦创建处理, Spring会立即根据<property>子元素来执行setter方法。也就是说,<bean>元素驱动Spring调用构造器创建对象; <property>子元素驱动Spring执行setter方法,这两步是先后执行的,中间几乎没有任何间隔。

以上面配置文件中的如下配置为例:

1
2
3
4
<bean id="person" class="org.crazyit.app.service.Person">
<!-- 控制调用setAxe()方法,将容器中axe Bean作为传入参数 -->
<property name="axe" ref="axe" />
</bean>

上面配置中<property>元素的

  • name属性值为axe,该元素将驱动Spring以反射方式执行person这个BeansetAxe()方法;
  • ref属性值为axe,该属性值指定以容器中名为axeBean作为执行setter方法的传入参数.

也就是说, Spring底层会执行形如以下格式的代码

1
2
3
4
5
6
7
8
9
10
11
12
//解析`<property>`元素的name属性得到该字符串的值为"axe"
String nameStr=...;
//解析`<property>`元素的ref属性得到该字符串的值为"axe"
String refStr=...;
// 生成要调用的setter方法名
String setterName="set"+nameStr.substring(0,1).toUpperCase()+nameStr.substring(1);
// 获取Spring容器中名为refStr的Bean,该Bean将会作为传入参数.
Object paramBean=container.get(refStr);
// 此处的clazz是前一段反射代码通过<bean>元素的class属性得到的Class对象
Method setter=clazz.getMethod(setterName,paramBean.getClass());
// 此处的object是前一段反射代码为<bean>元素创建的对象
setter.invoke(object,paramBean);

上面代码就是最基本的反射代码(实际上Spring底层代码会更完善一些),
Spring框架通过反射根据<property>元素的name属性决定调用哪个setter方法,并根据valueref决定调用setter方法的传入参数。
如果不想真正理解Spring框架的底层机制,则只要记住:每个property>元素默认驱动Spring调用一次setter方法.
理解了Spring配置文件中<bean>元素的作用:默认驱动Spring在底层调用无参数的构造器创建对象.
就能猜到上面配置中4个<bean>元素产生的效果:Spring会依次创建org.crazyit.app.service.Personorg.crazyit.app.service.Axejavax.Swing.JFramejava.uti.Date这4个类的对象,并把它们当成容器中的Bean

其中idperson<bean>元素还包括一个<property>子元素,因此Spring会在创建完personBean之后,立即以容器中idaxeBean作为参数来调用personBeansettAxe方法—这样会导致容器中idaxeBean被赋值给person对象的axe实例变量。

使用ApplicationContext接口创建Spring容器

接下来程序就可通过Spring容器来访问容器中的Bean, ApplicationContextSpring容器最常用的接口,该接口有如下两个实现类。

  1. ClassPathXmlApplicationContext:从类加载路径下搜索配置文件,并根据配置文件来创建Spring容器
  2. FileSystemXmlApplicationContext:从文件系统的相对路径或绝对路径下去搜索配置文件,并根据配置文件来创建Spring容器

对于Java项目而言,类加载路径总是稳定的,因此通常总是使用ClassPathXmlApplicationContext创建Spring容器。

下面是本示例的主程序代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package lee;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.crazyit.app.service.*;

public class BeanTest
{
public static void main(String[] args) throws Exception
{
// 创建Spring容器
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml");
// 获取id为person的Bean
Person p = ctx.getBean("person", Person.class);
// 调用useAxe()方法
p.useAxe();
}
}

上面程序中main方法里的
第1行代码创建了Spring容器,
第2行代码通过Spring容器获取idpersonBean(Spring容器中的Bean,就是Java对象。)

使用getBean方法从Spring容器获取Bean

Spring容器获取Bean对象主要有如下两个方法。

方法 描述
Object getBean(String id) 根据容器中Beanid来获取指定Bean,获取Bean之后需要进行强制类型转换.
T getBean(String name, Class<T> requiredType) 根据容器中Beanid来获取指定Bean,但该方法带一个泛型参数,因此获取Bean之后无须进行强制类型转换

上面程序使用的是带泛型参数的getBean()方法,所以通过该方法获取Bean之后无须进行强制类型转换获得Bean(即Java对象)之后,即可通过该对象来调用方法访问实例变量(如果访问权限允许)总之,**原来怎么使用Java对象,现在还怎么使用这个获取到的Bean**。
编译、运行该程序,即可看到如下输出:

1
2
我打算去砍点柴火!
使用斧头砍柴

使用Spring不用自己创建对象

从上面的运行结果可以看出,使用Spring框架之后最大的改变之一是:程序不再使用new调用构造器创建Jaa对象,所有的Java对象都由Spring容器负责创建。