在spring项目经常遇到@PostConstruct注解,首先介绍一下它的用途: 被注解的方法,在对象加载完依赖注入后执行。

此注解是在Java EE5规范中加入的,在Servlet生命周期中有一定作用,它通常都是一些初始化的操作,但初始化可能依赖于注入的其他组件,所以要等依赖全部加载完再执行。与之对应的还有@PreDestroy,在对象消亡之前执行,原理差不多,这里不做过多介绍。

那么首先看下源码注释

 
PostConstruct注释介绍

总体概括如上,注意其中几个点

1. 要在依赖加载后,对象使用前执行,而且只执行一次,原因在上面已经说了。

2. 所有支持依赖注入的类都要支持此方法。首先,我们可以看到这个注解是在javax.annotation包下的,也就是java拓展包定义的注解,并不是spring定义的,但至于为什么不在java包下,是因为java语言的元老们认为这个东西并不是java核心需要的工具,因此就放到扩展包里(javax中的x就是extension的意思),而spring是支持依赖注入的,因此spring必须要自己来实现@PostConstruct的功能。

3. 文档中说一个类只能有一个方法加此注解,但实际测试中,我在一个类中多个方法加了此注解,并没有报错,而且都执行了,我用的是springboot框架。

再往下看,这个注解有一些使用条件,挑一些重点的说一下

 
PostConstruct注释规则

1. 除了拦截器这个特殊情况以外,其他情况都不允许有参数,否则spring框架会报IllegalStateException;而且返回值要是void,但实际也可以有返回值,至少不会报错,只会忽略

2. 方法随便你用什么权限来修饰,public、protected、private都可以,反正功能是由反射来实现

3. 方法不可以是static的,但可以是final的

所以,综上所述,在spring项目中,在一个bean的初始化过程中,方法执行先后顺序为

Constructor > @Autowired > @PostConstruct

先执行完构造方法,再注入依赖,最后执行初始化操作,所以这个注解就避免了一些需要在构造方法里使用依赖组件的尴尬。

==========以上是对@PostConstruct的简单介绍,下面会从spring源码分析其具体实现原理==========

spring遵守了JSR-250标准,实现了javax.annotation包里面的各种注解功能,首先我们在GitHub下载spring-framework源码,我下的是5.0.x分支代码,导入到idea中,下面就开始动手分析。

首先代码中搜索"import javax.annotation.PostConstruct",庆幸的是只有CommonAnnotationBeanPostProcessor这一个类有引用PostConstruct类,看名字八九不离十就是它了,它是在org.springframework.context.annotation包下,大致介绍如下

 
CommonAnnotationBeanPostProcessor注释

看来没什么营养,只是一些简单介绍说明了我们在什么版本,基于什么标准,实现了这几个注解,那么看代码。

 
CommonAnnotationBeanPostProcessor构造方法

看来只有CommonAnnotationBeanPostProcessor的构造方法使用了这个注解,声明了这个BeanPostProcessor要支持PostConstruct初始化注解,跟进去setInitAnnotationType这个方法,是父类InitDestroyAnnotationBeanPostProcessor中的方法,只是简单的将PostConstruct.class赋值给成员变量initAnnotationType,那么谁去使用了这个变量,再次意外的发现,只有buildLifecycleMetadata一个方法使用了这个变量。

 
buildLifecycleMetadata方法

这个方法做的事情也很简单,输入一个类,检查它或者它的祖先类是否有初始化方法以及销毁方法,如果有,把这些信息封装成一个LifecycleMetadata类,里面大概信息就是类名、初始化和销毁方法列表,方便bean注册或消亡的时候去调用。

偶然看到LifecycleMetadata中初始化方法列表是List<LifecycleElement>,LifecycleElement类里面的构造方法有限制方法不能有参数,否则报错IllegalStateException,和前文测试结果对应上了。

 
LifecycleElement构造方法

这是题外话了,接着看buildLifecycleMetadata方法中while循环里,不断遍历父类,找PostConstruct注解,每找完一个父类,往initMethods中累加,最后注册到与这个bean相应的initMethods中。

前文说了 “我在一个类中多个方法加了此注解,并没有报错,而且都执行了”,看过上述代码后就知道了,spring根本没有按照javax的要求做限制,可能认为没必要吧。那么多个PostConstruct注解或父类也有此注解,他们是什么顺序执行的呢?

1. 首先父类的初始化方法是先于子类的先执行,但注意不要被子类方法重写,那父类初始化方法就不会执行了,因为中间有一步是用LinkedHashSet存了method的名字。

2. 同一类内,多个PostConstruct注解方法不是按声明顺序执行的,看了一下代码逻辑,虽然存储方法的集合都是有序集合,看起来应该可以顺序执行,但实际上是以一种非常诡异的顺序来执行,为了看一下spring的初始化过程,在application.properties中设置trace=true,在控制台看debug日志后发现,跟存储方法的集合没声明关系,最开始反射取方法的时候顺序就打乱了,罪魁祸首就是ReflectionUtils.doWithLocalMethods 这个方法啦!看了一下JDK的API,发现它强调了Class类不能保证getDeclaredMethods()的顺序,因为JVM有权在编译时,自行决定类成员的顺序。

好了,所以现在知道了buildLifecycleMetadata这个方法,就是将bean生命周期的元数据组装一下返回,在类中也只有下面一个方法调用了

 
findLifecycleMetadata方法

