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

概述

從接口的名字上不難發現,InitializingBean 的做用就是在 bean 初始化後執行定製化的操做。git

Spring 容器中的 Bean 是有生命週期的,Spring 容許在 Bean 在初始化完成後以及 Bean 銷燬前執行特定的操做,經常使用的設定方式有如下三種:spring

  • 經過實現 InitializingBean/DisposableBean 接口來定製初始化以後/銷燬以前的操做方法;
  • 經過 <bean> 元素的 init-method/destroy-method 屬性指定初始化以後 /銷燬以前調用的操做方法;
  • 在指定方法上加上@PostConstruct 或@PreDestroy註解來制定該方法是在初始化以後仍是銷燬以前調用。

注:如下源碼分析基於spring 5.0.4springboot

InitializingBean vs init-method

接口定義以下:ide

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

接口只有一個方法afterPropertiesSet,此方法的調用入口是負責加載 spring bean 的AbstractAutowireCapableBeanFactory,源碼以下:spring-boot

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 方法調用的入口:post

@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方法的調用方式也是經過發射機制。debug

總結

  1. spring bean的初始化執行順序:構造方法 --> @PostConstruct註解的方法 --> afterPropertiesSet方法 --> init-method指定的方法。具體能夠參考例子
  2. afterPropertiesSet經過接口實現方式調用(效率上高一點),@PostConstructinit-method都是經過反射機制調用

例子

直接執行單測com.skyarthur.springboot.common.bean.InitSequenceBeanTest, 請戳代碼下載地址code

核心代碼以下:blog

@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());
    }
}
相關文章
相關標籤/搜索