0x00 参考

本笔记直接参考或引自如下链接文章:

https://www.w3cschool.cn/wkspring/

http://c.biancheng.net/spring/

0x01 Bean定义

被称作Bean的对象是构成应用程序的支柱,也是由Spring IoC容器管理的。Bean是一个被实例化、组装、并通过Spring IoC容器所管理的对象。这些Bean是由用容器提供的配置元数据创建的,例如前面看到的在XML的表单中的定义。

Spring配置文件支持两种不同的格式,分别是XML文件格式和Properties文件格式。

通常情况下,Spring会以XML文件格式作为Spring的配置文件,这种配置方式通过XML文件注册并管理Bean之间的依赖关系。XML格式配置文件的根元素是<beans>,该元素包含了多个<bean>子元素,每一个<bean>子元素定义了一个Bean,并描述了该Bean如何被装配到Spring容器中。

定义Bean的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

<!-- 使用id属性定义person1,其对应的实现类为com.mengma.person1 -->
<bean id="person1" class="com.mengma.damain.Person1" />

<!--使用name属性定义person2,其对应的实现类为com.mengma.domain.Person2-->
<bean name="Person2" class="com.mengma.domain.Person2"/>

</beans>

<bean>元素的常用属性:

属性名称 描述
id 是一个 Bean 的唯一标识符,Spring 容器对 Bean 的配置和管理都通过该属性完成
name Spring 容器同样可以通过此属性对容器中的 Bean 进行配置和管理,name 属性中可以为 Bean 指定多个名称,每个名称之间用逗号或分号隔开
class 该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,使用类的全限定名
scope 用于设定 Bean 实例的作用域,其属性值有 singleton(单例)、prototype(原型)、request、session 和 global Session。其默认值是 singleton
constructor-arg <bean>元素的子元素,可以使用此元素传入构造参数进行实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型
property <bean>元素的子元素,用于调用 Bean 实例中的 Set 方法完成属性赋值,从而完成依赖注入。该元素的 name 属性指定 Bean 实例中的相应属性名
ref <property><constructor-arg>等元素的子元索,该元素中的 bean 属性用于指定对 Bean 工厂中某个 Bean 实例的引用
value <property><constractor-arg>等元素的子元素,用于直接指定一个常量值
list 用于封装 List 或数组类型的依赖注入
set 用于封装 Set 类型属性的依赖注入
map 用于封装 Map 类型属性的依赖注入
entry <map>元素的子元素,用于设置一个键值对。其 key 属性指定字符串类型的键值,ref 或 value 子元素指定其值

下图表达了Bean与Spring容器之间的关系:

0x02 Bean实例化

在面向对象的程序中,要想调用某个类的成员方法,就需要先实例化该类的对象。在Spring中,实例化Bean有三种方式,分别是构造器实例化、静态工厂方式实例化和实例工厂方式实例化。

构造器实例化

构造器实例化是指Spring容器通过Bean对应的类中默认的构造函数实例化Bean。

Demo

Person.java,Bean类,拥有一个构造函数:

1
2
3
4
5
6
7
package com.mi1k7ea;

public class Person {
public Person() {
System.out.println("Person构造函数");
}
}

MainApp.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.mi1k7ea;

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

public class MainApp {
public static void main(String[] args) {
// 定义Spring配置文件的路径
String xmlPath = "Beans.xml";
// 初始化Spring容器,加载配置文件,并对bean进行实例化
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
// 通过容器获取id为person1的实例
System.out.println(applicationContext.getBean("person"));
}
}

Beans.xml,定义了一个id为person的Bean,其class属性指定了对应的类为Person:

1
2
3
4
5
6
7
8
9
<?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-3.0.xsd">

<bean id="person" class="com.mi1k7ea.Person" />

</beans>

运行输出,看到是通过调用Bean类的构造函数来实例化Bean:

静态工厂方式实例化

在Spring中,可以使用静态工厂的方式实例化Bean。此种方式需要提供一个静态工厂方法创建Bean的实例。

Demo

MainApp.java和Person.java不变。

MyBeanFactory.java,创建一个名为createBean()的静态方法,用于创建Bean的实例:

1
2
3
4
5
6
7
8
9
package com.mi1k7ea;

public class MyBeanFactory {
// 创建Bean实例的静态工厂方法
public static Person createBean() {
System.out.println("MyBeanFactory.createBean()");
return new Person();
}
}

