Spring - @PostConstruct 源码解析

简介: @PostConstruct 源码解析

@[toc]

在这里插入图片描述


Pre

Spring Boot - 扩展接口一览

在这里插入图片描述


javax.annotation.PostConstruct 注解

@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PostConstruct {
}

严格意义上来说这个并不算一个扩展点,其实就是一个标注。

其作用是在bean的初始化阶段,如果对一个方法标注了@PostConstruct,会先调用这个方法。

触发时机是在postProcessBeforeInitialization之后InitializingBean.afterPropertiesSet之前。


源码解析

带着这个疑问: 为什么@PostConstruct注解的方法会在程序启动的时候执行?

源码面前,了无秘密。

结合对Spring生命周期的理解, bean的创建过程,我们可以推测@PostConstruct方法将在最后生成Bean的时候被调用。

org.springframework.context.support.PostProcessorRegistrationDelegate#registerBeanPostProcessors()
    BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
        org.springframework.beans.factory.support.AbstractBeanFactory#getBean
          org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean    
              org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean
                    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
                         org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean 

在这里插入图片描述

我们从 AbstractBeanFactory#doGetBean 开始看

    protected <T> T doGetBean(
            String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
            throws BeansException {

    final String beanName = transformedBeanName(name);

    Object bean;

    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        // ...
    } else {
        try {
            // ...

            // 单例 
            if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, () -> {
                    try {
                        // 创建Bean的实例
                        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;
}

createBean包含了创建一个Bean的核心逻辑,继续跟进createBean方法

@Override
    protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
            throws BeanCreationException {

         try {    
                // 委托给了doCreateBean处理
                Object beanInstance = doCreateBean(beanName, mbdToUse, args);
                // ...
                return beanInstance;
            } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
                // ...
            } catch (Throwable ex) {
                // ...
            }
             
    }

继续 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做一些处理。

比如注解了@PostConstruct注解的Bean将会被其中一个BeanPostProcessor处理。或者一些@Schedule之类注解的Bean也会被处理,等一些所谓的后置处理操作。

在这里插入图片描述

@PostConstruct注解是会被一个专门的BeanPostProcessor接口的具体实现类来处理的 InitDestroyAnnotationBeanPostProcessor

在这里插入图片描述

进入到进InitDestroyAnnotationBeanPostProcessorpostProcessBeanInitialization方法

@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方法,那我们去验证下

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这个构建方法即可

private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {
    List<LifecycleElement> initMethods = new ArrayList<>();
    Class<?> targetClass = clazz;

    do {
        final List<LifecycleElement> 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);
}

这里包含了currInitMethodscurrDestroyMethods 方法

doWithLocalMethods这个工具方法将会从class中获取方法的反射对象。而后判断该方法是否被被initAnnotationType指定的注释注解。最后,添加到initMethods集合当中供后续反射调用。这里还向父类进行了递归处理,直到Object类为止。

看看 initAnnotationType

在这里插入图片描述

    /**
     * Create a new CommonAnnotationBeanPostProcessor,
     * with the init and destroy annotation types set to
     * {@link javax.annotation.PostConstruct} and {@link javax.annotation.PreDestroy},
     * respectively.
     */
    public CommonAnnotationBeanPostProcessor() {
        setOrder(Ordered.LOWEST_PRECEDENCE - 3);
        setInitAnnotationType(PostConstruct.class);
        setDestroyAnnotationType(PreDestroy.class);
        ignoreResourceType("javax.xml.ws.WebServiceContext");
    }

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

在这里插入图片描述

我们可以看到解析过程是通过反射来获取@PostConstruct注解的方法,并放到一个List集合里面去。 那继续看看这些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<LifecycleElement> checkedInitMethods = this.checkedInitMethods;
            Collection<LifecycleElement> 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);
                }
            }
        }

继续

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

继续跟 就已经到JDK了

@CallerSensitive
    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }

小结一下: spring的Bean在创建的时候会进行初始化,而初始化过程会解析出@PostConstruct注解的方法,并反射调用该方法。

在这里插入图片描述


扩展示例

package com.artisan.bootspringextend.testextends;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author 小工匠
 * @version 1.0
 * @description: TODO
 * @date 2022/12/4 22:46
 * @mark: show me the code , change the world
 */

@Component
@Slf4j
public class TestPostConstruct {


    public TestPostConstruct(){
        log.info(">>> TestPostConstruct no arg cons called");
    }

    @PostConstruct
    public void doSomething() {
        log.info(">>> TestPostConstruct doSomething");
    }
}
    

输出结果

2022-12-04 22:48:57.994  INFO 30472 --- [           main] c.a.b.testextends.TestPostConstruct      : >>> TestPostConstruct no arg cons called
2022-12-04 22:55:00.920  INFO 30472 --- [           main] c.a.b.testextends.TestPostConstruct      : >>> TestPostConstruct doSomething

在这里插入图片描述

相关文章
|
12月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
2884 1
|
11月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
633 70
|
7月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1148 29
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
484 4
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
467 2
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。

热门文章

最新文章

推荐镜像

更多
  • DNS