SpringBoot 源码解析 (三)----- Spring Boot 精髓:启动时初始化数据

本文内容纲要:

- ApplicationRunner与CommandLineRunner

- ApplicationRunner

- CommandLineRunner

- 源码分析

- InitializingBean

- @PostConstruct

- Spring的事件机制

- 基础概念

- 事件

- 创建事件监听者

- 事件发布

- 利用ContextRefreshedEvent事件进行初始化操作

在我们用 springboot 搭建项目的时候,有时候会碰到在项目启动时初始化一些操作的需求 ,针对这种需求 spring boot为我们提供了以下几种方案供我们选择:

  • ApplicationRunnerCommandLineRunner接口
  • Spring容器初始化时InitializingBean接口和@PostConstruct
  • Spring的事件机制

ApplicationRunner与CommandLineRunner

我们可以实现 ApplicationRunnerCommandLineRunner接口, 这两个接口工作方式相同,都只提供单一的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,所有事件都要继承它,也就是被观察者
  • 事件发布者: ApplicationEventPublisherApplicationEventMulticaster接口,使用这个接口,就可以发布事件了
  • 事件监听者: ApplicationListener,继承JDK的 EventListener,所有监听者都继承它,也就是我们所说的观察者,当然我们也可以使用注解 @EventListener,效果是一样的

事件

在Spring框架中,默认对ApplicationEvent事件提供了如下支持:

  • ContextStartedEvent:ApplicationContext启动后触发的事件
  • ContextStoppedEvent:ApplicationContext停止后触发的事件
  • ContextRefreshedEvent: ApplicationContext初始化或刷新完成后触发的事件 ;(容器初始化完成后调用,所以我们可以利用这个事件做一些初始化操作)
  • ContextClosedEvent:ApplicationContext关闭后触发的事件;(如 web 容器关闭时自动会触发spring容器的关闭,如果是普通 java 应用,需要调用ctx.registerShutdownHook();注册虚拟机关闭时的钩子才行)

Image

构造一个类继承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();

}

}

事件发布

对于事件发布,代表者是 ApplicationEventPublisherApplicationEventMulticaster,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("容器刷新完成后,我被调用了..");

}

}

本文内容总结:ApplicationRunner与CommandLineRunner,ApplicationRunner,CommandLineRunner,源码分析,InitializingBean,@PostConstruct,Spring的事件机制,基础概念,事件,创建事件监听者,事件发布,利用ContextRefreshedEvent事件进行初始化操作,

原文链接:https://www.cnblogs.com/java-chen-hao/p/11835120.html

以上是 SpringBoot 源码解析 (三)----- Spring Boot 精髓:启动时初始化数据 的全部内容, 来源链接: utcz.com/z/296602.html

回到顶部