Beans.xml,定义了一个id为person的Bean,其class属性指定了对应的工厂实现类为MyBeanFactory,而factory-method属性用于告诉Spring容器调用工厂类中的createBean()方法获取Bean的实例:

1
2
3
4
5
6
7
8
9
<?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-3.0.xsd">

<bean id="person" class="com.mi1k7ea.MyBeanFactory" factory-method="createBean" />

</beans>

运行输出,看到是通过调用静态工厂的方式即调用MyBeanFactory的静态方法createBean()来实例化Bean:

实例工厂方式实例化

在这种方式中,工厂类不再使用静态方法创建Bean的实例,而是直接在成员方法中创建Bean的实例。同时,在配置文件中,需要实例化的Bean也不是通过class属性直接指向其实例化的类,而是通过factory-bean属性配置一个实例工厂,然后使用factory-method属性确定使用工厂中的哪个方法。

Demo

Person.java和MainApp.java不变。

MyBeanFactory.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.mi1k7ea;

public class MyBeanFactory {
public MyBeanFactory() {
System.out.println("MyBeanFactory构造函数");
}

// 创建Bean实例的方法
public Person createBean() {
System.out.println("MyBeanFactory.createBean()");
return new Person();
}
}

Beans.xml,首先配置了一个实例工厂Bean,然后配置了需要实例化的Bean,在id为person的Bean中,使用factory-bean属性指定一个实例工厂,该属性值就是实例工厂的id属性值,使用factory-method属性确定使用工厂中的createBean()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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-3.0.xsd">

<!-- 配置实例工厂 -->
<bean id="myBeanFactory" class="com.mi1k7ea.MyBeanFactory" />

<!-- factory-bean属性指定一个实例工厂,factory-method属性确定使用工厂中的哪个方法 -->
<bean id="person" factory-bean="myBeanFactory" factory-method="createBean" />

</beans>

运行输出,看到先实例化工厂类并调用createBean()方法成功实例化Bean:

0x03 Bean作用域

当在Spring中定义一个Bean时,你必须声明该Bean的作用域的选项。例如,为了强制Spring在每次需要时都产生一个新的Bean实例,你应该声明Bean的作用域的属性为prototype。同理,如果你想让Spring在每次需要时都返回同一个Bean实例,你应该声明Bean的作用域的属性为singleton

Spring 框架支持以下五个作用域:

作用域 描述
singleton 在Spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值
prototype 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()
request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境
global-session 一般用于Portlet应用环境,该运用域仅适用于WebApplicationContext环境

singleton

singleton是默认的作用域。当一个Bean的作用域为singleton时,Spring容器中只会存在一个共享的Bean实例,并且所有对Bean的请求,只要id与该Bean定义相匹配,就只会返回Bean的同一个实例。

singleton是单例类型,就是在创建起容器时就同时自动创建了一个Bean的对象,不管你是否使用,它都存在了,每次获取到的对象都是同一个对象。

通常情况下,这种单例模式对于无会话状态的Bean(如DAO层、Service层)来说,是最理想的选择。

XML中显式设置示例:

1
2
3
4
<!-- A bean definition with singleton scope -->
<bean id="..." class="..." scope="singleton">
<!-- collaborators and configuration for this bean go here -->
</bean>

Demo

沿用前面的例子,HelloWorld.java不变。

MainApp.java,调用两次getBean()并调用Bean的getMessage()方法查看是否是同一个对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.mi1k7ea;

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

public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
objA.setMessage("Object A");
objA.getMessage();
HelloWorld objB = (HelloWorld) context.getBean("helloWorld");
objB.getMessage();
}
}

Beans.xml,显式设置scope,并去掉其中的设置Bean类属性值的property标签:

1
2
3
4
5
6
7
8
9
<?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-3.0.xsd">

<bean id="helloWorld" class="com.mi1k7ea.HelloWorld" scope="singleton" />

</beans>

输出都是一样的,说明singleton作用域即默认的作用域就是单例模式,在创建容器时就自动创建了该Bean对象,而每次getBean()获取到的都是同一个Bean对象:

prototype