它把bean的LifecycleMetadata放到一个ConcurrentHashMap保存。【说实话第一次看到对ConcurrentHashMap这么加锁的,改日写一篇文章解析一下java中锁的应用以及ConcurrentHashMap吧】然后再往上找,就是AbstractAutowireCapableBeanFactory对bean的初始化和消亡操作了,在注册完之后就会invoke方法,这是另外一个话题了,此处不再过多介绍,所以本文到此为止。

综上,通过源码来学习还是很高效的嘛,主要是学习大神们的代码精髓。

spring框架中@PostConstruct的实现原理的更多相关文章

  1. Spring框架中IoC(控制反转)的原理(转)

    原文链接:Spring框架中IoC(控制反转)的原理 一.IoC的基础知识以及原理: 1.IoC理论的背景:在采用面向对象方法设计的软件系统中,底层实现都是由N个对象组成的,所有的对象通过彼此的合作, ...

  2. 细说shiro之五:在spring框架中集成shiro

    官网:https://shiro.apache.org/ 1. 下载在Maven项目中的依赖配置如下: <!-- shiro配置 --> <dependency> <gr ...

  3. 设计模式(二十一)——解释器模式(Spring 框架中SpelExpressionParser源码分析)

    1 四则运算问题 通过解释器模式来实现四则运算,如计算 a+b-c 的值,具体要求 1) 先输入表达式的形式,比如 a+b+c-d+e,  要求表达式的字母不能重复 2) 在分别输入 a ,b, c, ...

  4. Spring框架系列(13) - SpringMVC实现原理之DispatcherServlet的初始化过程

    前文我们有了IOC的源码基础以及SpringMVC的基础,我们便可以进一步深入理解SpringMVC主要实现原理,包含DispatcherServlet的初始化过程和DispatcherServlet ...

  5. Spring框架系列(14) - SpringMVC实现原理之DispatcherServlet处理请求的过程

    前文我们有了IOC的源码基础以及SpringMVC的基础,我们便可以进一步深入理解SpringMVC主要实现原理,包含DispatcherServlet的初始化过程和DispatcherServlet ...

  6. 再析在spring框架中解决多数据源的问题

    在前面我写了<如何在spring框架中解决多数据源的问题>,通过设计模式中的Decorator模式在spring框架中解决多数据源的问题,得到了许多网友的关注.在与网友探讨该问题的过程中, ...

  7. Spring框架中 配置c3p0连接池 完成对数据库的访问

    开发准备: 1.导入jar包: ioc基本jar jdbcTemplate基本jar c3p0基本jar 别忘了mysql数据库驱动jar 原始程序代码:不使用配置文件方式(IOC)生成访问数据库对象 ...

  8. Spring框架中ModelAndView、Model、ModelMap区别

    原文地址:http://www.cnblogs.com/google4y/p/3421017.html SPRING框架中ModelAndView.Model.ModelMap区别   注意:如果方法 ...

  9. Spring框架中的定时器 使用和配置

    Spring框架中的定时器 如何使用和配置 转载自:<Spring框架中的定时器 如何使用和配置>https://www.cnblogs.com/longqingyang/p/554543 ...

随机推荐

  1. C语言实现通用链表初步(二)

    接着上次的内容,我们继续! 还是无头单向非循环链表.假如要删除某个节点,如何实现? //删除成功返回0,失败返回-1 int slist_del(struct node_info *node, str ...

  2. Linux 下 zip 文件解压乱码解决方案,ubuntu16.10亲测可用

    文章来源: https://www.zhihu.com/question/20523036 今天邮件中收到了一个压缩文件,解压后却是乱码,从网上也找了几个方法,目前这个方法还是比较可靠的,如下所示: ...

  3. Hosted Services+Quartz实现定时任务调度

    背景 之前.net core使用quartz.net时,总感觉非常变扭,百度和谷歌了N久都没解决以下问题,造成代码丑陋,非常不优雅: 1.项目启动时,要立刻恢复执行quartz.net中的任务 2.q ...

  4. Controller的使用

  5. 缩小javascript文件大小之缩编、混淆

    写前端的相信都遇到过要提高网页的性能,其中javascript文件越小,浏览器的下载速度面对文件的读取和解析就更快.而一般我们在开发又需要一定的代码规范来使我们的代码更加的容易维护和读懂,但是大量空格 ...

  6. Struts2_总结

    还未学习的内容,如果到时候要用到,再去学.1.Lamda 表达式(很复杂,很少用)2.验证框架(默认验证方法 validation.方法开始前验证.开始后验证)3.UI标签(用的不多)4.类型转换中的 ...

  7. Can't connect to any repository.Read timed out after 30,000 ms

    解决方法:

  8. jstl Maven 依赖导致的 Jar 包冲突

    概述 Jar 包冲突是日常开发过程中,时常会遇到的问题.本文介绍由 jstl 的 Maven 依赖导致的 Jar 包冲突问题,以及对应的解决方法. jstl 的 Maven 依赖配置 <depe ...

  9. 使用QJM实现HDFS的HA配置

    使用QJM实现HDFS的HA配置 1.背景 hadoop 2.0.0之前,namenode存在单点故障问题(SPOF,single point of failure),如果主机或进程不可用时,整个集群 ...

  10. Element-ui安装与使用(网站快速成型工具)

    我之所以将Element归类为Vue.js,其主要原因是Element是(饿了么团队)基于MVVM框架Vue开源出来的一套前端ui组件.我最爱的就是它的布局容器!!! 下面进入正题: 1.Elemen ...