@PostConstruct注解是Spring提供的?今天讲点不一样的「建议收藏」

@PostConstruct注解是Spring提供的?今天讲点不一样的「建议收藏」前言我们在讲《Spring的Lifecycle》时提到,在Spring的使用中可以通过Lifecycle接口实现一些基于Spring容器生命周期逻辑。与此对照的就是通过@PostConstruct和@PreDestroy在Bean初始化或销毁时执行一些操作。很明显Spring的Lifecycle是基于容器的生命周期来处理逻辑,而@PostConstruct和@PreDestroy是基于Bean的生命周期来处理业务逻辑。这里很多朋友就产生了一个误解,以为@PostConstruct注解也是Spring提

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

前言

我们在讲《Spring的Lifecycle》时提到,在Spring的使用中可以通过Lifecycle接口实现一些基于Spring容器生命周期逻辑。与此对照的就是通过@PostConstruct和@PreDestroy在Bean初始化或销毁时执行一些操作。

很明显Spring的Lifecycle是基于容器的生命周期来处理逻辑,而@PostConstruct和@PreDestroy是基于Bean的生命周期来处理业务逻辑。

这里很多朋友就产生了一个误解,以为@PostConstruct注解也是Spring提供的。其实不然,它是Java自带的注解,下面我们就从头来聊聊@PostConstruct注解。

JSR-250规范

在了解@PostConstruct注解之前,我们先来科普一个概念:JSR-250规范。

JSR-250主要围绕着“资源”的使用预定义了一些注解(Annotation),这里的“资源”可以理解为一个Class类的实例、一个JavaBean、或者一个Spring中的Bean。

JSR-250相关的注解全部在javax.annotation和javax.annotation.security包中,包括:资源定义和权限控制。像我们经常用到的@Resource、@PostConstruct、@PreDestroy、@Generated等都属于这个规范中定义的注解。

该规范并没有提供具体的实现方式,仅仅是提供了指导性的文档和几个注解,由具体的框架去实现。

也就是说,@PostConstruct注解并不是Spring提供的注解,只不过Spring按照JSR-250规范实现了规范中对@PostConstruct的约定。而别的框架,或者你自己写一个框架,同样可以按照约定进行实现。

@PostConstruct的约定

@PostConstruct和@PreDestroy是在Java EE 5引入的,位于javax.annotation包下,也就是java拓展包定义的注解。其中,javax中的x就是extension的意思。Java最初的设计者认为,这些功能并不是Java核心API,因此就放到了扩展包中,谁用谁实现,按照约定就行。

下面直接看看该类上的注解说明:

“PostConstruct注释用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。此方法必须在将类放入服务之前调用。支持依赖关系注入的所有类都必须支持此注释。即使类没有请求注入任何资源,用PostConstruct注释的方法也必须被调用。只有一个方法可以用此注释进行注释。”

“应用PostConstruct注释的方法必须遵守以下所有标准:

  • 该方法不得有任何参数,除非是在EJB拦截器(interceptor)的情况下,它将带有一个InvocationContext对象;
  • 该方法的返回类型必须为void;
  • 该方法不得抛出已检查异常;
  • 应用PostConstruct的方法可以是public、protected、package private或private;
  • 除了应用程序客户端之外,该方法不能是static;
  • 该方法可以是final;
  • 如果该方法抛出未检查异常,那么不得将类放入服务中,除非是能够处理异常并可从中恢复的EJB。

除了上述约定,如果用在Servlet容器当中,还有有一定的处理时机。

@PostConstruct的执行时机

下面所讲的@PostConstruct的执行时机是基于Spring的实现来讲的。被@PostConstruct修饰的方法会在服务器加载Servlet时运行,并且只会被执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。

对应的流程图如下:
@PostConstruct

实例演示

理解了上面的基本概念,就先来看一个实例演示吧,使用起来非常简单。

基于Java 8的Spring Boot项目中添加如下类:

@Service
public class OrderService {

	public OrderService(){
		System.out.println("OrderService构造方法被执行...");
	}

	@PostConstruct
	private void init() {
		System.out.println("PostConstruct注解方法被调用");
	}

	@PreDestroy
	private void shutdown() {
		System.out.println("PreDestroy注解方法被调用");
	}

}

启动Spring Boot项目,控制台打印日志如下:

OrderService构造方法被执行...
PostConstruct注解方法被调用

