Spring Boot 執行初始化邏輯的方法
序
在 Spring Boot 啟動後執行一些初始化的邏輯應該是一個很常見的場景,這裡總結下幾種方法,及執行的順序。
init-method
給bean配置init-method屬性,或者在xml配置檔案中指定,或者指定註解 Bean 的 initMethod 屬性。
InitializingBean
實現 InitializingBean 介面。
使用 PostConstruct 註解
在初始化方法上加 PostConstruct 註解。
Spring Boot 中的 ApplicationRunner/CommandLineRunner
實現 ApplicationRunner 或 CommandLineRunner 介面。
執行效果
我們的基本類:
public class Foo implements InitializingBean, CommandLineRunner, ApplicationRunner { public void init() { System.out.println("init method ..."); } @PostConstruct public void postConstruct() { System.out.println("init by PostConstruct ..."); } @Override public void afterPropertiesSet() throws Exception { System.out.println("init afterPropertiesSet ..."); } @Override public void run(String... args) throws Exception { System.out.println("init by CommandLineRunner ..."); } @Override public void run(ApplicationArguments args) throws Exception { System.out.println("init by ApplicationRunner ..."); } }
引入這個bean。
@Configuration public class BeanConfig { @Bean(initMethod = "init") public Foo foo() { return new Foo(); } }
執行輸出:
init by PostConstruct ... init afterPropertiesSet ... init method ... init by ApplicationRunner ... init by CommandLineRunner ...
執行順序原始碼分析
Spring Boot 應用啟動( SpringApplication.run
)後會先載入初始化 Spring 應用上下文(refresh),然後會呼叫 Spring Boot 引入的執行器(runner): ApplicationRunner 和 CommandLineRunner,本質上 ApplicationRunner 和 CommandLineRunner 並沒有什麼區別。

private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList<Object>(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); AnnotationAwareOrderComparator.sort(runners); for (Object runner : new LinkedHashSet<Object>(runners)) { if (runner instanceof ApplicationRunner) { callRunner((ApplicationRunner) runner, args); } if (runner instanceof CommandLineRunner) { callRunner((CommandLineRunner) runner, args); } } }
所以 Spring 中的初始化機制會先執行。接下來看看 init-method,InitializingBean,PostConstruct 的執行順序。

bean在例項化之後會進行初始化操作,即 initializeBean ,從 invokeInitMethods
方法中我們可以看到 InitializingBean 介面方法先執行,然後是配置的 init-method 方法。
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) { if (System.getSecurityManager() != null) { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { invokeAwareMethods(beanName, bean); return null; } }, getAccessControlContext()); } else { // 如果這個bean實現了一些Aware介面,則將對應的物件設定給他 invokeAwareMethods(beanName, bean); } Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { // 這裡 invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException( (mbd != null ? mbd.getResourceDescription() : null), beanName, "Invocation of init method failed", ex); } if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; } protected void invokeInitMethods(String beanName, final Object bean, 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(new PrivilegedExceptionAction<Object>() { @Override public Object run() throws Exception { ((InitializingBean) bean).afterPropertiesSet(); return null; } }, getAccessControlContext()); } catch (PrivilegedActionException pae) { throw pae.getException(); } } else { // 如果實現了 InitializingBean 介面 ((InitializingBean) bean).afterPropertiesSet(); } } if (mbd != null) { // 如果配置了 init-method String initMethodName = mbd.getInitMethodName(); if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && !mbd.isExternallyManagedInitMethod(initMethodName)) { invokeCustomInitMethod(beanName, bean, mbd); } } }
那麼為何 PostConstruct 註解會先執行呢?源於 PostConstruct 註解是基於 BeanPostProcessor實現的,即 InitDestroyAnnotationBeanPostProcessor。
@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; } public void invokeInitMethods(Object target, String beanName) throws Throwable { Collection<LifecycleElement> initMethodsToIterate = (this.checkedInitMethods != null ? this.checkedInitMethods : this.initMethods); if (!initMethodsToIterate.isEmpty()) { boolean debug = logger.isDebugEnabled(); for (LifecycleElement element : initMethodsToIterate) { if (debug) { logger.debug("Invoking init method on bean '" + beanName + "': " + element.getMethod()); } element.invoke(target); } } }
這個 BeanPostProcessor 首次執行的時候利用反射去尋找這個bean中定義的生命週期元資料,即 PostConstruct,PreDestroy 註解標註的方法。
private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) { if (this.lifecycleMetadataCache == null) { // Happens after deserialization, during destruction... return buildLifecycleMetadata(clazz); } // Quick check on the concurrent map first, with minimal locking. 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; } private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) { final boolean debug = logger.isDebugEnabled(); LinkedList<LifecycleElement> initMethods = new LinkedList<LifecycleElement>(); LinkedList<LifecycleElement> destroyMethods = new LinkedList<LifecycleElement>(); Class<?> targetClass = clazz; do { final LinkedList<LifecycleElement> currInitMethods = new LinkedList<LifecycleElement>(); final LinkedList<LifecycleElement> currDestroyMethods = new LinkedList<LifecycleElement>(); ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() { @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { if (initAnnotationType != null) { if (method.getAnnotation(initAnnotationType) != null) { LifecycleElement element = new LifecycleElement(method); currInitMethods.add(element); if (debug) { logger.debug("Found init method on class [" + clazz.getName() + "]: " + method); } } } if (destroyAnnotationType != null) { if (method.getAnnotation(destroyAnnotationType) != null) { currDestroyMethods.add(new LifecycleElement(method)); if (debug) { logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method); } } } } }); initMethods.addAll(0, currInitMethods); destroyMethods.addAll(currDestroyMethods); targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); return new LifecycleMetadata(clazz, initMethods, destroyMethods); }
上述程式碼中出現的初始化註解型別 initAnnotationType,銷燬註解型別 destroyAnnotationType 目前來看就是 PostConstruct 和 PreDestroy,可以從 CommonAnnotationBeanPostProcessor(繼承了 InitDestroyAnnotationBeanPostProcessor)的構造器中看到,可以看到這個程式碼的可擴充套件性很好。
public CommonAnnotationBeanPostProcessor() { setOrder(Ordered.LOWEST_PRECEDENCE - 3); setInitAnnotationType(PostConstruct.class); setDestroyAnnotationType(PreDestroy.class); ignoreResourceType("javax.xml.ws.WebServiceContext"); }

總結
