@PostConstruct注解使用

@PostConstruct注解使用PostConstruc PostConstruc 注解好多人以为是 Spring 提供的 其实是 Java 自己的注解 Java 中该注解的说明 PostConstruc 该注解被用来修饰一个非静态的 void 方法 被 PostConstruc 修饰的方法会在服务器加载 Servlet 的时候运行 并且只会被服务器执行一次 PostConstruc 在构造函数之后执行 init 方法之前执行 通常我们会是在 Spring 框架中使用到 PostConstruc 注解该注解的方法在整个 Bean 初始化

@PostConstruct

@PostConstruct注解好多人以为是Spring提供的。其实是Java自己的注解。

Java中该注解的说明:@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。

通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序:

Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)

应用:在静态方法中调用依赖注入的Bean中的方法。

 package com.example.studySpringBoot.util; import com.example.studySpringBoot.service.MyMethorClassService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @Component public class MyUtils { private static MyUtils staticInstance = new MyUtils(); @Autowired private MyMethorClassService myService; @PostConstruct public void init(){ staticInstance.myService = myService; } public static Integer invokeBean(){ return staticInstance.myService.add(10,20); } } 

知道了如何使用@PostConstruct以后,我们会产生疑问。为什么@PostConstruct注解的方法会在程序启动的时候执行呢?后续的内容将为你解开疑惑。

回顾spring中一个Bean的创建过程

在关注@PostConstruct原理之前,我们不得不先回顾一下spring中一个Bean是如何被创建的,这将有助于我们理清脉络。

配置Bean通常采用xml配置或者@Component、@Service、@Controller等注解配置,这是我们很熟悉的。这意味着Bean的创建过程第一步是配置Bean

配置Bean --> 

无论是xml配置,还是注解配置,都会执行解析处理,处理后的结果会变成BeanDefinition这样的对象,存储在Bean容器里面。我们可以把BeanDefinition理解为Bean的元数据。

所以,第二步就是将配置解析成Bean的元数据

配置Bean --> 解析为Bean的元数据 --> 

到这里,还只是Bean元数据,并不是我们最熟悉的Bean。所以,第三步就会根据Bean的元数据来创建Bean了。

这里注意了,触发某个Bean的创建,就是从Bean容器中第一次获取Bean的时候,也就是BeanFactory的getBean()方法。而不是解析了Bean元数据后就马上创建为Bean。

配置Bean --> 解析为Bean的元数据 --> 根据Bean的元数据生成Bean 

这样,我们就大体明白了一个Bean的创建过程。生成的Bean将会存放在Bean容器当中,或者我们称呼其为Bean工厂。

@PostConstruct原理

protected 
  
    T doGetBean( final String name, @Nullable final Class 
   
     requiredType, @Nullable final Object[] args, boolean typeCheckOnly ) throws BeansException { final String beanName = transformedBeanName(name); Object bean; Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { // ... } else { try { // ... // 创建Bean实例 if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // ... } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { // ... } else { // ... } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } // ... return (T) bean; } 
    
  

这里以创建单例Bean为例,我们注意到createBean方法将会创建一个Bean实例,所以createBean方法包含了创建一个Bean的核心逻辑。

再跟进createBean方法

protected Object createBean( String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // ... try { Object beanInstance = doCreateBean(beanName, mbdToUse, args); // ... return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { // ... } catch (Throwable ex) { // ... } } 

createBean委托给了doCreateBean处理

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; // ... // 创建Bean的实例对象 if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } // ... // 初始化一个Bean Object exposedObject = bean; try { // 处理Bean的注入 populateBean(beanName, mbd, instanceWrapper); // 处理Bean的初始化操作 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { // ... } // ... return exposedObject; } 

到这里,我们可以知道。BeanFactory的getBean()方法将会去创建Bean,在doCreateBean方法的创建逻辑中主要包含了三个核心逻辑:

1)创建一个Bean的实例对象,createBeanInstance方法执行

2)处理Bean之间的依赖注入,比如@Autowired注解注入的Bean。所以,populateBean方法将会先去处理注入的Bean,因此对于相互注入的Bean来说不用担心Bean的生成先后顺序问题。

3)Bean实例生成,相互注入以后。还需要对Bean进行一些初始化操作。比如我们@PostConstruct注解注释的方法,将再初始化的时候被解析并调用。当然还有一些Aware接口,@Schedule注解啥的也会做相应的处理。

