文章目录

  • 前言
  • 业务背景
    • 通过依赖查找实现
    • `@PostConstruct`注解实现
  • @PostConstruct注解原理
    • `@PostConstruct`注解
    • `@PostConstruct`注解源码分析
      • **示例代码**
      • **源码分析**
  • 总结

前言

本文简单来看一下Spring框架@PostConstruct注解的原理。

业务背景

在某些业务场景下我们需要程序在启动的时候就加载某些数据,比如,在程序启动的过程中需要从数据库中加载数据并缓存到程序的内存中。

通过依赖查找实现

针对这个场景最直观的做法是我在容器的启动过程当中,通过依赖查找的方式获取到mapper,然后从数据库中获取数据并缓存到内存中。实现方式如下:


@Slf4j
public class MainClass {public static ClassPathXmlApplicationContext context = null;private static CountDownLatch shutdownLatch = new CountDownLatch(1);public static void main(String[] args) throws Exception {// 加载spring上下文context = new ClassPathXmlApplicationContext(new String[]{"spring-config.xml"});context.start();// 从数据库获取数据并缓存到内存// 1.从容器中获取mapperItpcsConfigMapper itpcsConfigMapper = (ItpcsConfigMapper) context.getBean("itpcsConfigMapper");// 2.从数据库中获取数据List<ItpcsConfig> RuleResultSet = itpcsConfigMapper.selectAll();// 3.将数据加载到PropertyMap中RuleResultSet.forEach(itpcsConfig -> PropertyMap.add(itpcsConfig.getName(), itpcsConfig.getValue()));context.registerShutdownHook();log.info(LogUtil.marker(), "System already started.");shutdownLatch.await();}
}

这么实现的确也没问题,但是如果有一种更加优雅的实现那就更好了,这个时候@PostConstruct注解就登场了。

@PostConstruct注解实现

@Slf4j
@Component
public class InitConfigParameter {@Resourceprivate ItpcsConfigMapper itpcsConfigMapper;@PostConstructpublic void init() throws Exception {// 将数据库中的参数加载到哈希表中List<ItpcsConfig> RuleResultSet = itpcsConfigMapper.selectAll();log.info(LogUtil.marker(RuleResultSet), "init propertyMap");RuleResultSet.forEach(itpcsConfig -> PropertyMap.add(itpcsConfig.getName(), itpcsConfig.getValue()));}
}

使用@PostConstruct注解修饰的init方法就会在Spring容器的启动时自动的执行,下面我们看一下@PostConstruct注解是做和做到的这个能力。

@PostConstruct注解原理

@PostConstruct注解

/*** The PostConstruct annotation is used on a method that needs to be executed* after dependency injection is done to perform any initialization. This* method MUST be invoked before the class is put into service. This* annotation MUST be supported on all classes that support dependency* injection. The method annotated with PostConstruct MUST be invoked even* if the class does not request any resources to be injected. Only one* method can be annotated with this annotation. The method on which the* PostConstruct annotation is applied MUST fulfill all of the following** 省略...*/
@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PostConstruct {}

@PostConstruct注解的源码注释文档描述了该注解的功能:描述了这个注解被用于需要在依赖注入完成后执行的方法上。其他注释描述了一些该注解的必要条件;那么他究竟是如何实现的在依赖注入完成后执行方法呢,下面我们通过简单测试和源码一起分析一下。

@PostConstruct注解源码分析

一开始,根本无从下手,根本不知道看哪部分的源码,所以这里我们要debug一下被@PostConstruct注解修饰的方法,然后根据idea上的debug调用链看一下方法调用流程来进行追溯源头,示例代码,debug过程如下:

示例代码

示例代码中的OrderServiceOrder为测试依赖注入顺序的,这里先不考虑,后面再说。

@Component
public class PostConstructTest {@Autowiredprivate OrderService orderService;@Resource(name = "order1")private Order order;@PostConstructpublic void initAfter() {Order order = new Order();order.setOrderTotalAmount(1000);orderService.pay(order);System.out.println("Spring 容器初始化完成,执行。。。");}
}

从debug图中能够看出,@PostConstruct注解修饰的方法也是在创建bean,初始化bean的过程中执行的,具体执行从debug的调用链上来看初步判定是反射实现的。

源码分析

CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

我们先忽略bean创建示例和初始化等过程,先定位到CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition方法,别问我是咋定位的,我也是debug和启动日志的搜索定位到的。该方法调用其父类的postProcessMergedBeanDefinition,其父类是InitDestroyAnnotationBeanPostProcessor,从调用链中也能够看出。这里要注意一下CommonAnnotationBeanPostProcessor的构造方法,可以看到构造方法中初始化了initAnnotationType和destroyAnnotationType,initAnnotationType初始化的值就是PostConstruct注解的类,后面就会用到了。