当一个Bean的作用域为prototype,表示一个Bean定义对应多个对象实例。prototype作用域的Bean会导致在每次对该Bean请求(将其注入到另一个Bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的Bean实例。

prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取Bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。

根据经验,对有状态的Bean应该使用prototype作用域,而对无状态的Bean则应该使用singleton作用域。

XML中设置示例:

1
2
3
4
<!-- A bean definition with singleton scope -->
<bean id="..." class="..." scope="prototype">
<!-- collaborators and configuration for this bean go here -->
</bean>

Demo

HelloWorld.java和MainApp.java不变。

Beans.xml,设置scope,并在property标签中设置创建Bean类时message属性的默认值为”Default Object”:

1
2
3
4
5
6
7
8
9
10
11
<?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-3.0.xsd">

<bean id="helloWorld" class="com.mi1k7ea.HelloWorld" scope="prototype">
<property name="message" value="Default Object"/>
</bean>

</beans>

输出不一样,说明两次getBean()获取的Bean类对象并不是同一个,同时说明prototype作用域是在获取Bean时会新建一个Bean对象:

0x04 Bean生命周期

Bean的生命周期可以表达为:Bean的定义->Bean的初始化->Bean的使用->Bean的销毁

Spring容器可以管理singleton作用域Bean的生命周期,在此作用域下,Spring能够精确地知道该Bean何时被创建,何时初始化完成,以及何时被销毁。而对于prototype作用域的Bean,Spring只负责创建,当容器创建了Bean的实例后,Bean的实例就交给客户端代码管理,Spring容器将不再跟踪其生命周期。每次客户端请求prototype作用域的Bean时,Spring容器都会创建一个新的实例,并且不会管那些被配置成prototype作用域的Bean的生命周期。

当一个Bean被加载到Spring容器时,它就具有了生命,而Spring容器在保证一个Bean能够使用之前,会进行很多工作。Spring容器中Bean的生命周期流程如图:

Bean生命周期的整个执行过程描述如下:

  1. 根据配置情况调用Bean构造方法或工厂方法实例化Bean。
  2. 利用依赖注入完成Bean中所有属性值的配置注入。
  3. 如果Bean实现了BeanNameAware接口,则Spring调用Bean的setBeanName()方法传入当前Bean的id值。
  4. 如果Bean实现了BeanFactoryAware接口,则Spring调用setBeanFactory()方法传入当前工厂实例的引用。
  5. 如果Bean实现了ApplicationContextAware接口,则Spring调用setApplicationContext()方法传入当前ApplicationContext实例的引用。
  6. 如果BeanPostProcessor和Bean关联,则Spring将调用该接口的预初始化方法postProcessBeforeInitialzation()对Bean进行加工操作,此处非常重要,Spring的AOP就是利用它实现的。
  7. 如果Bean实现了InitializingBean接口,则Spring将调用afterPropertiesSet()方法。
  8. 如果在配置文件中通过init-method属性指定了初始化方法,则调用该初始化方法。
  9. 如果BeanPostProcessor和Bean关联,则Spring将调用该接口的初始化方法postProcessAfterInitialization()。此时,Bean已经可以被应用系统使用了。
  10. 如果在<bean>中指定了该 Bean 的作用范围为scope=”singleton”,则将该Bean放入Spring IoC的缓存池中,将触发Spring对该Bean的生命周期管理;如果在<bean>中指定了该Bean的作用范围为scope=”prototype”,则将该Bean交给调用者,调用者管理该Bean的生命周期,Spring不再管理该Bean。
  11. 如果Bean实现了DisposableBean接口,则Spring会调用destory()方法将Spring中的Bean销毁;如果在配置文件中通过destory-method属性指定了Bean的销毁方法,则Spring将调用该方法对Bean进行销毁。

这里我们只讨论两个重要的生命周期回调方法,它们在Bean的初始化和销毁的时候是必需的。

为了定义安装和拆卸一个Bean,我们只要声明带有init-methoddestroy-method参数的 。init-method属性指定一个方法,在实例化Bean 时,立即调用该方法。同样,destroy-method指定一个方法,只有从容器中移除Bean之后,才能调用该方法。

初始化回调

org.springframework.beans.factory.InitializingBean接口指定一个单一的方法:

1
void afterPropertiesSet() throws Exception;

因此,你可以简单地实现上述接口和初始化工作可以在afterPropertiesSet()方法中执行,如下所示:

1
2
3
4
5
public class ExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}

在基于XML的配置元数据的情况下,你可以使用 init-method 属性来指定带有void无参数方法的名称。例如:

1
<bean id="exampleBean" class="examples.ExampleBean" init-method="init"/>

下面是类的定义:

1
2
3
4
5
public class ExampleBean {
public void init() {
// do some initialization work
}
}

