疑问:Spring 中构造器、init-method、@PostConstruct、afterPropertiesSet 孰先孰后,自动注入发生时间
一、前言
spring的一大优点就是扩展性很强,比如,在spring bean 的生命周期中,给我们预留了很多参与bean 的生命周期的方法。
大致梳理一下,有以下几种:
- 通过实现 InitializingBean/DisposableBean 接口来定制初始化之后/销毁之前的操作方法;
- 通过 <bean> 元素的 init-method/destroy-method属性指定初始化之后 /销毁之前调用的操作方法;
- 在指定方法上加上@PostConstruct 或@PreDestroy注解来制定该方法是在初始化之后还是销毁之前调用;
- 自定义 org.springframework.beans.factory.config.BeanPostProcessor ,来让 spring 回调我们的方法来参与 bean的生命周期。
但有个问题是,如果同时使用上面四种方式,会是什么结果? 谁先,谁后呢?
二、验证
1、新建工程
我这边建立了测试工程,源码在文末:
xml中定义如下:
<bean class="com.ckl.springbeanlifecycle.DemoController" init-method="init" destroy-method="cleanUp"></bean>
重点是我们的DemoController,该类作为一个 bean,在xml中已经定义了,我们为该类,实现了各种接口,以及各种生命周期的相关注解:
package com.ckl.springbeanlifecycle; import com.ckl.springbeanlifecycle.service.IDemoService;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; /**
* desc:
*
* @author : caokunliang
* creat_date: 2019/7/20 0020
* creat_time: 18:45
**/
public class DemoController implements InitializingBean,DisposableBean { @Autowired
private IDemoService iDemoService; public DemoController() {
System.out.println();
System.out.println("constructor ");
System.out.println( "属性:" + iDemoService);
System.out.println();
} @Override
public void destroy() throws Exception {
System.out.println();
System.out.println("implements DisposableBean interface");
System.out.println( "属性iDemoService已注入:" + (iDemoService != null));
System.out.println( "属性iDemoService已注入:" + iDemoService);
System.out.println();
} @Override
public void afterPropertiesSet() throws Exception {
System.out.println();
System.out.println("afterPropertiesSet interface");
System.out.println( "属性iDemoService已注入:" + (iDemoService != null));
System.out.println( "属性iDemoService已注入:" + iDemoService);
System.out.println();
} @PostConstruct
public void postConstruct(){
System.out.println();
System.out.println("@PostConstrut....");
System.out.println( "属性iDemoService已注入:" + (iDemoService != null));
System.out.println( "属性iDemoService已注入:" + iDemoService);
System.out.println();
} @PreDestroy
public void preDestroy(){
System.out.println();
System.out.println("@PreDestroy.....");
System.out.println( "属性iDemoService已注入:" + iDemoService);
System.out.println();
} public void init(){
System.out.println();
System.out.println("init-method by xml 配置文件");
System.out.println( "属性iDemoService已注入:" + (iDemoService != null));
System.out.println();
}
public void cleanUp(){
System.out.println();
System.out.println("destroy-method by xml 配置文件");
System.out.println( "属性iDemoService已注入:" + iDemoService);
System.out.println();
}
}
因为我们还需要验证 org.springframework.beans.factory.config.BeanPostProcessor,所以我们自定义了一个 org.springframework.beans.factory.config.BeanPostProcessor:
package com.ckl.springbeanlifecycle; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component; import java.lang.reflect.Field; /**
* desc:
*
* @author : caokunliang
* creat_date: 2019/7/20 0020
* creat_time: 18:52
**/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof DemoController){
System.out.println();
System.out.println("BeanPostProcessor:" + "postProcessBeforeInitialization");
Field field = null;
try {
field = bean.getClass().getDeclaredField("iDemoService");
field.setAccessible(true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
try {
Object o = field.get(bean);
System.out.println( "属性iDemoService已注入:" + (o != null));
System.out.println( "属性iDemoService已注入:" + o);
System.out.println();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return bean;
} @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof DemoController){
System.out.println();
System.out.println("BeanPostProcessor:" + "postProcessAfterInitialization");
Field field = null;
try {
field = bean.getClass().getDeclaredField("iDemoService");
field.setAccessible(true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
try {
Object o = field.get(bean);
System.out.println( "属性iDemoService已注入:" + (o != null));
System.out.println( "属性iDemoService已注入:" + o);
System.out.println();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return bean;
}
}
2、运行
初始化时的顺序如下:
关闭容器时,执行顺序为:
3、扩展
可能部分朋友,还有这样的需求,即,在程序启动完成后,做点事情,比如,预热缓存之类的,那么,你可以这样:
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service; /**
* desc:
*
* @author : caokunliang
* creat_date: 2018/11/20 0020
* creat_time: 14:50
**/
@Service
public class InitRunner implements ApplicationListener<ContextRefreshedEvent> { @Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
//root application context
if (contextRefreshedEvent.getApplicationContext().getParent() == null) {
/**
* 这里既是 root 容器初始化完毕后,会进入该分支,
*/
todo。。。 }else {
/**
* 如果是spring mvc的话, dispatchServlet对应的 applicationContext 会进入这个分支
*/ ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
todo。。。
} }
}
上面是针对启动时做的事情,关闭时,如果想做点事情,可以这样:
package com.ceiec.webservice.init; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Service; /**
* desc:
*
* @author : caokunliang
* creat_date: 2018/11/20 0020
* creat_time: 14:50
**/
@Service
public class CloseRunner implements ApplicationListener<ContextClosedEvent> {
private static final Logger logger = LoggerFactory.getLogger(CloseRunner.class); @Override
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
logger.info("clean resources when close applicationContext");
if(contextClosedEvent.getApplicationContext().getParent() == null){ } }
}
4、spring boot 的扩展
针对spring boot,也可以注册我们的 listener来参与生命周期。
实现 org.springframework.boot.SpringApplicationRunListener 即可,并将自定义的 listener 配置到 meta-inf 下的 spring.factories 文件。
举例如下:
package com.ceiec.router.config; import com.ceiec.router.config.servletconfig.MyServletContext;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource; import javax.servlet.ServletContext;
import java.util.Map; /**
* desc:
*
* @author : caokunliang
* creat_date: 2019/5/24 0024
* creat_time: 20:07
**/
@Data
@Slf4j
public class MyListener implements SpringApplicationRunListener { public MyListener(SpringApplication application, String[] args) {
super();
} @Override
public void starting() { } @Override
public void environmentPrepared(ConfigurableEnvironment environment) {
MutablePropertySources propertySources = environment.getPropertySources();
for (PropertySource<?> propertySource : propertySources) {
Object value = propertySource.getProperty("spring.liveBeansView.mbeanDomain"); if (value != null) {
MapPropertySource source = (MapPropertySource) propertySource;
Map<String, Object> map = source.getSource();
map.remove("spring.liveBeansView.mbeanDomain"); log.info("spring.liveBeansView.mbeanDomain: after: {}",propertySource.getProperty("spring.liveBeansView.mbeanDomain"));
}
}
} @Override
public void contextPrepared(ConfigurableApplicationContext context) {
log.info("contextPrepared");
ServletContext servletContext = new MyServletContext();
ServletWebServerApplicationContext applicationContext = (ServletWebServerApplicationContext) context;
applicationContext.setServletContext(servletContext);
} @Override
public void contextLoaded(ConfigurableApplicationContext context) {
//Not used.
} @Override
public void started(ConfigurableApplicationContext context) { } @Override
public void running(ConfigurableApplicationContext context) { } @Override
public void failed(ConfigurableApplicationContext context, Throwable exception) { } }
源码在交友网站: https://github.com/cctvckl/spring-bean-lifecycle
疑问:Spring 中构造器、init-method、@PostConstruct、afterPropertiesSet 孰先孰后,自动注入发生时间的更多相关文章
- Spring中构造器、init-method、@PostConstruct、afterPropertiesSet孰先孰后,自动注入发生时间以及单例多例的区别、SSH线程安全问题
首先明白,spring的IOC功能需要是利用反射原理,反射获取类的无参构造方法创建对象,如果一个类没有无参的构造方法spring是不会创建对象的.在这里需要提醒一下,如果我们在class中没有显示的声 ...
- 疑问:Spring中构造器、init-method、@PostConstruct、afterPropertiesSet孰先孰后,自动注入发生时间
问题:今天想写一个通用点的方法,根据传入的参数的类型(clazz对象),判断使用哪个mapper来插入mysql数据库. 下面是我的写法: public interface BizNeeqCommon ...
- Spring 中IOC(控制反转)&& 通过SET方式为属性注入值 && Spring表达式
### 1. Spring IoC IoC:Inversion of control:控制反转:在传统开发模式下,对象的创建过程和管理过程都是由开发者通过Java程序来实现的,操作权在开发者的Java ...
- 谈谈Spring中的对象跟Bean,你知道Spring怎么创建对象的吗?
本系列文章: 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configuration注解? 推荐阅读: Spring官网阅读 | 总结篇 Spring杂 ...
- 原创 | 我被面试官给虐懵了,竟然是因为我不懂Spring中的@Configuration
GitHub 3.7k Star 的Java工程师成神之路 ,不来了解一下吗? GitHub 3.7k Star 的Java工程师成神之路 ,真的不来了解一下吗? GitHub 3.7k Star 的 ...
- 我被面试官给虐懵了,竟然是因为我不懂Spring中的@Configuration
现在大部分的Spring项目都采用了基于注解的配置,采用了@Configuration 替换标签的做法.一行简单的注解就可以解决很多事情.但是,其实每一个注解背后都有很多值得学习和思考的内容.这些思考 ...
- Spring官网阅读(十)Spring中Bean的生命周期(下)
文章目录 生命周期概念补充 实例化 createBean流程分析 doCreateBean流程分析 第一步:factoryBeanInstanceCache什么时候不为空? 第二步:创建对象(crea ...
- Spring中的Autowired注解和Resource注解的区别
1.所属jar包不同,Autowired是Spring中的Resource是JSR-250规范定义的注解
- Spring中的ThreadPoolTaskExecutor
在观察线上系统的运行情况下,发现在错误日志中有这类错误信息,org.springframework.core.task.TaskRejectedException,于是便对ThreadPoolTa ...
随机推荐
- 函数防抖VS函数节流
(1)函数防抖debounce 函数触发停止一段时间后(期间不能再触发 debounce,否则将重新计时),再执行回调函数 机制: 防抖函数主要利用定时器的延迟执行特性,根据是否有定时器在等待执行: ...
- tensorflow基础-数据类型
一:tensorflow中的计算定义和执行 首先,对于tensorflow来说,最重要的概念就是图(Graph)和会话(Session),tensorflow的计算思想是:以图的形式来表示模型,表示和 ...
- 剑指Offer-43.左旋转字符串(C++/Java)
题目: 汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果.对于一个给定的字符序列S,请你把其循环左移K位后的序列输出.例如,字符序列S=”abcX ...
- 富文本编辑器 tinymce 的安装与使用
百度的富文本编辑器大家都熟悉,那么下面给大家介绍一款富文本编辑器tinymce ,个人感觉比百度的界面好看,调用方便,就不知道各位大神怎么看咯! tinymce中文文档 以下是vue中使用示例,献上最 ...
- BOM的初级理解
1.什么是BOM,BOm有什么作用? BOM和DOM.ES是JavaScript的重要三个组成部分: 其中BOM是专门操作浏览器的API,其实他就是一种兼容性问题,这其中问题比较大就是IE浏览器,谁叫 ...
- 一文掌握 Lambda 表达式
本文将介绍 Java 8 新增的 Lambda 表达式,包括 Lambda 表达式的常见用法以及方法引用的用法,并对 Lambda 表达式的原理进行分析,最后对 Lambda 表达式的优缺点进行一个总 ...
- 【游戏开发】网络编程之浅谈TCP粘包、拆包问题及其解决方案
引子 现如今手游开发中网络编程是必不可少的重要一环,如果使用的是TCP协议的话,那么不可避免的就会遇见TCP粘包和拆包的问题,马三觉得haifeiWu博主的 TCP 粘包问题浅析及其解决方案 这篇博客 ...
- 记一次Ubuntu19无法安装docker源
按照各大网站以及个人习惯我会使用下面这种方法添加Docker源: root@ubuntu:~# sudo add-apt-repository "deb [arch=amd64] https ...
- vue路由传参和获取参数
参考链接 https://router.vuejs.org/zh/guide/essentials/passing-props.html#%E5%B8%83%E5%B0%94%E6%A8%A1%E5% ...
- java基础(3):变量、运算符
1. 变量 1.1 变量概述 前面我们已经学习了常量,接下来我们要学习变量.在Java中变量的应用比常量的应用要多很多.所以变量也是尤为重要的知识点! 什么是变量?变量是一个内存中的小盒子(小容器), ...