您现在的位置是:首页 > 正文

Spring Bean 初始化之InitializingBean, init-method 和 PostConstruct

2024-02-29 13:50:16阅读 3

概述

从接口的名字上不难发现,InitializingBean 的作用就是在 bean 初始化后执行定制化的操作。

Spring 容器中的 Bean 是有生命周期的,Spring 允许在 Bean 在初始化完成后以及 Bean 销毁前执行特定的操作,常用的设定方式有以下三种:

  • 通过实现 InitializingBean/DisposableBean 接口来定制初始化之后/销毁之前的操作方法;
  • 通过 <bean> 元素的 init-method/destroy-method 属性指定初始化之后 /销毁之前调用的操作方法;
  • 在指定方法上加上@PostConstruct 或@PreDestroy注解来制定该方法是在初始化之后还是销毁之前调用。

注:以下源码分析基于spring 5.0.4

InitializingBean vs init-method

接口定义如下:

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

接口只有一个方法afterPropertiesSet,此方法的调用入口是负责加载 spring bean 的AbstractAutowireCapableBeanFactory,源码如下:

    protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
            throws Throwable {

        boolean isInitializingBean = (bean instanceof InitializingBean);
        if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
            if (logger.isDebugEnabled()) {
                logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
            }
            if (System.getSecurityManager() != null) {
                try {
                    AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
                        ((InitializingBean) bean).afterPropertiesSet();
                        return null;
                    }, getAccessControlContext());
                }
                catch (PrivilegedActionException pae) {
                    throw pae.getException();
                }
            }
            else {
                ((InitializingBean) bean).afterPropertiesSet();
            }
        }

从这段源码可以得出以下结论:

  1. spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中通过init-method指定,两种方式可以同时使用
  2. 实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率相对来说要高点。但是init-method方式消除了对spring的依赖
  3. 先调用afterPropertiesSet,再执行 init-method 方法,如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。

@PostConstruct

通过 debug 和调用栈找到类InitDestroyAnnotationBeanPostProcessor, 其中的核心方法,即 @PostConstruct 方法调用的入口:

    @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;
    }

从命名上,我们就可以得到某些信息——这是一个BeanPostProcessor。想到了什么?在也谈Spring容器的生命周期中,提到过BeanPostProcessor的postProcessBeforeInitialization是在Bean生命周期中afterPropertiesSet和init-method之前被调用的。另外通过跟踪,@PostConstruct方法的调用方式也是通过发射机制。

总结

  1. spring bean的初始化执行顺序:构造方法 --> @PostConstruct注解的方法 --> afterPropertiesSet方法 --> init-method指定的方法。具体可以参考例子
  2. afterPropertiesSet通过接口实现方式调用(效率上高一点),@PostConstructinit-method都是通过反射机制调用

例子

直接执行单测com.skyarthur.springboot.common.bean.InitSequenceBeanTest, 请戳代码下载地址

核心代码如下:

@Slf4j
public class InitSequenceBean implements InitializingBean {

    public InitSequenceBean() {
        log.info("InitSequenceBean: construct");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("InitSequenceBean: afterPropertiesSet");
    }

    @PostConstruct
    public void postConstruct() {
        log.info("InitSequenceBean: postConstruct");
    }

    public void initMethod() {
        log.info("InitSequenceBean: initMethod");
    }
}

@Configuration
public class SystemConfig {

    @Bean(initMethod = "initMethod", name = "initSequenceBean")
    public InitSequenceBean initSequenceBean() {
        return new InitSequenceBean();
    }
}

@Slf4j
public class InitSequenceBeanTest extends ApplicationTests {

    @Autowired
    private InitSequenceBean initSequenceBean;

    @Test
    public void initSequenceBeanTest() {
        log.info("Finish: {}", initSequenceBean.toString());
    }
}

网站文章

  • linux设置仅开机启动一次的命令

    使用 &quot;systemctl set-property [服务名] RemainAfterExit=yes&quot; 可以将某个服务设置为仅开机启动一次。其中 [服务名] 是要设置的服务名称。 例如: systemctl set-property myservice.service RemainAfterExit=yes 这样这个服务在启动之后就不会再启动了。 ...

    2024-02-29 13:50:10
  • jQuery绑定事件的方法四种方法 热门推荐

    jq给元素绑定事件的方法有4种,1.bind() 2.live() 3.live() 4.on() //on常用 一:bind(type,[data],function(eventObject)) bind是使用频率较高的一种,作用就是在选择到的元素上绑定特定事件类型的监听函数,参数的含义如下: type:事件类型,如click、change、mouseover等; ...

    2024-02-29 13:49:41
  • 提取一个目录下的文件夹的名字

    问题描述:windows, 一个目录下面有很多子文件夹,想要批量去获得这些文件夹名称?处理方式:使用DIR命令。将 dir /a:d /b &gt;xxx.txt 复制到记事本中,另存成.bat文件,放到提取文件夹名称的目录中去,双击运行。结果保存到xxx.txt中了。详细信息,可以去掉/b参数,直接写成dir /a:d &gt;xxx.txt更多dir用法,win+r打开...

    2024-02-29 13:49:34
  • 【OS】第二章 进程

    【OS】第二章 进程

    程序:一个在时间上按严格次序、顺序执行的操作序列。概念:一个具有独立功能的程序独占处理机,直至得到最终结果的过程。在计算机系统中只有一个程序在运行,这个程序独占系统中的所有资源,其执行不受外界影响。(...

    2024-02-29 13:49:27
  • solidity之abi编码函数总结

    solidity之abi编码函数总结

    pragma solidity &gt;=0.4.0 &lt;0.6.0; contract Test { function set(uint value) public { } } contract User { function test() public view returns(bytes memory){ /...

    2024-02-29 13:48:57
  • JAVA配置文件服务器登录失败,com.jcraft.jsch.JSchException: Auth fail(示例代码)

    背景服务器信息:服务器A:10.102.110.1服务器B:10.102.110.2需要从服务器A通过Sftp传输文件到服务器B。应用项目中有一个功能,要通个关Sftp进行日志文件的传输,在部署的时候...

    2024-02-29 13:48:51
  • C#常用的加密算法:MD5、Base64、SHA1、SHA256、HmacSHA256、DES、AES、RSA

    C#常用的加密算法:MD5、Base64、SHA1、SHA256、HmacSHA256、DES、AES、RSA

    c#教程https://www.xin3721.com/eschool/CSharpxin3721/简介本文主要讲解一下C#常用的那些加密算法,包括MD5、Base64、SHA1、SHA256、Hma...

    2024-02-29 13:48:38
  • Confluo对比Kafka

    Confluo对比Kafka

    Confluo对比Kafka 最近几天老是看到一篇“伯克利开源 Confluo:吞吐量比 Kafka 高 4 到 10 倍”的文章,到底什么是Confluo,看了一下它的论文和代码,简单分析了一下:初步分析完感觉这个东东为啥要和Kafka比呢?又没有啥可比性,就像Kafka从来不会和Redis比Pub-Sub时延一样。而且这一篇文章感觉比Kafka高大上好多似的,非常容...

    2024-02-29 13:48:08
  • redis复习(参考书籍redis设计与实现)

    redis复习(参考书籍redis设计与实现)

    数据结构与对象 简单动态字符串 Simple Dynamic String struct sdshdr { //记录buf数组中已使用字节的数量 //等于SDS所保存字符串的长度 int len; /...

    2024-02-29 13:48:02
  • Golang 基本常量声明及 iota 使用

    Golang 基本常量声明及 iota 使用

    Golang 常量的基本声明以及 iota 的使用

    2024-02-29 13:47:55