销毁回调

org.springframework.beans.factory.DisposableBean接口指定一个单一的方法:

1
void destroy() throws Exception;

因此,你可以简单地实现上述接口并且结束工作可以在destroy()方法中执行,如下所示:

1
2
3
4
5
public class ExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work
}
}

在基于XML的配置元数据的情况下,你可以使用 destroy-method 属性来指定带有void无参数方法的名称。例如:

1
<bean id="exampleBean" class="examples.ExampleBean" destroy-method="destroy"/>

下面是类的定义:

1
2
3
4
5
public class ExampleBean {
public void destroy() {
// do some destruction work
}
}

如果你在非Web应用程序环境中使用Spring IoC容器,例如在丰富的客户端桌面环境中,那么在JVM中你要注册关闭hook。这样做可以确保正常关闭,为了让所有的资源都被释放,可以在单个Beans上调用destroy方法。

默认的初始化和销毁方法

如果你有太多具有相同名称的初始化或者销毁方法的Bean,那么你不需要在每一个Bean上声明初始化方法销毁方法。框架使用元素中的 default-init-methoddefault-destroy-method 属性提供了灵活地配置这种情况,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
<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-3.0.xsd"
default-init-method="init"
default-destroy-method="destroy">

<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>

</beans>

Demo

修改HelloWorld.java,添加init()和destroy()方法:

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

public class HelloWorld {
private String message;

public void setMessage(String message){
this.message = message;
}

public void getMessage(){
System.out.println("Your Message : " + message);
}

public void init(){
System.out.println("Bean is going through init.");
}

public void destroy(){
System.out.println("Bean will destroy now.");
}
}

修改MainApp.java,需要注册一个在AbstractApplicationContext类中声明的关闭hook的registerShutdownHook()方法。它将确保正常关闭,并且调用相关的destroy()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.mi1k7ea;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
context.registerShutdownHook();
}
}

Beans.xml,通过init-method和destroy-method来设置初始化和销毁的回调方法:

1
2
3
4
5
6
7
8
9
10
11
<?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-3.0.xsd">

<bean id="helloWorld" class="com.mi1k7ea.HelloWorld" init-method="init" destroy-method="destroy">
<property name="message" value="mi1k7ea"/>
</bean>

</beans>

运行输出,看到在在初始化之前调用了初始化回调方法以及在销毁Bean对象之前调用了销毁回调方法:

Spring中的事件处理

ApplicationContext负责管理Beans的完整生命周期。当加载Beans时,ApplicationContext发布某些类型的事件。例如,当上下文启动时,ContextStartedEvent发布,当上下文停止时,ContextStoppedEvent发布。

通过ApplicationEvent类和ApplicationListener接口来提供在ApplicationContext中处理事件。如果一个Bean实现ApplicationListener,那么每次ApplicationEvent被发布到ApplicationContext上,那个Bean会被通知。

Spring提供了以下的标准事件:

Spring 内置事件 描述
ContextRefreshedEvent ApplicationContext被初始化或刷新时,该事件被发布。这也可以在ConfigurableApplicationContext接口中使用refresh()方法来发生。
ContextStartedEvent 当使用ConfigurableApplicationContext接口中的start()方法启动ApplicationContext时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。
ContextStoppedEvent 当使用ConfigurableApplicationContext接口中的stop()方法停止ApplicationContext时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作。
ContextClosedEvent 当使用ConfigurableApplicationContext接口中的close()方法关闭ApplicationContext时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。
RequestHandledEvent 这是一个web-specific事件,告诉所有Bean HTTP请求已经被服务。

监听上下文事件

为了监听上下文事件,一个Bean应该实现只有一个方法onApplicationEvent()的ApplicationListener接口。

下面看下例子。

HelloWorld.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.mi1k7ea;

public class HelloWorld {
private String message;

public void setMessage(String message){
this.message = message;
}

public void getMessage(){
System.out.println("Your Message : " + message);
}
}

CStartEventHandler.java,实现ApplicationListener类的ContextStartedEvent事件:

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

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStartedEvent;

public class CStartEventHandler implements ApplicationListener<ContextStartedEvent> {
public void onApplicationEvent(ContextStartedEvent event) {
System.out.println("ContextStartedEvent Received");
}
}

CStopEventHandler.java,实现ApplicationListener类的ContextStoppedEvent事件:

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

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStoppedEvent;

