Spring的Bean生命周期
在Java中万物皆对象,既然是一个实例对象,那么就会有生命历程:被创建–>被使用–>被销毁…但是这说的太过于简洁了,以至于我们根本不能从本质上认清它具体的执行流程、生命历程,今天就来简单了解下Spring中的Bean生命周期具体流程。
我们知道,spring的ioc是创建和管理对象的关键容器,那么项目启动时,一定是spring容器先被创建出来,然后紧接着就是要放入spring容器中管理的各个组件bean,根据机制(是否为懒加载模式)来确定某些bean是否需要在spring容器被创建后立即创建出来进行管理。
总览图
那么Bean被创建的时候
-
IOC通过反射机制调用Bean的构造函数(创建完Bean对象)
-
进行属性填充,set方法注入等属性注入
-
处理各种实现自xxxAware的实现类
-
如果bean中设置了@PostConstruct方法,那么开始执行
-
BeanPostProcessor的postProcessBeforeInitialization()方法执行,
前置处理器
-
如果bean实现了InitializingBean,重写了afterPropertiesSet(),那么开始执行
-
自定义的init方法,如创建Bean时指定的
init-method
-
BeanPostProcessor的postProcessAfterInitialization()方法执行,
后置处理器
-
bean对象被使用
-
Spring容器销毁
-
实现自DisposableBean的bean执行重写的
destroy
方法 -
自定义的销毁方法,
destroy-method
执行
详解
前两条估计不用说了,先是调用对象的构造函数,然后属性注入执行set方法。
xxxAware
那么第3条,来自xxxAware的实现类目的其实就是让bean获取spring容器中的组件,当我们需要从spring中获取一些组件时可以使用
- BeanNameAware:获取容器中bean名称
- BeanFactorAware:获取BeanFactory容器
- ApplicationContextAware:获取应用上下文
比如有个BeanUtil工具类,通过这个工具类的getBean方法能获取spring中的组件
@Component
public class BeanUtil implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
// 对外方法
public <T> T getBean (Class<T> clazz) {
return context.getBean(clazz);
}
}
BeanPostProcessor处理器
BeanPostProcessor的前置和后置处理器:
- postProcessBeforeInitialization(Object bean, String beanName):当前要被初始化的对象会被传递进来,我们可以对它进行些操作,前置处理
- postProcessAfterInitialization(Object bean, String beanName):当前已经初始化的对象被传递进来,我们可以对它进行些操作,后置处理
spring中的所有bean在初始化前后都会执行前置和后置的处理器,有点类似是全局方式,比如我创建一个类实现了BeanPostProcessor接口,重写了前置和后置的处理,那么所有的bean初始化前后都会执行它,且方法的返回值可以是bean本身,亦可以是bean的包装类对象(代理对象等),一般使用较少,还是要看项目的实际情况使用
其实像实现一些xxxAware
或BeanPostProcessor
在bean初始化过程中横插一脚,多数情况下是没有必要的,除非是项目情况需求万不得已,而且实现这些接口将会使我们应用层的代码耦合到spring框架中,会增加耦合度。
项目启动初始化数据+注入bean
上面大概了解下了Bean的整个生命周期流程,再来详细介绍下项目启动时要进行一些初始化数据或者注意一些bean的正确用法。
如何注入bean
刚开始学习spring时,注入一些属性,基本类型多用了@Value,引用类型基本就是在用@Autowired,而在spring中现在已经不建议去使用@Autowired方式了,先来复习下属性注入的几种方式:
- 属性注入:@Autowired/@Resource
- 构造函数注入
(推荐使用)
- set方法注入
官方不推荐使用属性注入,属性注入其实有一个显而易见的缺点,那就是对于 IOC 容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。因为该类没有提供该属性的 set 方法或者相应的构造方法来完成该属性的初始化。换言之,要是使用属性注入,那么你这个类就只能在 IOC 容器中使用,要是想自己 new 一下这个类的对象,那么相关的依赖无法完成注入。
当然,他不推荐归为不推荐,各位想用他也拦住啊,O(∩_∩)O哈哈~
个人目前构造方法注入用的多一些,配合lombok的注解,用的嘎嘎爽,给个例子
@RestController
@RequestMapping("/snow/score")
@AllArgsConstructor
public class SnowScoreController {
private final SnowRefereeService refereeService;
}
项目启动的初始化操作
介绍个场景:项目启动时,需要将数据库中的一些配置信息数据读取出来加载到java的配置类中,完成初始化数据的操作。
列举下可以使用的方式:
-
@PostConstruct: bean中可以使用@PostConstruct来完成初始化,注意:此bean必须被spring管理,且方法必须是无返回值的
// 翻转到spring中 @Component public class AliyunConfig { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; // 阿里云域名(bucket域名或者自定义域名) private String aliyunDomain; @Autowired private SystemConfigService configService; @PostConstruct void initProperties() { configService.list(); // 以下完成初始化数据操作 } }
-
也阔以将此Bean通过不同的方式反转到ioc中
@Data class BeanTest { private String name; @Autowired private SystemConfigService configService; private void setConfigName() { System.out.println(configService); System.out.println("BeanTest初始化数据"); } } @Configuration @Slf4j public class AliyunConfig { @Bean(initMethod = "setConfigName") public BeanTest beanTest () { return new BeanTest(); } }
-
还可以实现
InitializingBean
,重写方法@Data @Component class BeanTest implements InitializingBean { private String name; @Autowired SystemConfigService systemConfigService; @Override public void afterPropertiesSet() throws Exception { System.out.println("BeanTest初始化"); System.out.println(systemConfigService); } }
-
还可以实现ApplicationRunner
或者
CommandLineRunner,这两个是springboot启动流程中最后被执行的runner,有兴趣可以了解下我的一篇文章Springboot启动流程(源码解析)、自动装配流程(源码解析)、总结、SrpringBoot初始化数据扩展@Component public class MyInitRunner1 implements ApplicationRunner { @Autowired private DispatcherService dispatcherService; @Override public void run(ApplicationArguments args) throws Exception { System.out.println("我是runner1"); System.out.println(dispatcherService); } }
-
实现CommandLineRunner
@Component public class MyInitRunner2 implements CommandLineRunner { @Autowired private DispatcherService dispatcherService; @Override public void run(String... args) throws Exception { System.out.println("我是runner2"); System.out.println(dispatcherService); } }
最后贴一下几个的优先级:
@PostConstruct
> 实现InitializingBean
接口 > 实现ApplicationRunner
或CommandLineRunner
接口
按源码来看,ApplicationRunner的子类执行是优先于CommandLineRunner的子类的,可以通过@Order设置类的加载顺序。
介绍了5种springboot项目启动初始化数据的方式,基本开发中差不多是够用的。