 public CommonAnnotationBeanPostProcessor() {setOrder(Ordered.LOWEST_PRECEDENCE - 3);setInitAnnotationType(PostConstruct.class);setDestroyAnnotationType(PreDestroy.class);ignoreResourceType("javax.xml.ws.WebServiceContext");}@Overridepublic void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);if (beanType != null) {InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null);metadata.checkConfigMembers(beanDefinition);}}

InitDestroyAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

下面代码做了以下几件事:

  1. postProcessMergedBeanDefinition方法获取bean生命周期元数据findLifecycleMetadata负责具体的获取元数据的流程。
  2. findLifecycleMetadata方法先从lifecycleMetadataCache缓存中获取元数据,获取不到就掉用buildLifecycleMetadata构造元数据,我这里lifecycleMetadataCache缓存不为空,往下走,从缓存中根据Class获取,获取不到的话掉用buildLifecycleMetadata构造元数据并且放到缓存中,这里用到了双重检查+对象锁的方式来解决并发问题。
    if (method.getAnnotation(initAnnotationType) != null) {
  3. buildLifecycleMetadata方法构造bean生命周期元数据,这里用了do-while循环来构建LifecycleElement,跳出循环的触发条件是targetClass等于null或者是Object类,循环中的ReflectionUtils.doWithLocalMethods中的doWith回掉方法负责初始化LifecycleElementinitAnnotationTypedestroyAnnotationType分别在CommonAnnotationBeanPostProcessor的构造方法中初始化的@PostConstruct@PreDestroy注解,简单来说这里主要构造bean生命周期元数据并将@PostConstruct@PreDestroy注解修饰的方法记录到bean生命周期元数据中。
 @Overridepublic void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {if (beanType != null) {LifecycleMetadata metadata = findLifecycleMetadata(beanType);metadata.checkConfigMembers(beanDefinition);}}private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) {if (this.lifecycleMetadataCache == null) {// Happens after deserialization, during destruction...return buildLifecycleMetadata(clazz);}// Quick check on the concurrent map first, with minimal locking.LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz);if (metadata == null) {synchronized (this.lifecycleMetadataCache) {metadata = this.lifecycleMetadataCache.get(clazz);if (metadata == null) {metadata = buildLifecycleMetadata(clazz);this.lifecycleMetadataCache.put(clazz, metadata);}return metadata;}}return metadata;}private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {final boolean debug = logger.isDebugEnabled();LinkedList<LifecycleElement> initMethods = new LinkedList<LifecycleElement>();LinkedList<LifecycleElement> destroyMethods = new LinkedList<LifecycleElement>();Class<?> targetClass = clazz;do {final LinkedList<LifecycleElement> currInitMethods = new LinkedList<LifecycleElement>();final LinkedList<LifecycleElement> currDestroyMethods = new LinkedList<LifecycleElement>();ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {@Overridepublic void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {if (initAnnotationType != null) {if (method.getAnnotation(initAnnotationType) != null) {LifecycleElement element = new LifecycleElement(method);currInitMethods.add(element);if (debug) {logger.debug("Found init method on class [" + clazz.getName() + "]: " + method);}}}if (destroyAnnotationType != null) {if (method.getAnnotation(destroyAnnotationType) != null) {currDestroyMethods.add(new LifecycleElement(method));if (debug) {logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method);}}}}});initMethods.addAll(0, currInitMethods);destroyMethods.addAll(currDestroyMethods);targetClass = targetClass.getSuperclass();}while (targetClass != null && targetClass != Object.class);return new LifecycleMetadata(clazz, initMethods, destroyMethods);}

检查依赖注入

我的测试代码中注入了orderServiceorder1,创建PostConstructTest实例并构建了bean的生命周期元数据后进行bean的属性赋值和初始化,并且会检查依赖注入情况,发现依赖了orderServiceorder1会对其进行依赖注入。如果需要注入的bean没有被创建,会先创建和初始化需要被注入的bean,这里先不细说这部分,感兴趣的同学可以自行debug研究以下的元吗,下面的元吗省略了其他的代码,只保留了属性赋值的入口和bean初始化的入口。

try {省略...// 属性赋值populateBean(beanName, mbd, instanceWrapper);if (exposedObject != null) {// bean初始化exposedObject = initializeBean(beanName, exposedObject, mbd);}省略...}

PostConstructTest初始化完成掉用@PostConstruct注解修饰的方法

  • AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization
 @Overridepublic Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)throws BeansException {Object result = existingBean;for (BeanPostProcessor processor : getBeanPostProcessors()) {result = processor.postProcessBeforeInitialization(result, beanName);if (result == null) {return result;}}return result;}
  • InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization方法

这里通过CommonAnnotationBeanPostProcessor掉用到InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization方法,该方法主要的逻辑是通过bean的声明周期元数据LifecycleMetadatainvokeInitMethods方法来通过反射调用@PostConstruct@PreDestroy注解来修饰的方法。

 @Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());try {// 调用`@PostConstruct`和`@PreDestroy`注解来修饰的方法metadata.invokeInitMethods(bean, beanName);}catch (InvocationTargetException ex) {throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());}catch (Throwable ex) {throw new BeanCreationException(beanName, "Failed to invoke init method", ex);}return bean;}
  • LifecycleMetadata#invokeInitMethods方法

该方法主要就是遍历initMethodsToIterate反射调用方法。

     public void invokeInitMethods(Object target, String beanName) throws Throwable {Collection<LifecycleElement> initMethodsToIterate =(this.checkedInitMethods != null ? this.checkedInitMethods : this.initMethods);if (!initMethodsToIterate.isEmpty()) {boolean debug = logger.isDebugEnabled();for (LifecycleElement element : initMethodsToIterate) {if (debug) {logger.debug("Invoking init method on bean '" + beanName + "': " + element.getMethod());}// LifecycleElement elementelement.invoke(target);}}}
  • LifecycleElement#invoke方法

反射调用@PostConstruct@PreDestroy注解来修饰的方法。

     public void invoke(Object target) throws Throwable {ReflectionUtils.makeAccessible(this.method);this.method.invoke(target, (Object[]) null);}

总结

@PostConstruct注解的原理大致就分析完毕了,总结一下,在Spring容器刷新创建bean实例时会构建bean生命周期元数据,在元数据中会保存@PostConstruct@PreDestroy注解修饰的方法,然后在bean属性赋值阶段会进行bean的依赖注入检查如果依赖的bean没有被创建则会创建依赖的bean并进行依赖注入,最后在bean的初始化过程中会执行postProcessBeforeInitialization方法,该方法在bean初始化之前执行,postProcessBeforeInitialization该方法会通过bean的声明周期元数据通过反射进行方法调用,这就实现了@PostConstruct注解的原理,@PreDestroy@PostConstruct的原理类似这里就不过多介绍了。

Spring框架@PostConstruct注解详解相关推荐

  1. spring框架 AOP核心详解

    AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子. 一 AOP的基本概念 (1)Asp ...

  2. Spring Cache常用注解详解

    Spring Cache常用注解详解 @EnableCaching 开启Spring Cache框架支持.解析对应的注解,实现缓存读写访问 @CacheConfig 缓存配置,可以配置当前类型中所用缓 ...

  3. Spring的@PostConstruct标签详解

    @PostContruct是spring框架的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法. /*** 功能说明:启动项目 将字典放入缓存中 ...

  4. Spring框架面试题详解

    1. 什么是spring? Spring 是个java企业级应用的开源开发框架.Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用.Spring 框架目标是简化Jav ...

  5. Spring定时任务 - @Schedule注解详解

    一.@Schedule注解一览 @Scheduled注解共有8个属性(其中有3组只是不同类型的相同配置)和一个常量CRON_DISABLED,源码如下: /*** 标记要调度的方法的注释. 必须准确指 ...

  6. @PostConstruct注解详解

    简介 javaEE5引入了@PostConstruct和@PreDestroy两个作用于Servlet生命周期的注解,实现Bean初始化之前和销毁之前的自定义操作 使用场景 在项目中主要是在Servl ...

  7. Spring中@Value注解详解

    在spring项目中必不可少的就是读取配置文件,那么读取配置文件就有两种方式.一种就是使用Spring中@Value注解,还有一种是使用SpringBoot中的@ConfigurationProper ...

  8. spring mvc -@RequestMapping注解详解

    https://www.cnblogs.com/caoyc/p/5635173.html @RequestMapping参数说明: value:定义处理方法的请求的URL地址(重点): method: ...

  9. Spring MVC @RequestMapping注解详解

    @RequestMapping 参数说明 value:定义处理方法的请求的 URL 地址.(重点) method:定义处理方法的 http method 类型,如 GET.POST 等.(重点) pa ...

最新文章

  1. HDU 1086 You can Solve a Geometry Problem too
  2. 深度探索C++ 对象模型(1)-三种对象模型的设计
  3. 89c52单片机c语言延时程序计算 脉冲,stc89c52单片机的程序 求翻译
  4. http头部content-type与数据格式
  5. [转载]一个游戏程序员的学习资料
  6. Nginx For Windows 路由配置
  7. signature=c0c1b69f720d190a4a817d6bf2ff57c3,Fungicidal substituted N-(1-iodopropargyl)thiazolidinones
  8. Atitit.创业之uke团队规划策划 v9
  9. 第五十三篇 三角函数公式大全
  10. 程序员视角:鹿晗公布恋情是如何把微博搞炸的?
  11. OpenGL三维图形编程技术(转)
  12. 《Windows-Program:Win32/Contebrew.A!ml 病毒》
  13. 什么软件可以代替sc防火墙_车玻璃水的成份是什么?普通肥皂水和清水可以代替吗?...
  14. mysql如何启动_如何重启MySQL,正确启动MySQL
  15. E. Cashback
  16. 制药企业计算机系统urs,制药设备urs.pdf
  17. ​企业商城APP开发制作的3种方式各需要多少钱
  18. go语言自动化编写word
  19. 【单片机仿真项目】外部中断0和1控制两位数码管进行计数
  20. 给想学UG编程的人几点建议

热门文章

  1. vba正则表达式无效文件名过滤_VBA进阶 | 文件操作18:Folder对象与Folders集合详解...
  2. PTA 7-26 聪明的高斯
  3. 自己总结的redis面试题(4)
  4. GitHub:我沦为了美国制裁其他国家的政治工具
  5. 足球的永恒魅力- 观天下足球有感
  6. 判断苹果黑条_买iPhoneXS前必须知道的20件事
  7. 学习笔记——ECMAScript6
  8. shopify主题开发_在Photoshop中为手工商品设计Shopify主题
  9. 实用计算机快捷键,超实用电脑快捷键汇总
  10. python单片机编程软件下载_KRobot(IDE编程软件)