文章目录
前言
本篇通过代码方式演示spirng bean 完整的生命周期。
一、Spirng bean 的生命周期:
Spring Bean的生命周期主要分为 实例化,属性注入,初始化前处理(Initialization pre-processing,初始化(Initialization),初始化后处理(Initialization post-processing,AOP 代理,bean 的使用,bean 的销毁
。
二、 示例代码:
2.1 代码参考
代码如下(示例):
// 类1
package com.example.springmycat.bean;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import javax.annotation.PostConstruct;
public class BeanProcess implements InitializingBean, BeanNameAware, DisposableBean {
private String name;
public String getName() {
return name;
}
public BeanProcess() {
System.out.println("-------- 1 对象创建");
}
public void setName(String name) {
System.out.println("-------- 2属性注入");
this.name = name;
}
@Override
public void setBeanName(String s) {
System.out.println("-------- 3 BeanNameAware bean 的名称"+s);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("-------- 5.1 对象初始化InitializingBean");
}
@PostConstruct
// @PreDestroy
public void init(){
System.out.println("-------- 对象PostConstruct");
}
public void myInit(){
System.out.println("-------- 5.2 对象初始化init-method");
}
@Override
public String toString() {
System.out.println("-------- 7 对象使用");
return "BeanProcess{" +
"name='" + name + '\'' +
'}';
}
@Override
public void destroy() throws Exception {
System.out.println("-------- 8 对象销毁DisposableBean");
}
public void myDestroy() {
System.out.println("-------- 9 对象销毁myDestroy");
}
}
// 类2
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if ("beanProcess".equals(beanName)){
System.out.println("-------- 4 对象初始化之前");
}
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if ("beanProcess".equals(beanName)){
System.out.println("-------- 6 对象初始化之后");
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
// 类3
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanProcessTest {
public static void main(String[] args) {
ApplicationContext applicationContext= new ClassPathXmlApplicationContext("beanProcess.xml");
BeanProcess beanProcess= applicationContext.getBean(BeanProcess.class);
System.out.println(beanProcess);
((ClassPathXmlApplicationContext)applicationContext).close();
}
}
bean 定义:
<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="beanProcess" class="com.example.springmycat.bean.BeanProcess" init-method="myInit" destroy-method="myDestroy" >
<property name="name" value="John"/>
</bean>
<bean id="myBeanPostProcessor" class="com.example.springmycat.bean.MyBeanPostProcessor"></bean>
</beans>
2.2 代码执行结果
-------- 1 对象创建
-------- 2属性注入
-------- 3 BeanNameAware bean 的名称beanProcess
-------- 4 对象初始化之前
-------- 5.1 对象初始化InitializingBean
-------- 5.2 对象初始化init-method
-------- 6 对象初始化之后
-------- 7 对象使用
BeanProcess{name='John'}
16:52:59.975 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Closing org.springframework.context.support.ClassPathXmlApplicationContext@2ff5659e, started on Fri Oct 13 16:52:59 CST 2023
-------- 8 对象销毁DisposableBean
-------- 9 对象销毁myDestroy
三、 一些方法的对比:
3.1 BeanNameAware 接口 :
在Spring框架中,BeanNameAware接口是一个特殊的接口,其设计主要用于让·Bean知道并获取到自己在Spring配置文件中定义的id或name
。
如果一个类实现了BeanNameAware接口,那么在Spring容器初始化该Bean时,容器会调用这个Bean的setBeanName(String name)方法,将该Bean在容器中的id或name作为参数传入。这样,这个Bean就可以在其内部访问到自身在Spring容器中的id或name了。
public class MyBean implements BeanNameAware {
private String beanName;
@Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public void printName(){
System.out.println("Bean的名称为: " + beanName);
}
}
在上述代码中,当Spring容器初始化MyBean实例时,会调用setBeanName()方法,将MyBean在容器中的id或name传入。然后在MyBean内部,就可以通过printName()方法打印出自身在Spring容器中的名称了。
使用BeanNameAware接口的情况虽然不多,但在某些场合下,知道Bean的id或name可以帮助我们更灵活地控制和管理Bean。例如,我们可以将Bean的名称用于日志记录,或者基于Bean的名称实现一些特定的业务逻辑。
3.2 bean 的属性注入:
Spring通过对字段添加@Autowired、@Resource、@Value等注解,通过反射的方式 ,自动按类型或按名称进行依赖注入
。
Spring的依赖注入(Dependency Injection,简称DI)可以对类中的以下属性进行值的填充:
3.2.1 属性的3种填充方式:
- 成员变量/字段:Spring通过对字段添加@Autowired、@Resource、@Value等注解,能够自动按类型或按名称进行依赖注入。
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
}
在上面的示例中,userRepository字段上的@Autowired注解告诉Spring,需要一个UserRepository类型的bean,并将其注入到userRepository字段中。
- 构造器参数:Spring也可以通过构造函数来进行依赖注入。当一个类只有构造函数时,Spring 4.3以上版本可以省略@Autowired注解,Spring能够自动完成依赖注入。
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
在上述代码中,UserRepository类型的bean将被自动注入到UserService的构造函数参数中。
- Setter方法参数:除了字段和构造函数,Spring也支持通过setter方法进行依赖注入。
@Service
public class UserService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
在以上例子中,UserRepository类型的bean将被自动注入到setUserRepository方法的参数中。
总的来说,只要是非静态的字段、构造器或设置器方法,且存在于Spring容器管理的类中,都可以通过Spring的依赖注入进行值填充。
3.2.2 基于字段的属性填充:
基于字段的注入,即直接将依赖注入到类的字段(成员变量)中。Spring 使用 Java 的反射 API 实现这种注入。
在 Spring 的依赖注入过程中,当 Spring IoC 容器识别到一个Bean中的字段上标记了 @Autowired 或 @Resource 等注解,就会触发依赖注入流程。
在依赖注入过程中,Spring 会使用 Java 的反射 API 来访问和修改这个字段。具体来说,Spring 会通过 Field 类的 setAccessible(true) 方法来打开这个字段的访问权限,然后使用 Field 类的 set(Object obj, Object value) 方法来给字段设置值。
这个过程中,Spring 会首先寻找 IoC 容器中匹配的Bean,并根据字段类型或者名字来匹配(具体取决于 @Autowired 或 @Resource 等注解的使用方式)。如果找到了匹配的Bean,就会将这个Bean设置为字段的值。
如果没有找到匹配的Bean,则根据注解的 required 属性(默认为 true),Spring可能会抛出异常,也可能不做任何操作。如果 required 属性设置为 false,当没有找到匹配的Bean时,Spring 就不会做任何操作,字段的值将为null。
例如:
Field field = someObject.getClass().getDeclaredField("fieldName");
field.setAccessible(true);
field.set(someObject, value);
3.3 spring @PostConstruct 和 InitializingBean :
在Spring框架中,有多种方法可以在bean初始化时执行特定的代码。@PostConstruct
注解和InitializingBean
接口就是两种最常见的方式。
以下是使用这两者时的执行顺序:
-
首先,Spring容器会调用bean的构造函数或工厂方法来创建bean实例。
-
然后,Spring会使用依赖注入(DI)来设置bean的属性。
-
一旦所有的属性被设置,Spring框架就会根据以下顺序调用初始化方法:
-
如果bean实现了
InitializingBean
接口,那么Spring会调用它的afterPropertiesSet
方法。 -
如果bean配置了
@PostConstruct
注解的方法,那么Spring会调用这个注解标记的方法。注意,这个注解标记的方法在afterPropertiesSet
方法之后被调用。 -
如果在Spring的XML配置文件中定义了bean的
init-method
,那么这个方法将在@PostConstruct
注解的方法之后被调用。
-
如果你在同一个bean中同时使用InitializingBean
接口和@PostConstruct
注解,那么afterPropertiesSet
方法将会在@PostConstruct
方法之前运行。这是因为InitializingBean
接口是Spring框架的一部分,而@PostConstruct
注解是Java EE 5的一部分,Spring在设计之初决定了后者的优先级更低。
3.4 bean 中的 destroy-method=“myDestroy” 和spring DisposableBean
Spring中的destroy-method
属性和DisposableBean
接口都可以用于自定义Bean的销毁方法,它们都可以在Bean销毁时执行某些清理工作,比如关闭网络连接、释放资源等。
不过,它们之间有一些区别:
-
如果一个Bean实现了
DisposableBean
接口,那么Spring容器在销毁这个Bean时会自动调用它的destroy()
方法。但是如果你在Bean的定义中同时指定了destroy-method
属性,那么Spring将会先调用destroy()
方法,然后再调用指定的销毁方法。 -
destroy-method
更灵活,它允许你指定任何名称的方法作为销毁方法,而不仅仅是destroy()
;而且,销毁方法可以有任意的访问权限,可以有参数,也可以抛出异常。 -
如果一个类实现了
DisposableBean
接口,那么这个类的所有实例都将有一个销毁方法;而destroy-method
只影响定义了此属性的Bean。
总的来说,DisposableBean
接口和destroy-method
属性都可以用于定制Bean的销毁过程,但destroy-method
属性更灵活,更易于使用。不过,如果你希望在销毁Bean时执行的清理工作是通用的,那么实现`DisposableBean接口可能是更好的选择。