前言
深入理解DIP、IoC、DI以及IoC容器:https://www.cnblogs.com/liuhaorain/p/3747470.html
为什么要使用IOC
传统开发模式的弊端
三层架构是经典的开发模式,我们一般将视图控制、业务逻辑和数据库操作分别抽离出来单独形成一个类,这样各个职责就非常清晰且易于复用和维护。大致代码如下:
用户DAO
public class UserDAO {
private String database;
public UserDAO(String dataBase) {
this.database = dataBase;
}
public void doSomething() {
System.out.println("保存用户!");
}
}
用户Service
public class UserService {
private UserDAO dao;
public UserService(UserDAO dao) {
this.dao = dao;
}
public void doSomething() {
dao.doSomething();
}
}
用户Controller
public class Controller {
public UserService service;
public Controller(UserService userService) {
this.service = userService;
}
public void doSomething() {
service.doSomething();
}
}
接下来我们就必须手动一个一个创建对象,并将dao、service、controller依次组装起来,然后才能调用。
public static void main(String[] args) {
UserDAO dao = new UserDAO("mysql");
UserService service = new UserService(dao);
Controller controller = new Controller(service);
controller.doSomething();
}
分析一下这种做法的弊端有哪些呢?
- 在生成Controller的地方我们都必须先创建dao再创建service最后再创建Controller,这么一条繁琐的创建过程。
- 在这三层结构当中,上层都需要知道下层是如何创建的,上层必须自己创建下层,这样就形成了紧密耦合。为什么业务程序员在写业务的时候却需要知道数据库的密码并自己创建dao呢?不仅如此,当如果dao的数据库密码变化了,在每一处生成Controller的地方都需要进行修改。
- 通过new关键字生成了具体的对象,这是一种硬编码的方式,违反了面向接口编程的原则。当有一天我们从Hibernate更换到Mybatis的时候,在每一处new DAO的地方,我们都需要进行更换。
- 我们频繁的创建对象,浪费了资源。
这时候我们再来看看如果用SpringIOC的情况,刚才的代码变成如下。
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
Controller controller = (Controller) context.getBean("controller");
controller.doSomething();
}
很明显,使用IOC之后,我们只管向容器索取所需的Bean即可。IOC便解决了以下的痛点:
- Bean之间的解耦,这种解耦体现在我们没有在代码中去硬编码bean之间的依赖。(不通过new操作依次构建对象,由springIOC内部帮助我们实现了依赖注入)。一方面,IOC容器将通过new对象设置依赖的方式转变为运行期动态的进行设置依赖。
- IOC容器天然地给我们提供了单例。
- 当需要更换dao的时候,我们只需要在配置文件中更换dao的实现类,完全不会破坏到之前的代码。
- 上层现在不需要知道下层是如何创建的。
Spring IoC容器
IoC容器是Spring的核心,也可以称为Spring容器。Spring 通过 IoC 容器来管理对象的实例化和初始化,以及对象从创建到销毁的整个生命周期。
Spring 中使用的对象都由 IoC 容器管理,不需要我们手动使用 new 运算符创建对象。由 IoC 容器管理的对象称为 Spring Bean,Spring Bean 就是 Java 对象,和使用 new 运算符创建的对象没有区别。
Spring 通过读取 XML 或 Java 注解中的信息来获取哪些对象需要实例化。
Spring 提供 2 种不同类型的 IoC 容器,即 BeanFactory 和 ApplicationContext 容器。
BeanFactory 容器
BeanFactory 是最简单的容器,由 org.springframework.beans.factory.BeanFactory 接口定义,采用懒加载(lazy-load),所以容器启动比较快。BeanFactory 提供了容器最基本的功能。
为了能够兼容 Spring 集成的第三方框架(如 BeanFactoryAware、InitializingBean、DisposableBean),所以目前仍然保留了该接口。
简单来说,BeanFactory 就是一个管理 Bean 的工厂,它主要负责初始化各种 Bean,并调用它们的生命周期方法。
BeanFactory 接口有多个实现类,最常见的是 org.springframework.beans.factory.xml.XmlBeanFactory。使用 BeanFactory 需要创建 XmlBeanFactory 类的实例,通过 XmlBeanFactory 类的构造函数来传递 Resource 对象。如下所示。
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory factory = new XmlBeanFactory(resource);
ApplicationContext 容器
ApplicationContext 继承了 BeanFactory 接口,由 org.springframework.context.ApplicationContext 接口定义,对象在启动容器时加载。ApplicationContext 在 BeanFactory 的基础上增加了很多企业级功能,例如 AOP、国际化、事件支持等。
ApplicationContext 接口有两个常用的实现类,具体如下。
ClassPathXmlApplicationContext
该类从类路径 ClassPath 中寻找指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作,具体如下所示。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);
在上述代码中,configLocation 参数用于指定 Spring 配置文件的名称和位置,如 Beans.xml。
FileSystemXmlApplicationContext
该类从指定的文件系统路径中寻找指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作,具体如下所示。
ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);
它与 ClassPathXmlApplicationContext 的区别是:在读取 Spring 的配置文件时,FileSystemXmlApplicationContext 不会从类路径中读取配置文件,而是通过参数指定配置文件的位置。即 FileSystemXmlApplicationContext 可以获取类路径之外的资源,如“F:/workspaces/Beans.xml”。
通常在 Java 项目中,会采用 ClassPathXmlApplicationContext 类实例化 ApplicationContext 容器的方式,而在 Web 项目中,ApplicationContext 容器的实例化工作会交由 Web 服务器完成。Web 服务器实例化 ApplicationContext 容器通常使用基于 ContextLoaderListener 实现的方式,它只需要在 web.xml 中添加如下代码:
<!--指定Spring配置文件的位置,有多个配置文件时,以逗号分隔-->
<context-param>
<param-name>contextConfigLocation</param-name>
<!--spring将加载spring目录下的applicationContext.xml文件-->
<param-value>
classpath:spring/applicationContext.xml
</param-value>
</context-param>
<!--指定以ContextLoaderListener方式启动Spring容器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
需要注意的是,BeanFactory 和 ApplicationContext 都是通过 XML 配置文件加载 Bean 的。
二者的主要区别在于,如果 Bean 的某一个属性没有注入,使用 BeanFacotry 加载后,第一次调用 getBean() 方法时会抛出异常,而 ApplicationContext 则会在初始化时自检,这样有利于检查所依赖的属性是否注入。
因此,在实际开发中,通常都选择使用 ApplicationContext,只有在系统资源较少时,才考虑使用 BeanFactory。
Spring Bean定义
由 Spring IoC 容器管理的对象称为 Bean,Bean 根据 Spring 配置文件中的信息创建。
可以把 Spring IoC 容器看作是一个大工厂,Bean 相当于工厂的产品,如果希望这个大工厂生产和管理 Bean,则需要告诉容器需要哪些 Bean,以及需要哪种方式装配 Bean。
Spring 配置文件支持两种格式,即 XML 文件格式和 Properties 文件格式。
- Properties 配置文件主要以 key-value 键值对的形式存在,只能赋值,不能进行其他操作,适用于简单的属性配置。
- XML 配置文件是树形结构,相对于 Properties 文件来说更加灵活。XML 配置文件结构清晰,但是内容比较繁琐,适用于大型复杂的项目。
通常情况下,Spring 的配置文件使用 XML 格式。XML 配置文件的根元素是 <beans>
,该元素包含了多个子元素 <bean>
。每一个 <bean>
元素都定义了一个 Bean,并描述了该 Bean 如何被装配到 Spring 容器中。
在上一节中的 Beans.xml 配置文件,代码如下所示:
<?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.springlearn.HelloWorld">
<property name="message" value="Hello World!" />
</bean>
</beans>
解释
-
xmlns="http://www.springframework.org/schema/beans"
声明xml文件默认的命名空间,表示未使用其他命名空间的所有标签的默认命名空间。
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
声明XML Schema实例,声明后就可以使用schemaLocation属性。
-
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd“指定Schema的位置这个属性必须结合命名空间使用。这个属性有两个值,第一个值表示需要使用的命名空间。第二个值表示供命名空间使用的XML schema的位置。
上面配置的命名空间指定xsd规范文件,这样你在进行下面具体配置的时候就会根据这些xsd规范文件给出相应的提示,比如说每个标签是怎么写的,都有些什么属性是都可以智能提示的,在启动服务的时候也会根据xsd规范对配置进行校验。
配置技巧:对于属性值的写法是有规律的,中间使用空格隔开,后面的值是前面的补充,也就是说,前面的值是去除了xsd文件后得来的。
上述代码中,使用 id 属性定义了 Bean,并使用 class 属性指定了 Bean 对应的类。
<bean>
元素中可以包含很多属性,其常用属性如下表所示。
属性名称 | 描述 |
---|---|
id | Bean 的唯一标识符,Spring 容器对 Bean 的配置和管理都通过该属性完成。id 的值必须以字母开始,可以使用字母、数字、下划线等符号。 |
name | name 属性中可以为 Bean 指定多个名称,每个名称之间用逗号或分号隔开。Spring 容器可以通过 name 属性配置和管理容器中的 Bean。 |
class | 该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,即类的全限定名。 |
scope | 用于设定 Bean 实例的作用域,属性值可以为 singleton(单例)、prototype(原型)、request、session 和 global Session。其默认值是 singleton |
constructor-arg | |
property | |
ref | |
value | |
list | 用于封装 List 或数组类型的依赖注入 |
set | 用于封装 Set 类型的依赖注入 |
map | 用于封装 Map 类型的依赖注入 |
entry | |
init-method | 容器加载 Bean 时调用该方法,类似于 Servlet 中的 init() 方法 |
destroy-method | 容器删除 Bean 时调用该方法,类似于 Servlet 中的 destroy() 方法。该方法只在 scope=singleton 时有效 |
lazy-init | 懒加载,值为 true,容器在首次请求时才会创建 Bean 实例;值为 false,容器在启动时创建 Bean 实例。该方法只在 scope=singleton 时有效 |
Spring Bean作用域
Bean的生命周期,从创建到销毁。
在配置文件中,除了可以定义 Bean 的属性值和相互之间的依赖关系,还可以声明 Bean 的作用域。例如,如果每次获取 Bean 时,都需要一个 Bean 实例,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。
作用域的种类
Spring 容器在初始化一个 Bean 实例时,同时会指定该实例的作用域。Spring 5 支持以下 6 种作用域。
-
singleton
默认值,单例模式,表示在 Spring 容器中只有一个 Bean 实例,Bean 以单例的方式存在。
-
prototype
原型模式,表示每次通过 Spring 容器获取 Bean 时,容器都会创建一个 Bean 实例。
-
request
每次 HTTP 请求,容器都会创建一个 Bean 实例。该作用域只在当前 HTTP Request 内有效。
-
session
同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在当前 HTTP Session 内有效。
-
application
同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。
类似于 singleton,不同的是,singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而同一个 Web 应用中可能会有多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。
-
websocket
websocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。
注意:Spring 5 版本之前还支持 global Session,该值表示在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。一般用于 Portlet 应用环境。Spring 5.2.0 版本中已经将该值移除了。
request、session、application、websocket 和 global Session 作用域只能在 Web 环境下使用,如果使用 ClassPathXmlApplicationContext 加载这些作用域中的任意一个的 Bean,就会抛出以下异常。
java.lang.IllegalStateException: No Scope registered for scope name 'xxx'
下面我们详细讲解常用的两个作用域:singleton 和 prototype。
singleton
singleton 是 Spring 容器默认的作用域。
当 Bean 的作用域为 singleton 时,Spring 容器中只会存在一个共享的 Bean 实例。该 Bean 实例将存储在高速缓存中,并且所有对 Bean 的请求,只要 id 与该 Bean 定义相匹配,都会返回该缓存对象。
通常情况下,这种单例模式对于无会话状态的 Bean(如 DAO 层、Service 层)来说,是最理想的选择。
在 Spring 配置文件中,可以使用 <bean>
元素的 scope 属性,将 Bean 的作用域定义成 singleton,其配置方式如下所示:
<bean id="..." class="..." scope="singleton"/>
例子
我们重新改装MainApp类
package com.springlearn;
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("对象A");
objA.getMessage();
HelloWorld objB = (HelloWorld) context.getBean("helloWorld");
objB.getMessage();
}
}
由于Spring 容器默认的作用域是singleton。
所以Bean.xml可以不用修改,当前你也可以加上scope
<?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.springlearn.HelloWorld" scope="singleton" />
</beans>
运行结果如下
message : 对象A
message : 对象A
从运行结果可以看出,两次输出内容相同,这说明 Spring 容器只创建了一个 HelloWorld 类的实例。由于 Spring 容器默认作用域是 singleton,所以如果省略 scope 属性,其输出结果也会是一个实例。
prototype
瞬时
对于 prototype 作用域的 Bean,Spring 容器会在每次请求该 Bean 时都创建一个新的 Bean 实例。prototype 作用域适用于需要保持会话状态的 Bean(如 Struts2 的 Action 类)。
在 Spring 配置文件中,可以使用
<bean id="..." class="..." scope="prototype"/>
例子
修改配置文件 Beans.xml,内容如下。
<?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.springlearn.HelloWorld" scope="prototype" />
</beans>
运行结果如下
message : 对象A
message : null
从运行结果可以看出,两次输出的内容并不相同,这说明在 prototype 作用域下,Spring 容器创建了两个不同的 HelloWorld 实例。
Spring Bean生命周期
在传统的 Java 应用中,Bean 的生命周期很简单,使用关键字 new 实例化 Bean,当不需要该 Bean 时,由 Java 自动进行垃圾回收。
Spring 中 Bean 的生命周期为:Bean 的定义 -> Bean 的初始化 -> Bean 的使用 -> Bean 的销毁。
Spring 根据 Bean 的作用域来选择管理方式。对于 singleton 作用域的 Bean,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁;而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。
Spring Bean生命周期执行流程
Spring 容器在确保一个 Bean 能够使用之前,会进行很多工作。Spring 容器中 Bean 的生命周期流程如下图所示。
Bean 生命周期的整个执行过程描述如下。
- Spring 启动,查找并加载需要被 Spring 管理的 Bean,并实例化 Bean。
- 利用依赖注入完成 Bean 中所有属性值的配置注入。
- 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
- 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
- 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
- 如果 Bean 实现了 BeanPostProcessor 接口,则 Spring 调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
- 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
- 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
- 如果 BeanPostProcessor和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
- 如果在
中指定了该 Bean 的作用域为 singleton,则将该 Bean 放入 Spring IoC 的缓存池中,触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用域为 prototype,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。 - 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法销毁 Bean;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。
Spring 为 Bean 提供了细致全面的生命周期过程,实现特定的接口或设置
了解 Spring 生命周期的意义就在于,可以利用 Bean 在其存活期间的指定时刻完成一些相关操作。一般情况下,会在 Bean 被初始化后和被销毁前执行一些相关操作。
Spring 官方提供了 3 种方法实现初始化回调和销毁回调:
- 实现 InitializingBean 和 DisposableBean 接口;
- 在 XML 中配置 init-method 和 destory-method;
- 使用 @PostConstruct 和 @PreDestory 注解。
在一个 Bean 中有多种生命周期回调方法时,优先级为:注解 > 接口 > XML。
不建议使用接口和注解,这会让 pojo 类和 Spring 框架紧耦合。
初始化回调
-
使用接口
修改
HelloWorld
类package com.springlearn; import org.springframework.beans.factory.InitializingBean; public class HelloWorld implements InitializingBean { private String message; public void setMessage(String message) { this.message = message; } public void getMessage() { System.out.println("message : " + message); } @Override public void afterPropertiesSet() throws Exception { System.out.printf("调用接口:InitializingBean,方法:afterPropertiesSet,无参数"); } }
调用接口:InitializingBean,方法:afterPropertiesSet,无参数message : 对象A 调用接口:InitializingBean,方法:afterPropertiesSet,无参数message : null
-
配置XML
bean.xml
<?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.springlearn.HelloWorld" init-method="init" scope="prototype" /> </beans>
添加
init()
方法package com.springlearn; import org.springframework.beans.factory.InitializingBean; public class HelloWorld{ private String message; public void setMessage(String message) { this.message = message; } public void getMessage() { System.out.println("message : " + message); } public void init() { System.out.println("调用init-method指定的初始化方法:init" ); } }
-
使用注解
使用 @PostConstruct 注解标明该方法为 Bean 初始化后的方法。
public class HelloWrold { @PostConstruct public void init() { System.out.println("@PostConstruct注解指定的初始化方法:init" ); } }
使用
@PostConstruct
注解需要引入javax.annotation
在
pom.xml
中添加<dependency> <groupId>javax.annotation</groupId> <artifactId>jsr250-api</artifactId> <version>1.0</version> </dependency>
销毁回调
-
使用接口
org.springframework.beans.factory.DisposableBean 接口提供了以下方法:
void destroy() throws Exception;
您可以实现以上接口,在 destroy 方法内指定 Bean 初始化后需要执行的操作。
<bean id="..." class="..." /> public class HelloWrold implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { System.out.println("调用接口:InitializingBean,方法:afterPropertiesSet,无参数"); } }
-
配置XML
<bean id="..." class="..." destroy-method="destroy"/> public class HelloWrold { public void destroy() { System.out.println("调用destroy-method指定的销毁方法:destroy" ); } }
-
使用注解
使用 @PreDestory 注解标明该方法为 Bean 销毁前执行的方法。
public class HelloWrold { @PreDestory public void init() { System.out.println("@PreDestory注解指定的初始化方法:destroy" ); } }
示例
HelloWorld
package com.springlearn;
public class HelloWorld{
private String message;
public void setMessage(String message) {
this.message = message;
}
public void getMessage() {
System.out.println("message : " + message);
}
public void init() {
System.out.println("Bean正在进行初始化");
}
public void destroy() {
System.out.println("Bean将要被销毁");
}
}
Bean.xml
<?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.springlearn.HelloWorld" init-method="init" destroy-method="destroy">
<property name="message" value="Hello World!" />
</bean>
</beans>
MainApp
package com.springlearn;
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");
Thread.currentThread();
HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
objA.getMessage();
//销毁容器
context.registerShutdownHook();
}
}
Bean正在进行初始化
message : Hello World!
Bean将要被销毁
默认的初始化和销毁方法
如果多个 Bean 需要使用相同的初始化或者销毁方法,不用为每个 bean 声明初始化和销毁方法,可以使用 default-init-method 和 default-destroy-method 属性,如下所示。
<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="...">
...
</bean>
</beans>
BeanPostProcessor(Spring后置处理器)
BeanPostProcessor 接口也被称为后置处理器,通过该接口可以自定义调用初始化前后执行的操作方法。
BeanPostProcessor 接口源码如下:
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
postProcessBeforeInitialization 在 Bean 实例化、依赖注入后,初始化前调用。postProcessAfterInitialization 在 Bean 实例化、依赖注入、初始化都完成后调用。
当需要添加多个后置处理器实现类时,默认情况下 Spring 容器会根据后置处理器的定义顺序来依次调用。也可以通过实现 Ordered 接口的 getOrder 方法指定后置处理器的执行顺序。该方法返回值为整数,默认值为 0,值越大优先级越低。
新建InitHelloWorld 类代码如下
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
public class InitHelloWorld implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("A Before : " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("A After : " + beanName);
return bean;
}
@Override
public int getOrder() {
return 5;
}
}
需要注意的是,postProcessBeforeInitialization 和 postProcessAfterInitialization 方法返回值不能为 null,否则会报空指针异常或者通过 getBean() 方法获取不到 Bean 实例对象,因为后置处理器从Spring IoC 容器中取出 Bean 实例对象后没有再次放回到 IoC 容器中。
InitHelloWorld2 的代码如下。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
public class InitHelloWorld2 implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("B Before : " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("B After : " + beanName);
return bean;
}
@Override
public int getOrder() {
return 0;
}
}
beans.xml
<?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="net.biancheng.HelloWorld"
init-method="init" destroy-method="destroy">
<property name="message" value="Hello World!" />
</bean>
<!-- 注册处理器 -->
<bean class="net.biancheng.InitHelloWorld" />
<bean class="net.biancheng.InitHelloWorld2" />
</beans>
MainApp
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();
}
}
B Before : helloWorld
A Before : helloWorld
Bean正在初始化
B After : helloWorld
A After : helloWorld
Message : Hello World!
Bean将要被销毁
由运行结果可以看出,postProcessBeforeInitialization 方法是在 Bean 实例化和依赖注入后,自定义初始化方法前执行的。而 postProcessAfterInitialization 方法是在自定义初始化方法后执行的。由于 getOrder 方法返回值越大,优先级越低,所以 InitHelloWorld2 先执行。