当关闭服务时,会打印:

PreDestroy注解方法被调用

通过实例,基本印证了上述说的理论。

Java9的以后的移除

在Java 8中我们可以直接使用对应的注解即可,但到Java 9及以后,J2EE弃用了@PostConstruct和@PreDestroy这两个注解,并计划在Java 11中将其删除。

针对这种情况,我们有两种解决方案:第一添加额外的依赖;第二,换用其他的方式。

第一种方案针对的是,你非要使用这个注解,或者说你的项目暂时没办法弃用这两个注解。那么,可以手动添加依赖:

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

也就是说,虽然移除了,但是你把它们的依赖添加上,依旧还是可以用的。但此时也给我们提了一个醒儿,在项目中尽量别用这两个注解了,Java 11都计划将其移除了。

此时,如果你使用的是Spring的项目,则可考虑另外一种方式,基于Spring的InitializingBean和DisposableBean接口来实现同样的功能:

@Service
public class PaymentService implements InitializingBean, DisposableBean {

	public PaymentService(){
		System.out.println("PaymentService构造方法被执行...");
	}

	@Override
	public void destroy() throws Exception {
		System.out.println("destroy方法被调用");
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("afterPropertiesSet方法被调用");
	}
}

启动项目,打印日志如下:

PaymentService构造方法被执行...
afterPropertiesSet方法被调用

停止项目,打印如下信息:

destroy方法被调用

也就是说在Spring的生态中,我们已经有替代方案可实现了,而且是比较推荐的方式。

其实Spring并没有遵守约定

在上面的约定中我们讲到一个类中“只有一个方法可以用此注释进行注释”,在OrderService中再添加一个@PostConstruct注解的方法试试:

@Service
public class OrderService {

	public OrderService(){
		System.out.println("OrderService构造方法被执行...");
	}

	@PostConstruct
	private void init() {
		System.out.println("PostConstruct注解方法被调用");
	}

	@PostConstruct
	private void init1() {
		System.out.println("PostConstruct init1 注解方法被调用");
	}

	@PreDestroy
	private void shutdown() {
		System.out.println("PreDestroy注解方法被调用");
	}

}

启动程序,打印日志:

OrderService构造方法被执行...
PostConstruct init1 注解方法被调用
PostConstruct注解方法被调用

不但没报错,而且两个方法还都执行了。这说明什么?这说明约定有时候就是用来被打破的,记住这一特殊情况就好。

Spring的实现原理

以上是对@PostConstruct的简单介绍,下面会从Spring源码层面简单分析一下实现原理。

我们先来看一个Spring的接口BeanPostProcessor:

public interface BeanPostProcessor {
 
	// 任何Bean实例化,并且Bean已经populated(填充属性) 就会回调这个方法
	Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
 
	// 任何Bean实例化,并且Bean已经populated(填充属性) 就会回调这个方法
	Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

BeanPostProcessor是Spring IOC容器给我们提供的一个扩展接口,它两个回调方法。当一个BeanPostProcessor的实现类注册到Spring IOC容器后,对于该Spring IOC容器所创建的每个bean实例在初始化方法(如afterPropertiesSet和任意已声明的init方法)调用前,将会调用BeanPostProcessor中的postProcessBeforeInitialization方法,而在bean实例初始化方法调用完成后,则会调用BeanPostProcessor中的postProcessAfterInitialization方法,整个调用顺序可以简单示意如下:

--> Spring IOC容器实例化Bean
--> 调用BeanPostProcessor的postProcessBeforeInitialization方法
--> 调用bean实例的初始化方法
--> 调用BeanPostProcessor的postProcessAfterInitialization方法

而BeanPostProcessor有个实现类CommonAnnotationBeanPostProcessor,就是专门处理@PostConstruct和@PreDestroy注解。其中CommonAnnotationBeanPostProcessor的父类InitDestroyAnnotationBeanPostProcessor中,对应的调用逻辑如下:

InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization()
    InitDestroyAnnotationBeanPostProcessor.findLifecycleMetadata()
        // 组装生命周期元数据
        InitDestroyAnnotationBeanPostProcessor.buildLifecycleMetadata()
            // 查找@PostConstruct注释的方法
            InitDestroyAnnotationBeanPostProcessor.initAnnotationType
            // 查找@PreDestroy注释方法
            InitDestroyAnnotationBeanPostProcessor.destroyAnnotationType
 // 反射调用          
 metadata.invokeInitMethods(bean, beanName);    

关于业务逻辑的处理细节,这里就不再逐一展示,大家感兴趣的话可以跟踪一下源代码。

小结

本篇文章我们需要留意几点:第一,Spring只是实现了Java中对@PostConstruct注解定义的规范;第二,该注解在Java 9逐步开始废弃,不建议再使用;第三,可采用Spring的InitializingBean和DisposableBean来替代对应的功能。



程序新视界




公众号“
程序新视界”,一个让你软实力、硬技术同步提升的平台,提供海量资料

微信公众号:程序新视界

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

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

(0)
上一篇 2022年10月21日 上午7:36
下一篇 2022年10月21日 上午7:46


相关推荐