public class CStopEventHandler implements ApplicationListener<ContextStoppedEvent> {
public void onApplicationEvent(ContextStoppedEvent event) {
System.out.println("ContextStoppedEvent Received");
}
}

MainApp.java:

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

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
public static void main(String[] args) {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");

// Let us raise a start event.
context.start();

HelloWorld obj = (HelloWorld) context.getBean("helloWorld");

obj.getMessage();

// Let us raise a stop event.
context.stop();
}
}

Beans.xml,添加两个实现监听上下文事件的Bean类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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-3.0.xsd">

<bean id="helloWorld" class="com.mi1k7ea.HelloWorld">
<property name="message" value="mi1k7ea"/>
</bean>

<bean id="cStartEventHandler" class="com.mi1k7ea.CStartEventHandler"/>

<bean id="cStopEventHandler" class="com.mi1k7ea.CStopEventHandler"/>

</beans>

运行输出:

Spring中的自定义事件

Spring支持编写和发布用户的自定义事件,具体步骤如下示例。

CustomEvent.java,通过扩展ApplicationEvent类来创建的事件类,这个类必须定义一个默认的构造函数,它应该是从ApplicationEvent类中继承的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
package com.mi1k7ea;

import org.springframework.context.ApplicationEvent;

public class CustomEvent extends ApplicationEvent {
public CustomEvent(Object source) {
super(source);
}
public String toString(){
return "My Custom Event";
}
}

一旦定义事件类,你可以从任何类中发布它。

CustomEventPublisher.java,实现了ApplicationEventPublisherAware接口类,用于发布事件类,此外还需要在XML配置文件中声明这个类作为一个Bean,之所以容器可以识别Bean作为事件发布者,是因为它实现了ApplicationEventPublisherAware接口:

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

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;

public class CustomEventPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
public void setApplicationEventPublisher
(ApplicationEventPublisher publisher){
this.publisher = publisher;
}
public void publish() {
CustomEvent ce = new CustomEvent(this);
publisher.publishEvent(ce);
}
}

CustomEventHandler.java,实现了ApplicationListener接口类,发布的事件可以在该类中被处理,而且实现了自定义事件的onApplicationEvent()方法:

1
2
3
4
5
6
7
8
9
package com.mi1k7ea;

import org.springframework.context.ApplicationListener;

public class CustomEventHandler implements ApplicationListener<CustomEvent> {
public void onApplicationEvent(CustomEvent event) {
System.out.println(event.toString());
}
}

MainApp.java,获取自定义事件发布者的Bean,并调用发布方法:

1
2
3
4
5
6
7
8
9
10
11
12
package com.mi1k7ea;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
public static void main(String[] args) {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
CustomEventPublisher cvp = (CustomEventPublisher) context.getBean("customEventPublisher");
cvp.publish();
}
}

Beans.xml,声明事件发布者的Bean和事务处理者的Bean:

1
2
3
4
5
6
7
8
9
10
11
<?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-3.0.xsd">

<bean id="customEventHandler" class="com.mi1k7ea.CustomEventHandler"/>

<bean id="customEventPublisher" class="com.mi1k7ea.CustomEventPublisher"/>

</beans>

运行输出:

0x05 Bean后置处理器

Bean后置处理器允许在调用初始化方法前后对Bean进行额外的处理。

BeanPostProcessor接口定义回调方法,你可以实现该方法来提供自己的实例化逻辑,依赖解析逻辑等。你也可以在Spring容器通过插入一个或多个BeanPostProcessor的实现来完成实例化,配置和初始化一个Bean之后实现一些自定义逻辑回调方法。

你可以配置多个BeanPostProcessor接口,通过设置BeanPostProcessor实现的Ordered接口提供的order属性来控制这些BeanPostProcessor接口的执行顺序。

BeanPostProcessor可以对Bean(或对象)实例进行操作,这意味着Spring IoC容器实例化一个Bean实例,然后BeanPostProcessor接口进行它们的工作。

ApplicationContext会自动检测由BeanPostProcessor接口的实现定义的Bean,注册这些Bean为后置处理器,然后通过在容器中创建Bean,在适当的时候调用它。

Demo

HelloWorld.java,添加初始化和销毁方法:

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

public class HelloWorld {
private String message;

public void setMessage(String message){
this.message = message;
}

public void getMessage(){
System.out.println("Your Message : " + message);
}

public void init(){
System.out.println("Bean is going through init.");
}

public void destroy(){
System.out.println("Bean will destroy now.");
}
}

