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 &amp;&amp; (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>&lt;Object&gt;) () -&gt;<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> &amp;&amp; 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) &amp;&amp;
            !(isInitializingBean &amp;&amp; "afterPropertiesSet".equals(initMethodName)) &amp;&amp;
            !<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)">);
}

}