  • 矩阵分解 Cholesky分解

    矩阵分解 Cholesky分解Cholesky 分解法又叫平方根法 是求解对称正定线性方程组最常用的方法之一 对于一般矩阵 为了消除 LU 分解的局限性和误差的过分积累 采用了选主元的方法 但对于对称正定矩阵而言 选主元是不必要的 nbsp 定理 若对称正定 则存在一个对角元为正数的下三角矩阵 使得成立 nbsp 假设现在要求解线性方程组 其中为对称正定矩阵 那么可通过下面步骤求解 nbsp 1 求的

    2026年3月18日
    2
  • UE4导入高度图

    UE4导入高度图UE4导入高度图查找高度图使用ps编辑图片打开虚幻引擎的地形编辑器查找高度图可以从百度直接搜索使用ps编辑图片下载下来之后图片是jpg格式的,虚幻引擎不能直接用需要使用ps编辑一下选择图像->勾选灰度和16位通道,注意:如果不勾选灰度,图片有颜色的话,ue生成地形可能出现为题;还有ue只支持16位通道,不要选择其他通道点击文件和储存为,之后出现一个弹窗,填写自己图片的名字,注意图片的格式只能是png和raw,其他格式ue不支持打开虚幻引擎的地形编辑器1.ue4.25的地形在模式

    2022年5月26日
    54
  • 十进制小数如何转换为二进制小数[通俗易懂]

    十进制小数如何转换为二进制小数[通俗易懂]关于十进制小数转换为二进制数,下面是我的详细操作说明,仅供参考。纯小数首先,最高位是符号位,正数是0,负数是1;小数部分乘以2,然后取整数部分,,剩余小数部分继续乘以2,取整数部分,……直到小数部分为0。以+0.125为例:+数,最高位为0;小数部分0.125×2=0.25,取0;再取小数部分0.25×2=0.5,取0;再取小数部分0.5×2=1.0,取1;这时小数部分是0,结束。…

    2026年3月8日
    11
  • 前端vue面试题2021_vue框架面试题

    前端vue面试题2021_vue框架面试题一.自我介绍(我是谁来自哪里,今天来的目的,面试的岗位是什么,几年的工作经验,掌握的技术栈有哪些,开发过什么项目,项目中负责的板块是什么)面试官您好!我叫XXX,来自XXX,很荣幸能来我们公司面试,我从事前端开发有3年了,目前掌握的技术有html,css,js,ajax,vue,小程序,参与过各种类型的项目。我做过的项目有A,B,C,D,E那么最近做的一个项目是XXX在这个项目中我主要负责的板块是XXX面试官您这边还有什么想要了解的么。二.项目功能提问vue后台项目(这几个功能点要求

    2022年8月28日
    4
  • 图形推理题 答案揭晓图_图形推理1000题

    图形推理题 答案揭晓图_图形推理1000题本周一,小灰给大家出了一道图形推理题,有许多小伙伴都在踊跃作答,非常感谢大家的积极参与!原题如下,需要根据前两行的图形规律,找出第三行最后一个图形是什么:这道题的难度不小…

    2025年10月12日
    5
  • SpringBoot 整合 Kafka

    SpringBoot 整合 Kafka环境 ZooKeeper Kafka 如果使用 docker compose 部署 Kafka 集群 可以参考我的这篇文章 docker compose 安装 Kafka 集群依赖 pom xml 依赖文件如下 springboot 版本是 2 3 0 RELEASE dependencies dependency groupId org springframew boot groupId lt dependency dependencies

    2026年3月20日
    2

发表回复

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

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