InitHelloWorld.java,实现BeanPostProcessor接口类,在任何Bean的初始化前后输出该Bean的名称:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.mi1k7ea;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class InitHelloWorld implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeforeInitialization : " + beanName);
return bean; // you can return any other object as well
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("AfterInitialization : " + beanName);
return bean; // you can return any other object as well
}
}

MainApp.java,需要注册一个在AbstractApplicationContext类中声明的关闭hook的registerShutdownHook()方法。它将确保正常关闭,并且调用相关的destroy方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.mi1k7ea;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
context.registerShutdownHook();
}
}

Beans.xml,在前面初始化回调和销毁回调的基础上,添加设置bean标签指定后置处理器为InitHelloWorld,ApplicationContext会自动检测到该实现类定义的Bean为后置处理器、在恰当的时候调用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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-3.0.xsd">

<bean id="helloWorld" class="com.mi1k7ea.HelloWorld" init-method="init" destroy-method="destroy">
<property name="message" value="mi1k7ea"/>
</bean>

<bean class="com.mi1k7ea.InitHelloWorld" />

</beans>

运行输出如下,看到在调用HelloWorld的初始化函数init()的前后都调用了Bean后置处理器对应的函数进行输出:

0x06 Bean定义继承

Bean定义可以包含很多的配置信息,包括构造函数的参数、属性值、容器的具体信息等等。

子Bean的定义继承父定义的配置数据。子定义可以根据需要重写一些值,或者添加其他值。

Spring Bean定义的继承与Java类的继承无关,但是继承的概念是一样的。你可以定义一个父Bean的定义作为模板,其他子Bean就可以从父Bean中继承所需的配置。

当你使用基于XML的配置元数据时,通过使用父属性,指定父Bean作为该属性的值来表明子Bean的定义。

Demo

HelloWorld.java,拥有2个message属性及其getter和setter方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.mi1k7ea;

public class HelloWorld {
private String message1;
private String message2;

public void setMessage1(String message){
this.message1 = message;
}

public void setMessage2(String message){
this.message2 = message;
}

public void getMessage1(){
System.out.println("Message1 : " + message1);
}

public void getMessage2(){
System.out.println("Message2 : " + message2);
}
}

Son.java,拥有3个message属性及其getter和setter方法:

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
package com.mi1k7ea;

public class Son {
private String message1;
private String message2;
private String message3;

public void setMessage1(String message){
this.message1 = message;
}

public void setMessage2(String message){
this.message2 = message;
}

public void setMessage3(String message){
this.message3 = message;
}

public void getMessage1(){
System.out.println("Son Message1 : " + message1);
}

public void getMessage2(){
System.out.println("Son Message2 : " + message2);
}

public void getMessage3(){
System.out.println("Son Message3 : " + message3);
}
}

MainApp.java,分别声明了HelloWorld对象和Son对象并分别调用getter方法:

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

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

public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");

HelloWorld objA = (HelloWorld) context.getBean("helloWorld");

objA.getMessage1();
objA.getMessage2();

Son objB = (Son) context.getBean("son");
objB.getMessage1();
objB.getMessage2();
objB.getMessage3();
}
}

Beans.xml,注意son是作为helloWorld的子Bean,用parent标记其父Bean,其中重写了message1的属性,继承了message2的属性,添加了message3属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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-3.0.xsd">

<bean id="helloWorld" class="com.mi1k7ea.HelloWorld">
<property name="message1" value="Father's Message1"/>
<property name="message2" value="Father's Message2"/>
</bean>

<bean id="son" class="com.mi1k7ea.Son" parent="helloWorld">
<property name="message1" value="Son's Message1"/>
<property name="message3" value="Son's Message3"/>
</bean>

</beans>

输出:

Bean定义模板

此外,还可以定义一个Bean定义模板,不用指定class的属性,而应该指定带true值的abstract属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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-3.0.xsd">

<bean id="beanTeamplate" abstract="true">
<property name="message1" value="A"/>
<property name="message2" value="B"/>
<property name="message3" value="C"/>
</bean>

<bean id="son" class="com.mi1k7ea.Son" parent="beanTeamplate">
<property name="message1" value="new A"/>
<property name="message3" value="new C"/>
</bean>

</beans>

此时beanTeamplate为父Bean,由于标记为抽象的,因此只作为模板给子Bean使用而不会被实例化。