我们继续跟进初始化过程,进入initializeBean方法

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { // ... Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { // 初始化前置处理 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { // 调用初始化方法 invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { // ... } if (mbd == null || !mbd.isSynthetic()) { // 初始化后置处理 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; } 

这里主要包含了初始化的前置、后置处理,以后初始化方法的调用。@PostConstruct注解将在applyBeanPostProcessorsBeforeInitialization这个前置处理

我们跟进applyBeanPostProcessorsBeforeInitialization前置方法

@Override public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; // 遍历所有的后置处理器 for (BeanPostProcessor processor : getBeanPostProcessors()) { // 调用初始化前置方法 Object current = processor.postProcessBeforeInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; } 

我们注意到,这里遍历了在spring启动过程中被注册的BeanPostProcessor接口,并调用其前置方法。

BeanPostProcessor接口被称作Bean的后置处理器接口,也就是说当一个Bean生成以后,会针对生成的Bean做一些处理。比如我们注解了@PostConstruct注解的Bean将会被其中一个BeanPostProcessor处理。或者一些@Schedule之类注解的Bean也会被处理,等一些所谓的后置处理操作。

到这里呢,我们意识到,原来@PostConstruct注解是会被一个专门的BeanPostProcessor接口的具体实现类来处理的。

我们找到该实现类:InitDestroyAnnotationBeanPostProcessor,根据名字我们就大体可以知道这个后置处理器是用于处理Bean的初始化方法注解和Bean的销毁方法注解的。这里,我们只关注@PostConstruct初始化注解相关的

我们跟进InitDestroyAnnotationBeanPostProcessor的postProcessBeanInitialization方法

@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // 元数据解析 LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); try { // 触发初始化方法 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; } 

findLifecycleMetadata方法将会解析元数据,所以@PostConstruct注解的初始化方法也会在这里被找到。

invokeInitMethods方法将会触发上一步被找到的方法。

其实,到这里我们大体都可以猜测这两个方法的逻辑了。就是通过反射将Method给找出来,然后再通过反射去调用这些method方法。

跟进findLifecycleMetadata方法看看初始化方法的查找过程吧

private LifecycleMetadata findLifecycleMetadata(Class 
   clazz) { // ... 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; } 

这里做了一个双重校验来控制缓存,我们更关心的是buildLifecycleMetadata这个构建方法

跟进方法,简单起见这里删除了destroy相关的部分

private LifecycleMetadata buildLifecycleMetadata(final Class 
   clazz) { List 
  
    initMethods = new ArrayList<>(); Class 
    targetClass = clazz; do { final List 
   
     currInitMethods = new ArrayList<>();// 变量类中的方法Method对象 ReflectionUtils.doWithLocalMethods(targetClass, method -> { // 判断是否被@PostConstruct注解注释 if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) { LifecycleElement element = new LifecycleElement(method); currInitMethods.add(element); } // ... }); // 添加到集合中,后续调用 initMethods.addAll(0, currInitMethods); // ... targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); return new LifecycleMetadata(clazz, initMethods, destroyMethods); } 
    
  

可以看到,doWithLocalMethods这个工具方法将会从class中获取方法的反射对象。而后判断该方法是否被被initAnnotationType指定的注释注解。最后,添加到initMethods集合当中供后续反射调用。

这里还向父类进行了递归处理,直到Object类为止。

我们看看initAnnotationType是一个什么注解

public CommonAnnotationBeanPostProcessor() { setOrder(Ordered.LOWEST_PRECEDENCE - 3); setInitAnnotationType(PostConstruct.class); setDestroyAnnotationType(PreDestroy.class); ignoreResourceType("javax.xml.ws.WebServiceContext"); } 

可以看到,@PostConstruct注解被设置为了initAnnotationType的值。值得注意的是,这是在CommonAnnotationBeanPostProcessor这个后置处理器的构造方法中执行的。

下面,我们再简单看看这些Method被调用的过程吧。

回到InitDestroyAnnotationBeanPostProcessor的postProcessBeforeInitialization方法

@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); try { 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; } 

这次我们关注invokeInitMethods方法

public void invokeInitMethods(Object target, String beanName) throws Throwable { Collection 
  
    checkedInitMethods = this.checkedInitMethods; Collection 
   
     initMethodsToIterate = (checkedInitMethods != null ? checkedInitMethods : this.initMethods); if (!initMethodsToIterate.isEmpty()) { for (LifecycleElement element : initMethodsToIterate) { if (logger.isTraceEnabled()) { logger.trace("Invoking init method on bean '" + beanName + "': " + element.getMethod()); } // 调用 element.invoke(target); } } } 
    
  

跟进invoke,单纯地invoke了method,是我们很熟悉的反射调用

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

总结

至此,本文就结束了。做一个简单的总结,本文内容包含三块:1)如何使用@PostConstruct;2)Bean创建过程简介;3)@PostConstruct的原理分析。

我们提出了一个问题:为什么@PostConstruct注解的方法会在启动的时候执行呢?

到这里大家应该能够知道答案了,spring的Bean在创建的时候会进行初始化,而初始化过程会解析出@PostConstruct注解的方法,并反射调用该方法。从而,在启动的时候该方法被执行了。

还有一个小点要注意,spring中的Bean默认是不会lazy-init的,所以在启动过程就会调用getBean方法。如果不希望该Bean在启动过程就调用,那么将lazy-init设置为true,它就会在程序第一次使用的时候进行初始化。

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

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

(0)
上一篇 2026年3月26日 下午5:02
下一篇 2026年3月26日 下午5:02


相关推荐

  • java xxe漏洞利用_XXE漏洞攻防原理

    java xxe漏洞利用_XXE漏洞攻防原理方便永远是安全的敌人你的知识面 决定你的攻击面 1 简述 XXE XMLExternalE 是指 xml 外部实体攻击漏洞 XML 外部实体攻击是针对解析 XML 输入的应用程序的一种攻击 当包含对外部实体的引用的 XML 输入被弱配置 XML 解析器处理时 就会发生这种攻击 这种攻击通过构造恶意内容 可导致读取任意文件 执行系统命令 探测内网端口 攻击内网网站等危害 而如今越来越多的 WEB 程序被发现和报告

    2026年3月19日
    2
  • Mysql自连接查询「建议收藏」

    Mysql自连接查询「建议收藏」自连接查询假想以下场景:某一电商网站想要对站内产品做层级分类,一个类别下面有若干子类,子类下面也会有别的子类。例如数码产品这个类别下面有笔记本,台式机,智能手机等;笔记本,台式机,智能手机又可以按照品牌分类;品牌又可以按照价格分类,等等。也许这些分类会达到一个很深的层次,呈现一种树状的结构。那么这些数据要怎么在数据库中表示呢?我们可以在数据库中创建两个字段来存储id和类别名称,使用第三个字段存

    2022年6月10日
    34
  • 项目运行指标:micrometer自定义metrics

    项目运行指标:micrometer自定义metricsmicrometer 自定义 metricsmicro 提供了基于 Java 的 monitorfacad 其与 springboot 应用和 prometheus 的集成方式如下图展示上图中展示的很清楚 应用通过 micrometer 采集和暴露监控端点给 prometheus prometheus 通过 pull 模式来采集监控时序数据信息 之后作为数据源提供给 grafana 进行展示 micrometer 支持的度量方式及在 springboot 中的应用示例 CounterCount 计数器 简单理解

    2025年7月17日
    4
  • 必须为元素类型 “mapper” 声明属性 “namespace” 或The content of element type “mapper” must match “EMPTY”

    必须为元素类型 “mapper” 声明属性 “namespace” 或The content of element type “mapper” must match “EMPTY”

    2021年7月19日
    107
  • postgreSql修改端口后psql命令行链接报错”/tmp/.s.PGSQL.5432“

    postgreSql修改端口后psql命令行链接报错”/tmp/.s.PGSQL.5432“今天修改pg的端口号port改成54328后重启完数据库的时候直接psql进库的时候进不去[postgres@iZ8vbifqgkwljcq9ccpkg7Zdata]$psqlpsql:couldnotconnecttoserver:NosuchfileordirectoryIstheserverrunninglocallyandacceptingconnectionsonUnixdomainsocket”/tmp/.s.PGSQL.5432这时,

    2022年6月19日
    61
  • js小脚本——判断小弹窗和用户浏览器类型

    js小脚本——判断小弹窗和用户浏览器类型

    2021年8月26日
    86

发表回复

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

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