java postconstruct_spring框架中@PostConstruct的实现原理

java postconstruct_spring框架中@PostConstruct的实现原理在 spring 项目经常遇到 PostConstruc 注解 首先介绍一下它的用途 被注解的方法 在对象加载完依赖注入后执行 此注解是在 JavaEE5 规范中加入的 在 Servlet 生命周期中有一定作用 它通常都是一些初始化的操作 但初始化可能依赖于注入的其他组件 所以要等依赖全部加载完再执行 与之对应的还有 PreDestroy 在对象消亡之前执行 原理差不多 这里不做过多介绍 那么首先看下源码

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

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

那么首先看下源码注释

format,png

PostConstruct注释介绍

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

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

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

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

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

format,png

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包下,大致介绍如下

format,png

CommonAnnotationBeanPostProcessor注释

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

format,png

CommonAnnotationBeanPostProcessor构造方法

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

format,png

buildLifecycleMetadata方法

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

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

format,png

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生命周期的元数据组装一下返回,在类中也只有下面一个方法调用了

format,png

findLifecycleMetadata方法

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/178668.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午4:45
下一篇 2026年3月26日 下午4:45


相关推荐

  • 易语言画板组件实现自绘圆形进度条源码

    易语言画板组件实现自绘圆形进度条源码进度条是易语言常用的一个组件,有时候我们想让进度有个性,比如绘制一个圆形进度条,下面封装了子程序,直接调用就可以了。窗口用到三个编辑框,一个时钟,一个标签,一个画板,三个颜色选择器,效果如下图,我们可以灵活调整双环椭圆大小,颜色等等。一、源码:.版本2.子程序画圆进度条,整数型.参数画板,画板,,画板名称.参数最小位置,双精度小数型,,进度条的最小初始值.参数当前位置,双精度小数型,,返回当前的位置.参数最大位置,双精度小数型,,进度条的最大位置

    2022年7月26日
    5
  • javaScript — touch事件详解(touchstart、touchmove和touchend)

    HTML5中新添加了很多事件,但是由于他们的兼容问题不是很理想,应用实战性不是太强,所以在这里基本省略,咱们只分享应用广泛兼容不错的事件,日后随着兼容情况提升以后再陆续添加分享。今天为大家介绍的事件主要是触摸事件:touchstart、touchmove和touchend。一开始触摸事件touchstart、touchmove和touchend是iOS版Safari浏览器为了向开发人员传达一些信息新

    2022年4月9日
    49
  • MySQL递归查询 三种实现方式

    MySQL递归查询 三种实现方式我是以山东济南的行政区划作为示例的,数据库是MySQL话不多说,直接上示例代码!感觉阅读麻烦的伙伴可以直接下载资源:点我下载1.建表脚本1.1.建表DROPTABLEIFEXISTS`sys_region`;CREATETABLE`sys_region`(`id`int(50)NOTNULLAUTO_INCREMENTCOMMENT’地区主键编号’,`name`varchar(50)CHARACTERSETutf8COLLATEut

    2022年7月15日
    43
  • Java8 ConcurrentHashMap详解

    Java8 ConcurrentHashMap详解Java8ConcurrentHashMapJava7中实现的ConcurrentHashMap说实话还是比较复杂的,Java8对ConcurrentHashMap进行了比较大的改动。建议读者可以参考Java8中HashMap相对于Java7HashMap的改动,对于ConcurrentHashMap,Java8也引入了红黑树。说实话,Java8Concurrent

    2022年6月24日
    23
  • json_decode的结果是null

    json_decode的结果是null一、前言      突然发现一个接口出了问题,经过排查之后发现是json_decode($str,true)的问题,返回竟然是null。这个问题大家可能都碰到过,出现问题的原因就那么几种,再次记录一下吧二、原因1、首先使用json_last_error确定问题$arrDataList=json_decode($content…

    2022年7月17日
    21
  • 工作多年想转行,有哪些正确的方法及技巧呢

    工作多年想转行,有哪些正确的方法及技巧呢

    2022年2月14日
    66

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号