在我们用 springboot 搭建项目的时候,有时候会碰到在项目启动时初始化一些操作的需求 ,针对这种需求 spring boot为我们提供了以下几种方案供我们选择:
- ApplicationRunner 与- CommandLineRunner 接口
- Spring容器初始化时InitializingBean接口和@PostConstruct
- Spring的事件机制
1. ApplicationRunner与CommandLineRunner
我们可以实现 ApplicationRunner 或 CommandLineRunner 接口, 这两个接口工作方式相同,都只提供单一的run方法,该方法在SpringApplication.run(…)完成之前调用,不知道大家还对我上一篇文章结尾有没有印象,我们先来看看这两个接口
| 12
 3
 4
 5
 6
 7
 
 | public interface ApplicationRunner {void run(ApplicationArguments var1) throws Exception;
 }
 
 public interface CommandLineRunner {
 void run(String... var1) throws Exception;
 }
 
 | 
都只提供单一的run方法,接下来我们来看看具体的使用
1.1 ApplicationRunner
构造一个类实现ApplicationRunner接口
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | //需要加入到Spring容器中@Component
 public class ApplicationRunnerTest implements ApplicationRunner {
 
 @Override
 public void run(ApplicationArguments args) throws Exception {
 System.out.println("ApplicationRunner");
 }
 }
 
 | 
很简单,首先要使用**@Component**将实现类加入到Spring容器中,为什么要这样做我们待会再看,然后实现其run方法实现自己的初始化数据逻辑就可以了。
1.2 CommandLineRunner
对于这两个接口而言,我们可以通过Order注解或者使用Ordered接口来指定调用顺序, @Order() 中的值越小,优先级越高。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | //需要加入到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,是对原始参数的进一步封装。
1.3 源码分析
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | 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容器中了。
2. InitializingBean
在spring初始化bean的时候,如果bean实现了 InitializingBean 接口,在对象的所有属性被初始化后之后才会调用afterPropertiesSet()方法。
| 12
 3
 4
 5
 6
 7
 8
 
 | @Componentpublic class InitialingzingBeanTest implements InitializingBean {
 
 @Override
 public void afterPropertiesSet() throws Exception {
 System.out.println("InitializingBean..");
 }
 }
 
 | 
我们可以看出spring初始化bean肯定会在 ApplicationRunner和CommandLineRunner接口调用之前。
3. @PostConstruct
| 12
 3
 4
 5
 6
 7
 8
 
 | @Componentpublic class PostConstructTest {
 
 @PostConstruct
 public void postConstruct() {
 System.out.println("init...");
 }
 }
 
 | 
我们可以看到,只用在方法上添加**@PostConstruct注解,并将类注入到Spring容器中就可以了。我们来看看@PostConstruct注解的方法是何时执行的**。
在Spring初始化bean时,对bean的实例赋值时,populateBean方法下面有一个initializeBean(beanName, exposedObject, mbd)方法,这个就是用来执行用户设定的初始化操作。我们看下方法体:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 
 | 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
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
 | 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
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | 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方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 
 | 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
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 
 | 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()方法之前执行
4. Spring的事件机制
4.1 基础概念
Spring的事件驱动模型由三部分组成
- 事件: ApplicationEvent ,继承自JDK的EventObject ,所有事件都要继承它,也就是被观察者
- 事件发布者: ApplicationEventPublisher 及ApplicationEventMulticaster 接口,使用这个接口,就可以发布事件了
- 事件监听者: ApplicationListener ,继承JDK的EventListener ,所有监听者都继承它,也就是我们所说的观察者,当然我们也可以使用注解@EventListener ,效果是一样的
4.2 事件
在Spring框架中,默认对ApplicationEvent事件提供了如下支持:
- ContextStartedEvent:ApplicationContext启动后触发的事件
- ContextStoppedEvent:ApplicationContext停止后触发的事件
- ContextRefreshedEvent: ApplicationContext初始化或刷新完成后触发的事件 ;(容器初始化完成后调用,所以我们可以利用这个事件做一些初始化操作)
- ContextClosedEvent:ApplicationContext关闭后触发的事件;(如 web 容器关闭时自动会触发spring容器的关闭,如果是普通 java 应用,需要调用ctx.registerShutdownHook();注册虚拟机关闭时的钩子才行)
构造一个类继承ApplicationEvent
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | 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;
 }
 
 }
 
 | 
4.3 创建事件监听者
有两种方法可以创建监听者,一种是直接实现ApplicationListener的接口,一种是使用注解 @EventListener , 注解是添加在监听方法上的 ,下面的例子是直接实现的接口
| 12
 3
 4
 5
 6
 7
 
 | @Componentpublic class ApplicationListenerTest implements ApplicationListener<TestEvent> {
 @Override
 public void onApplicationEvent(TestEvent testEvent) {
 testEvent.getMessage();
 }
 }
 
 | 
4.4 事件发布
对于事件发布,代表者是 ApplicationEventPublisher 和 ApplicationEventMulticaster ,ApplicationContext接口继承了ApplicationEventPublisher,并在AbstractApplicationContext实现了具体代码,实际执行是委托给ApplicationEventMulticaster(可以认为是多播)
下面是一个事件发布者的测试实例:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | @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);
 }
 }
 
 | 
4.5 利用ContextRefreshEvent事件进行初始化操作
利用 ContextRefreshedEvent 事件进行初始化,该事件是 ApplicationContext 初始化完成后调用的事件,所以我们可以利用这个事件,对应实现一个 监听器 ,在其 onApplicationEvent() 方法里初始化操作。
| 12
 3
 4
 5
 6
 7
 8
 
 | @Componentpublic class ApplicationListenerTest implements ApplicationListener<ContextRefreshedEvent> {
 
 @Override
 public void onApplicationEvent(ContextRefreshedEvent event) {
 System.out.println("容器刷新完成后,我被调用了..");
 }
 }
 
 |