SpringBoot 原始碼解析 (三)----- Spring Boot 精髓:啟動時初始化資料
在我們用 springboot 搭建專案的時候,有時候會碰到在專案啟動時初始化一些操作的需求 ,針對這種需求 spring boot為我們提供了以下幾種方案供我們選擇:
-
ApplicationRunner
與CommandLineRunner
介面 -
Spring容器初始化時InitializingBean介面和@PostConstruct
-
Spring的事件機制
ApplicationRunner與CommandLineRunner
我們可以實現 ApplicationRunner
CommandLineRunner
介面, 這兩個介面工作方式相同,都只提供單一的run方法,該方法在SpringApplication.run(…)完成之前呼叫,不知道大家還對我上一篇文章結尾有沒有印象,我們先來看看這兩個介面
public interface ApplicationRunner { void run(ApplicationArguments var1) throws Exception; } public interface CommandLineRunner { void run(String... var1) throws Exception; }
都只提供單一的run方法,接下來我們來看看具體的使用
ApplicationRunner
構造一個類實現ApplicationRunner介面
//需要加入到Spring容器中 @Component public class ApplicationRunnerTest implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println("ApplicationRunner"); } }
很簡單,首先要使用@Component將實現類加入到Spring容器中,為什麼要這樣做我們待會再看,然後實現其run方法實現自己的初始化資料邏輯就可以了
CommandLineRunner
對於這兩個介面而言,我們可以通過Order註解或者使用Ordered介面來指定呼叫順序, @Order()
中的值越小,優先順序越高
//需要加入到Spring容器中 @Component @Order(1) public class CommandLineRunnerTest implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("CommandLineRunner..."); } }
同樣需要加入到Spring容器中,CommandLineRunner的引數是最原始的引數,沒有進行任何處理,ApplicationRunner的引數是ApplicationArguments,是對原始引數的進一步封裝
原始碼分析
大家回顧一下我上一篇文章,也就是SpringApplication.run方法的最後一步第八步:執行Runners,這裡我直接把程式碼複製過來
private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList<Object>(); //獲取容器中所有的ApplicationRunner的Bean例項 runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); //獲取容器中所有的CommandLineRunner的Bean例項 runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); AnnotationAwareOrderComparator.sort(runners); for (Object runner : new LinkedHashSet<Object>(runners)) { if (runner instanceof ApplicationRunner) { //執行ApplicationRunner的run方法 callRunner((ApplicationRunner) runner, args); } if (runner instanceof CommandLineRunner) { //執行CommandLineRunner的run方法 callRunner((CommandLineRunner) runner, args); } } }
很明顯,是直接從Spring容器中獲取ApplicationRunner和CommandLineRunner的例項,並呼叫其run方法,這也就是為什麼我要使用@Component將ApplicationRunner和CommandLineRunner介面的實現類加入到Spring容器中了。
InitializingBean
在spring初始化bean的時候,如果bean實現了 InitializingBean
介面,在物件的所有屬性被初始化後之後才會呼叫afterPropertiesSet()方法
@Component public class InitialingzingBeanTest implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { System.out.println("InitializingBean.."); } }
我們可以看出spring初始化bean肯定會在 ApplicationRunner和CommandLineRunner介面呼叫之前。
@PostConstruct
@Component public class PostConstructTest { @PostConstruct public void postConstruct() { System.out.println("init..."); } }
我們可以看到,只用在方法上新增@PostConstruct註解,並將類注入到Spring容器中就可以了。我們來看看@PostConstruct註解的方法是何時執行的
在Spring初始化bean時,對bean的例項賦值時,populateBean方法下面有一個initializeBean(beanName, exposedObject, mbd)方法,這個就是用來執行使用者設定的初始化操作。我們看下方法體:
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { // 啟用 Aware 方法 invokeAwareMethods(beanName, bean); return null; }, getAccessControlContext()); } else { // 對特殊的 bean 處理:Aware、BeanClassLoaderAware、BeanFactoryAware invokeAwareMethods(beanName, bean); } Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { // 後處理器 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { // 啟用使用者自定義的 init 方法 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; }
我們看到會先執行後處理器然後執行invokeInitMethods方法,我們來看下applyBeanPostProcessorsBeforeInitialization
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { result = beanProcessor.postProcessBeforeInitialization(result, beanName); if (result == null) { return result; } } return result; } public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { result = beanProcessor.postProcessAfterInitialization(result, beanName); if (result == null) { return result; } } return result; }
獲取容器中所有的後置處理器,迴圈呼叫後置處理器的postProcessBeforeInitialization方法,這裡我們來看一個BeanPostProcessor
public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable { public CommonAnnotationBeanPostProcessor() { this.setOrder(2147483644); //設定初始化引數為PostConstruct.class this.setInitAnnotationType(PostConstruct.class); this.setDestroyAnnotationType(PreDestroy.class); this.ignoreResourceType("javax.xml.ws.WebServiceContext"); } //略... }
在構造器中設定了一個屬性為PostConstruct.class,再次觀察CommonAnnotationBeanPostProcessor這個類,它繼承自InitDestroyAnnotationBeanPostProcessor。InitDestroyAnnotationBeanPostProcessor顧名思義,就是在Bean初始化和銷燬的時候所作的一個前置/後置處理器。檢視InitDestroyAnnotationBeanPostProcessor類下的postProcessBeforeInitialization方法:
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, "Couldn't invoke init method", ex); } return bean; } private LifecycleMetadata buildLifecycleMetadata(final Class clazz) { final LifecycleMetadata newMetadata = new LifecycleMetadata(); final boolean debug = logger.isDebugEnabled(); ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() { public void doWith(Method method) { if (initAnnotationType != null) { //判斷clazz中的methon是否有initAnnotationType註解,也就是PostConstruct.class註解 if (method.getAnnotation(initAnnotationType) != null) { //如果有就將方法新增進LifecycleMetadata中 newMetadata.addInitMethod(method); if (debug) { logger.debug("Found init method on class [" + clazz.getName() + "]: " + method); } } } if (destroyAnnotationType != null) { //判斷clazz中的methon是否有destroyAnnotationType註解 if (method.getAnnotation(destroyAnnotationType) != null) { newMetadata.addDestroyMethod(method); if (debug) { logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method); } } } } }); return newMetadata; }
在這裡會去判斷某方法是否有PostConstruct.class註解,如果有,則新增到init/destroy佇列中,後續一一執行。@PostConstruct註解的方法會在此時執行,我們接著來看invokeInitMethods
protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd) throws Throwable { // 是否實現 InitializingBean // 如果實現了 InitializingBean 介面,則只掉呼叫bean的 afterPropertiesSet() 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 { // 直接呼叫 afterPropertiesSet() ((InitializingBean) bean).afterPropertiesSet(); } } if (mbd != null && bean.getClass() != NullBean.class) { // 判斷是否指定了 init-method(), // 如果指定了 init-method(),則再呼叫制定的init-method String initMethodName = mbd.getInitMethodName(); if (StringUtils.hasLength(initMethodName) && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && !mbd.isExternallyManagedInitMethod(initMethodName)) { // 利用反射機制執行 invokeCustomInitMethod(beanName, bean, mbd); } } }
首先檢測當前 bean 是否實現了 InitializingBean 介面,如果實現了則呼叫其 afterPropertiesSet()
,然後再檢查是否也指定了 init-method()
,如果指定了則通過反射機制呼叫指定的 init-method()
。
我們也可以發現@PostConstruct會在實現 InitializingBean 介面的afterPropertiesSet()方法之前執行
Spring的事件機制
基礎概念
Spring的事件驅動模型由三部分組成
- 事件:
ApplicationEvent
,繼承自JDK的EventObject
,所有事件都要繼承它,也就是被觀察者 - 事件釋出者:
ApplicationEventPublisher
及ApplicationEventMulticaster
介面,使用這個介面,就可以釋出事件了 - 事件監聽者:
ApplicationListener
,繼承JDK的EventListener
,所有監聽者都繼承它,也就是我們所說的觀察者,當然我們也可以使用註解@EventListener
,效果是一樣的
事件
在Spring框架中,預設對ApplicationEvent事件提供瞭如下支援:
- ContextStartedEvent:ApplicationContext啟動後觸發的事件
- ContextStoppedEvent:ApplicationContext停止後觸發的事件
- ContextRefreshedEvent: ApplicationContext初始化或重新整理完成後觸發的事件 ;(容器初始化完成後呼叫,所以我們可以利用這個事件做一些初始化操作)
- ContextClosedEvent:ApplicationContext關閉後觸發的事件;(如 web 容器關閉時自動會觸發spring容器的關閉,如果是普通 java 應用,需要呼叫ctx.registerShutdownHook();註冊虛擬機器關閉時的鉤子才行)
構造一個類繼承ApplicationEvent
public class TestEvent extends ApplicationEvent { private String message; public TestEvent(Object source) { super(source); } public void getMessage() { System.out.println(message); } public void setMessage(String message) { this.message = message; } }
建立事件監聽者
有兩種方法可以建立監聽者,一種是直接實現ApplicationListener的介面,一種是使用註解 @EventListener
, 註解是新增在監聽方法上的 ,下面的例子是直接實現的介面
@Component public class ApplicationListenerTest implements ApplicationListener<TestEvent> { @Override public void onApplicationEvent(TestEvent testEvent) { testEvent.getMessage(); } }
事件釋出
對於事件釋出,代表者是 ApplicationEventPublisher
和 ApplicationEventMulticaster
,ApplicationContext介面繼承了ApplicationEventPublisher,並在AbstractApplicationContext實現了具體程式碼,實際執行是委託給ApplicationEventMulticaster(可以認為是多播)
下面是一個事件釋出者的測試例項:
@RunWith(SpringRunner.class) @SpringBootTest public class EventTest { @Autowired private ApplicationContext applicationContext; @Test public void publishTest() { TestEvent testEvent = new TestEvent(""); testEvent.setMessage("hello world"); applicationContext.publishEvent(testEvent); } }
利用ContextRefreshedEvent事件進行初始化操作
利用 ContextRefreshedEvent
事件進行初始化,該事件是 ApplicationContext
初始化完成後呼叫的事件,所以我們可以利用這個事件,對應實現一個 監聽器 ,在其 onApplicationEvent()
方法裡初始化操作
@Component public class ApplicationListenerTest implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println("容器重新整理完成後,我被呼叫了.."); } }