当前位置 : 主页 > 编程语言 > java >

SpringBoot——启动初始化数据

来源:互联网 收集:自由互联 发布时间:2023-02-04
前言 在我们用 springboot 搭建项目的时候,有时候会碰到在项目启动时初始化一些操作的需求 ,针对这种需求 spring boot为我们提供了以下几种方案供我们选择: ApplicationRunner 与 CommandLin

前言

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

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

ApplicationRunner与CommandLineRunner

我们可以实现 ApplicationRunner 或 CommandLineRunner 接口, 这两个接口工作方式相同,都只提供单一的run方法,该方法在SpringApplication.run(…)完成之前调用,我们先来看看这两个接口

@FunctionalInterface public interface CommandLineRunner { /** * Callback used to run the bean. * @param args incoming main method arguments * @throws Exception on error */ void run(String... args) throws Exception; } @FunctionalInterface public interface ApplicationRunner { /** * Callback used to run the bean. * @param args incoming application arguments * @throws Exception on error */ void run(ApplicationArguments args) throws Exception; }

都只提供单一的run方法,接下来我们来看看具体的使用

ApplicationRunner

构造一个类实现ApplicationRunner接口

@Component @Order(1) public class ApplicationRunner1 implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println("\u001B[32m[>>> startup ApplicationRunner1 <<<]"); } }

很简单,首先要使用@Component将实现类加入到Spring容器中,如果有多个的话通过@Order(1)进行排序,然后实现其run方法实现自己的初始化数据逻辑就可以了

CommandLineRunner

对于这两个接口而言,我们可以通过Order注解或者使用Ordered接口来指定调用顺序, @Order() 中的值越小,优先级越高

@Component @Order(1) public class CommandLineRunner1 implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("\u001B[32m[>>> startup runner1 <<<]"); } }

同样需要加入到Spring容器中,CommandLineRunner的参数是最原始的参数,没有进行任何处理,ApplicationRunner的参数是ApplicationArguments,是对原始参数的进一步封装,如果有多个的话通过@Order(1)进行排序

ApplicationRunner和CommandLineRunner排序规则

  • 通过Order指定顺序
  • Order值相同ApplicationRunner的实现优先执行

源码分析

从SpringApplication.run方法的第8步callRunners开始

public ConfigurableApplicationContext run(String... args) { ``` // 第八步:执行Runners callRunners(context, applicationArguments); ``` return context; } private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList<>(); //获取容器中所有的ApplicationRunner的Bean实例 runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); //获取容器中所有的CommandLineRunner的Bean实例 runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); //对runners集合进行排序 AnnotationAwareOrderComparator.sort(runners); for (Object runner : new LinkedHashSet<>(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容器中了。

ApplicationRunner和CommandLineRunner实现类的差异点

  • 执行优先级差异
  • run方法入参不一致

ApplicationRunner和CommandLineRunner实现类的相同点

  • 调用点一样
  • 实现方法名一样

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)方法,这个就是用来执行用户设定的初始化操作。我们看下方法体:

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { 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

@Override public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessBeforeInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; } @Override public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; }

获取容器中所有的后置处理器,循环调用后置处理器的postProcessBeforeInitialization方法,这里我们来看一个BeanPostProcessor

public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable { public CommonAnnotationBeanPostProcessor() { setOrder(Ordered.LOWEST_PRECEDENCE - 3); //设置初始化参数为PostConstruct.class setInitAnnotationType(PostConstruct.class); setDestroyAnnotationType(PreDestroy.class); ignoreResourceType("javax.xml.ws.WebServiceContext"); } }

在构造器中设置了一个属性为PostConstruct.class,再次观察CommonAnnotationBeanPostProcessor这个类,它继承自InitDestroyAnnotationBeanPostProcessor。InitDestroyAnnotationBeanPostProcessor顾名思义,就是在Bean初始化和销毁的时候所作的一个前置/后置处理器。查看InitDestroyAnnotationBeanPostProcessor类下的postProcessBeforeInitialization方法:

public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, PriorityOrdered, Serializable { @Override 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, "Failed to invoke init method", ex); } return bean; } private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) { if (this.lifecycleMetadataCache == null) { // Happens after deserialization, during destruction... return buildLifecycleMetadata(clazz); } // Quick check on the concurrent map first, with minimal locking. LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz); if (metadata == null) { synchronized (this.lifecycleMetadataCache) { metadata = this.lifecycleMetadataCache.get(clazz); if (metadata == null) { metadata = buildLifecycleMetadata(clazz); this.lifecycleMetadataCache.put(clazz, metadata); } return metadata; } } return metadata; } private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) { List<LifecycleElement> initMethods = new ArrayList<>(); List<LifecycleElement> destroyMethods = new ArrayList<>(); Class<?> targetClass = clazz; do { final List<LifecycleElement> currInitMethods = new ArrayList<>(); final List<LifecycleElement> currDestroyMethods = new ArrayList<>(); ReflectionUtils.doWithLocalMethods(targetClass, method -> { if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) { //判断clazz中的methon是否有initAnnotationType注解,也就是PostConstruct.class注解 LifecycleElement element = new LifecycleElement(method); //如果有就将方法添加进LifecycleMetadata中 currInitMethods.add(element); if (logger.isTraceEnabled()) { logger.trace("Found init method on class [" + clazz.getName() + "]: " + method); } } if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) { //判断clazz中的methon是否有destroyAnnotationType注解 currDestroyMethods.add(new LifecycleElement(method)); if (logger.isTraceEnabled()) { logger.trace("Found destroy method on class [" + clazz.getName() + "]: " + method); } } }); initMethods.addAll(0, currInitMethods); destroyMethods.addAll(currDestroyMethods); targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); return new LifecycleMetadata(clazz, initMethods, destroyMethods); } }

在这里会去判断某方法是否有PostConstruct.class注解,如果有,则添加到init/destroy队列中,后续一一执行。@PostConstruct注解的方法会在此时执行,我们接着来看invokeInitMethods

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { 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.isTraceEnabled()) { logger.trace("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("容器刷新完成后,我被调用了.."); } }

参考: https://www.cnblogs.com/java-chen-hao/p/11835120.html

https://www.cnblogs.com/liaojie970/p/9055579.html

上一篇:SpringBoot——属性配置解析
下一篇:没有了
网友评论