zoukankan      html  css  js  c++  java
  • 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
        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 ,所有事件都要继承它,也就是被观察者
    • 事件发布者: 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("容器刷新完成后,我被调用了..");
        }
    }
  • 相关阅读:
    oracle 导入数据时提示只有 DBA 才能导入由其他 DBA 导出的文件
    oracle 常用语句
    android udp 无法收到数据 (模拟器中)
    android DatagramSocket send 发送数据出错
    AtCoder ABC 128E Roadwork
    AtCoder ABC 128D equeue
    AtCoder ABC 127F Absolute Minima
    AtCoder ABC 127E Cell Distance
    CodeForces 1166E The LCMs Must be Large
    CodeForces 1166D Cute Sequences
  • 原文地址:https://www.cnblogs.com/java-chen-hao/p/11835120.html
Copyright © 2011-2022 走看看