品Spring:对@PostConstruct和@PreDestroy注解的处理方法
在bean的实例化过程中,也会用到一系列的相关注解。
如@PostConstruct和@PreDestroy用来标记初始化和销毁方法。
平常更多的是侧重于应用,很少会有人去了解它背后发生的事情。
今天就来看下它们的源码,这样它们对你来说就不再是黑盒子了,而且学习源码对每个技术人来说都是必经之路。
人们对事物的认知以及自己的做法,往往分为三个阶段:
1)最初看一个事物,非常复杂,简直没有一点头绪,此时很多人就会放弃。
2)过了一段时间后,发现整体来看没有想象中的那么难,此时很多人以为自己已经get到了,然后就停止不前。
3)随着了解的深入,发现很多细节的处理,很多方方面面的考虑是自己无法想到的,此时才会有一点点的东西转化为你的智慧,永远的属于你。只可惜能够到达这一步的人已经凤毛麟角了。
因此能够到达第3步的人就已经很少了,如果你恰巧到达了,那么需要做的就只剩下坚持了。
坚持了足够的时间之后,你付出的努力对你付出的时间的累积效果(类似于高数中的积分)就会非常明显,此时你已经站在了一个相对较高的位置。可以体会下“会当凌绝顶,一览众山小”。
学习源码就是要做到第2点和第3点。既要知道宏观的处理进程,也要知道一部分处理细节。
而且宏观过程很容易理解,很多时候猜都能猜出来,难就难在细节处理上。
因为宏观对应的是说,细节对应的是做,用嘴说说谁都会,一旦做起来就不是那么回事了。
细节处理之所以难,是因为没有找到一种很好的表述方法。
一个很重要但从不被重视的观点:
要解决一个问题,首先要找到这个问题,并把它描述(表示)出来。
编程新说注:很多时候我们看到的只是现象,隐藏在现象背后的才是问题。
我想表达,其实如何把一个事物准确而又良好的描述出来,是很重要的。
在编程的世界里,描述事物很大程度上对应于广义的数据结构。
一、注解的描述
使用两个Class<? extends Annotation>字段存储两个注解,通过setter方法设置进来,而不是写死。如下图0102:
这样做的好处是,如果你看不上Java提供的这两个注解,完全可以自己定义两个,把它们替换掉,效果完全一样。
二、方法的描述
我们都能想到使用反射在一个类里面找出所有标有注解的方法,就是Method类型的一个对象。
Spring也是这样做的,但是它考虑的更加细节化。如下图0304:
把Method保存起来不足为奇,而且还要求方法的参数个数必须为0,即没有参数。
还有一个String类型的identifier字段,这个单词是标识符的意思。
看到下面对它的赋值,如果是私有方法,则是方法的全名(包括类名),否则是方法的简单名(不包括类名)。
看到这里,一定会觉得很奇怪,先卖个关子,到后面再说明为什么。
还重写了hashCode和equals方法,也是依赖identifier字段来实现的。说明这个字段非常重要。
还说明可能会把这个类的对象作为Map的key使用,或往Set集合中添加,其实都是在比较两个对象是否相等。
三、类和方法的对应关系描述
其实我们需要知道的是每个类中的相应方法,所以要和类关联起来。如下图05:
我们可以看到有两组字段表示初始化和销毁方法,一组是Collection类型,一组是Set类型。
Set类型是不能重复的,可以是无序的如HashSet,也可以是有序的如LinkedHashSet。
Collection类型是比较宽泛的接口,还可以有重复元素。
再看下构造方法,使用的是Collection类型,说明传进来的初始化或销毁方法存在重复或冗余,需要进行一些处理,然后变为Set类型,把冗余的过滤掉。
四、通过反射找出标有注解的方法
如下图06:
整体思路并不难,这里主要看几个细节问题。
这里提到了LocalMethods(即本地方法),指的是一个类自己声明的方法,还有它实现的接口里的默认方法。
就是从这些方法里找出标有初始化和销毁注解的方法。
然后再从这个类的父类里面按照相同的方式找出父类中的这些方法。接着再找父类的父类。
说明这里是支持继承的,如C继承B,B继承A,那么C、B、A中的注解方法都会被找出来。
这就是带来一个问题,即顺序该如何处理,是父类的在前还是子类的在前?
如果对面向对象比较熟悉的话,就会知道初始化属于类的构造,销毁属于类的析构。
可以再进一步,把构造看作是构造方法,把析构看作是析构方法。所以其实就是构造方法和析构方法的调用顺序了。
对于构造方法是父类的先执行,因为子类依赖父类,父类不构建好,子类无法构建。
对于析构方法是子类的先执行,同样是因为子类依赖父类,如果父类先销毁,子类的依赖就不存在了,它怎么可能存活。
可以看出调用顺序正好相反。可以把父类比作房子的一楼,子类比作房子的二楼,子类依赖父类,相当于二楼依赖一楼。
盖楼时先一楼再二楼,对应于构造方法的调用顺序。拆楼时先二楼再一楼,对应于析构方法的调用顺序。
因此,标有初始化注解的方法是父类的方法在前面,子类的方法在后面。标有销毁注解的方法正好倒过来,子类的在前,父类的在后。
构造方法和析构方法是不能被继承和重写的,但是标有注解的方法是可以被继承(只要不是private)或重写(只要不是private/final)的。
这就表明它们之间肯定会有些不同,上面提到的identifier字段,和依赖它实现hashCode和equals方法,就是为了解决这个问题的。
看下它的值,非常特殊:
对于私有方法,identifier字段的值就是方法全名,因为私有方法不能被继续和重写,子类里和父类里定义的同名私有方法,也是不同的两个方法。
所以它们不能互相覆盖,必须全部保留,因此用方法的全名,全名肯定是不同的,所以hashCode也不同。
对于非私有方法(一般是公共和受保护的),identifier字段的值就是方法的简单名,因为非私有方法可以被继续和重写。
子类里和父类里定义的同名非私有方法,虽然也是不同的两个方法,但是它们以反射的方式在子类对象上调用时产生的结果是一样的,都等同于调用子类上的方法。
所以此时只需保留一个就可以了,使用方法的简单名,因为是一样的,所以通过Set时就可以过滤掉一个,实际保留的是父类的,过滤掉的是子类的。
现在我们就明白了Spring设置identifier字段的真正用意了。
编程新说注:父类中的Method对象,可以在子类的实例上invoke,得到的结果就是子类重写方法后的结果。
五、对找出来的注解方法进行检查
如下图07:
看到把这些方法逐个添加到Set里面,按照定义好的规则进行过滤。
最终的结果就是,私有同名方法都会被保留,非私有同名方法只会保留一个,由于顺序的原因,初始化方法保留的是父类中的,销毁方法保留的是子类中的。
六、通过反射来调用它们
当然,在框架开发中,使用反射来调用是很正常的事情。
如下图0809:
到目前为止,我们已经了解了Spring对初始化和销毁方法的处理逻辑。包括方法的表示,如何找出这些方法,方法的过滤去重与排序问题,以及方法的反射调用。
还有最后一个问题,就是这些处理要和bean后处理器的方法结合起来。
七、使用bean后处理器决定调用时机
共涉及到3个方法,
第一个,postProcessMergedBeanDefinition,如下图10:
该方法虽然与合并后的bean定义相关,但却不是用来处理bean定义的。
它一般用来做一些自我检测的操作或准备和缓存一些相关的元数据的操作。
在这里就是把所有的初始化和销毁方法都找出来,整理好并缓存起来备用。其实这些就相当于元数据的处理。
第二个,postProcessBeforeInitialization,如下图11:
在这里完成对所有初始化方法的按顺序调用。
第三个,postProcessBeforeDestruction,如下图12:
在这里完成对所有销毁方法的按顺序调用。
编程新说注:
本文介绍的都是@PostConstruct和@PreDestroy这两个注解标注的方法。
除此之外还有其它方式也可以指定初始化和销毁方法,上一篇文章中已写明。
你还发现,这两个注解可以标在多个方法上,还可以标在父类里,且不受访问控制符影响,因为私有方法也可以标。
而且当父类和子类中都有时,还知道了它们的调用顺序。
以上这些都是细节问题,即使看官方文档,都不会写的这么详细。这就是学习源码的好处。细节之处见真知。
最后用一句话共勉你我他:
很多事情,听是一回事,看是一回事,说是一回事,做则是另一回事。
>>> 品Spring系列文章 <<<
品Spring:SpringBoot和Spring到底有没有本质的不同?
品Spring:SpringBoot轻松取胜bean定义注册的“第一阶段”
品Spring:SpringBoot发起bean定义注册的“二次攻坚战”
品Spring:注解之王@Configuration和它的一众“小弟们”
>>> 热门文章集锦 <<<
爸爸又给Spring MVC生了个弟弟叫Spring WebFlux
【面试】吃透了这些Redis知识点,面试官一定觉得你很NB(干货 | 建议珍藏)
【面试】如果你这样回答“什么是线程安全”,面试官都会对你刮目相看(建议珍藏)
【面试】迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO讲的这么清楚的好文章(快快珍藏)
【面试】一篇文章帮你彻底搞清楚“I/O多路复用”和“异步I/O”的前世今生(深度好文,建议珍藏)
作者是工作超过10年的码农,现在任架构师。喜欢研究技术,崇尚简单快乐。追求以通俗易懂的语言解说技术,希望所有的读者都能看懂并记住。下面是公众号和知识星球的二维码,欢迎关注!
品Spring:对@PostConstruct和@PreDestroy注解的处理方法的更多相关文章
- @PostConstruct和@PreDestroy注解
从Java EE5规范开始,Servlet增加了两个影响Servlet生命周期的注解(Annotation):@PostConstruct和@PreConstruct.这两个注解被用来修饰一个非静态的 ...
- Java开发之@PostConstruct和@PreDestroy注解
从Java EE5规范开始,Servlet增加了两个影响Servlet生命周期的注解(Annotation):@PostConstruct和@PreConstruct.这两个注解被用来修饰一个非静态的 ...
- Spring@PostConstruct和@PreDestroy注解详解
@PostConstruct注解使用 @PostConstructApi使用说明 The PostConstruct annotation is used on a method that needs ...
- 品Spring:能工巧匠们对注解的“加持”
问题的描述与方案的提出 在Spring从XML转向注解时,为了自身的开发方便,对注解含义进行了扩充(具体参考本号上一篇文章). 这个扩充直接导致了一个问题,就是需要从注解往元注解以及元元注解(即沿着从 ...
- 品Spring:对@Resource注解的处理方法
@Resource是Java的注解,表示一个资源,它具有双向的含义,一个是从外部获取一个资源,一个是向外部提供一个资源. 这其实就对应于Spring的注入和注册.当它用在字段和方法上时,表示前者.当它 ...
- 品Spring:对@Autowired和@Value注解的处理方法
在Spring中能够完成依赖注入的注解有JavaSE提供的@Resource注解,就是上一篇文章介绍的. 还有JavaEE提供的@javax.inject.Inject注解,这个用的很少,因为一般都不 ...
- 品Spring:真没想到,三十步才能完成一个bean实例的创建
在容器启动快完成时,会把所有的单例bean进行实例化,也可以叫做预先实例化. 这样做的好处之一是,可以及早地发现问题,及早的抛出异常,及早地解决掉. 本文就来看下整个的实例化过程.其实还是比较繁琐的. ...
- 品Spring:关于@Scheduled定时任务的思考与探索,结果尴尬了
非Spring风格的代码与Spring的结合 现在的开发都是基于Spring的,所有的依赖都有Spring管理,这没有问题. 但是要突然写一些非Spring风格的代码时,可能会很不习惯,如果还要和Sp ...
- 【spring源码学习】Spring @PostConstruct和@PreDestroy实例
在Spring中,既可以实现InitializingBean和DisposableBean接口或在bean配置文件中指定 init-method 和 destroy-method 在初始化和销毁回调函 ...
随机推荐
- matplotlib 库的使用
1.问题描述: 在学习kaggle经典学习项目Titanic,进行数据可视化处理时,对于每个特征进行相关性分析(也就是绘制pearson correlation heatmap )热力相关性矩阵时, ...
- UVA - 10480 Sabotage 最小割,输出割法
UVA - 10480 Sabotage 题意:现在有n个城市,m条路,现在要把整个图分成2部分,编号1,2的城市分成在一部分中,拆开每条路都需要花费,现在问达成目标的花费最少要隔开那几条路. 题解: ...
- JavaScript数组方法速查,32个数组的常用方法和属性
JavaScript数组方法速查手册极简版 http://30ke.cn/doc/js-array-method JavaScript数组方法速查手册极简版中共收了32个数组的常用方法和属性,并根据方 ...
- webpack4 公共模块打包,怎么抽取出来一个需要经常修改的包
项目中有一个需求: 所有页面引用了一个公共ad.js 因为广告需要不断投放新渠道,所以ad.js需要经常上线更新,这样会导致打包出来的 commons.js经常更新,缓存一下无效了.所以急需将ad.j ...
- Linux OOM-killer(内存不足时kill高内存进程的策略)
OOM_killer是Linux自我保护的方式,当内存不足时不至于出现太严重问题,有点壮士断腕的意味 在kernel 2.6,内存不足将唤醒oom_killer,挑出/proc/<pid> ...
- redis之pipeline使用
redis之pipeline 我们要完成一个业务,可能会对redis做连续的多个操作,这有很多个步骤是需要依次连续执行的.这样的场景,网络传输的耗时将是限制redis处理量的主要瓶颈. 那么此时就可以 ...
- Python中使用高德API实现经纬度转地名
场景 高德API提供给开发者们一些常用功能的接口,其中有一种叫地理/逆地理编码能实现 地名查询经纬度和经纬度查地名. 实现 高德API平台: https://lbs.amap.com/ 注册并登陆 找 ...
- Centos7上安装jdk8
CentOS7 下安装jdk8环境 1 检查服务器环境 首先,我们需要检查一下服务器是否安装过java环境,可以使用如下命令: java -version 如果已经安装有java环境,会出现类似于以下 ...
- [大数据学习研究] 4. Zookeeper-分布式服务的协同管理神器
本来这一节想写Hadoop的分布式高可用环境的搭建,写到一半,发现还是有必要先介绍一下ZooKeeper这个东西. ZooKeeper理念介绍 ZooKeeper是为分布式应用来提供协同服务的,而且Z ...
- 磁盘告警之---神奇的魔法(Sparse file)
一.问题来源 半夜钉钉接到告警,某台机器的磁盘使用率少于20%,于是迷糊中爬起来,咔咔咔 find / -size +1G,咔咔咔,把几个只有4-5G的日志文件echo空值了一下,然后吓蒙了,刚刚 ...