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 </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> <strong>run</strong>(ApplicationArguments args) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception { System.out.println(</span>"ApplicationRunner"<span style="color: rgba(0, 0, 0, 1)">); }
}
很简单,首先要使用@Component将实现类加入到 Spring 容器中,为什么要这样做我们待会再看,然后实现其 run 方法实现自己的初始化数据逻辑就可以了
CommandLineRunner
对于这两个接口而言,我们可以通过 Order 注解或者使用 Ordered 接口来指定调用顺序, @Order()
中的值越小,优先级越高
//需要加入到 Spring 容器中 @Component @Order(1) public class CommandLineRunnerTest implements CommandLineRunner {@Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> run(String... args) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception { System.out.println(</span>"CommandLineRunner..."<span style="color: rgba(0, 0, 0, 1)">); }
}
同样需要加入到 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 </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> <strong>afterPropertiesSet()</strong> <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception { System.out.println(</span>"InitializingBean.."<span style="color: rgba(0, 0, 0, 1)">); }
}
我们可以看出 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 </span>=<span style="color: rgba(0, 0, 0, 1)"> bean; </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mbd == <span style="color: rgba(0, 0, 255, 1)">null</span> || !<span style="color: rgba(0, 0, 0, 1)">mbd.isSynthetic()) { </span><strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 后处理器</span> wrappedBean =</strong><span style="color: rgba(0, 0, 0, 1)"><strong> applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);</strong> } </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { </span><strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 激活用户自定义的 init 方法</span>
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 </span>=<span style="color: rgba(0, 0, 0, 1)"> existingBean; </span><strong><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { result </span>=</strong><span style="color: rgba(0, 0, 0, 1)"><strong> beanProcessor.postProcessBeforeInitialization(result, beanName); </strong> </span><span style="color: rgba(0, 0, 255, 1)">if</span> (result == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result; } } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result;
}
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {Object result </span>=<span style="color: rgba(0, 0, 0, 1)"> existingBean; </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { result </span>=<span style="color: rgba(0, 0, 0, 1)"> beanProcessor.postProcessAfterInitialization(result, beanName); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (result == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result; } } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> 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 {</span><strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 是否实现 InitializingBean </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果实现了 InitializingBean 接口,则只掉调用bean的 afterPropertiesSet()</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> isInitializingBean = (bean <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> InitializingBean); </span></strong><span style="color: rgba(0, 0, 255, 1)">if</span> (isInitializingBean && (mbd == <span style="color: rgba(0, 0, 255, 1)">null</span> || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"<span style="color: rgba(0, 0, 0, 1)">))) { </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (logger.isDebugEnabled()) { logger.debug(</span>"Invoking afterPropertiesSet() on bean with name '" + beanName + "'"<span style="color: rgba(0, 0, 0, 1)">); } </span><span style="color: rgba(0, 0, 255, 1)">if</span> (System.getSecurityManager() != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { AccessController.doPrivileged((PrivilegedExceptionAction</span><Object>) () -><span style="color: rgba(0, 0, 0, 1)"> { ((InitializingBean) bean).afterPropertiesSet(); </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; }, getAccessControlContext()); } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (PrivilegedActionException pae) { </span><span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)"> pae.getException(); } } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> { </span><strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 直接调用 afterPropertiesSet()</span>
((InitializingBean) bean).afterPropertiesSet();
}
}</span><span style="color: rgba(0, 0, 255, 1)">if</span> (mbd != <span style="color: rgba(0, 0, 255, 1)">null</span> && bean.getClass() != NullBean.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 判断是否指定了 init-method(), </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果指定了 init-method(),则再调用制定的init-method</span> String initMethodName =<span style="color: rgba(0, 0, 0, 1)"> mbd.getInitMethodName(); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (StringUtils.hasLength(initMethodName) && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && !<span style="color: rgba(0, 0, 0, 1)">mbd.isExternallyManagedInitMethod(initMethodName)) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 利用反射机制执行</span>
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 {</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String message; </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> TestEvent(Object source) { </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">(source); } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> getMessage() { System.out.println(message); } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setMessage(String message) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.message =<span style="color: rgba(0, 0, 0, 1)"> 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 </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> publishTest() { TestEvent testEvent </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> TestEvent(""<span style="color: rgba(0, 0, 0, 1)">); testEvent.setMessage(</span>"hello world"<span style="color: rgba(0, 0, 0, 1)">); <strong> applicationContext.publishEvent(testEvent);</strong> }
}
利用 ContextRefreshedEvent 事件进行初始化操作
利用 ContextRefreshedEvent
事件进行初始化,该事件是 ApplicationContext
初始化完成后调用的事件,所以我们可以利用这个事件,对应实现一个 监听器 ,在其 onApplicationEvent()
方法里初始化操作
@Component public class ApplicationListenerTest implements ApplicationListener<ContextRefreshedEvent> {@Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onApplicationEvent(ContextRefreshedEvent event) { System.out.println(</span>"容器刷新完成后,我被调用了.."<span style="color: rgba(0, 0, 0, 1)">); }
}