springBoot中常用接口的分析

springBoot中常用接口的分析

一、应用上下文初始化相关接口

ApplicationContextInitializer接口

ApplicationContextInitializer,直译过来就是“应用程序上下文初始值设定接口”,在官方文档中描述其作用为“Spring容器刷新前执行的一个回调函数”,如果是对SpringBoot了解较深的老司机一定知道在SpringBoot初始化的时候有两个极为重要的步骤:准备上下文(prepareContext)和刷新上下文(refreshContext),那么ApplicationContextInitializer接口就在刷新上下文(refreshContext)前期准备工作的时候起作用,如果你还是一个“萌新”SpringBoot的初学者不知道这两个步骤也不要着急,我们后面细细道来。
  废话这么多,那么ApplicationContextInitializer接口到底是用来做什么的呢??又需要怎么使用它呢??
  ApplicationContextInitializer接口主要的作用是向SpringBoot的容器中注入属性,我们一共有3中方式来使用它,姿势很多直接开干。

1、将属性注入到容器
(1)spring.factories文件注入

步骤1:新建一个类并实现ApplicationContextInitializer接口

@Order(1)
public class TestInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        //从上下文中获取到程序环境
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        //创建需要注入的属性
        HashMap<String, Object> map = new HashMap<>();
        map.put("key1","value1");
        //将属性封装成配置项
        MapPropertySource testPropertySource = new MapPropertySource("testInitializer", map);
        //添加到项目环境配置的最末端
        environment.getPropertySources().addLast(testPropertySource);
        //完成控制台输出提示
        System.out.println("TestInitializer执行完成");
    }
}

步骤2:将编写好的类注入到容器中,在resources文件夹下新建META-INF文件夹,META-INF文件夹中创建一个spring.factories的文件,里面填写以下内容

#等号后面是需要加载的类,就是我们实现了ApplicationContextInitializer的类的全路径
org.springframework.context.ApplicationContextInitializer=com.gyx.test.TestInitializer
(2) SpringApplication手动注入

步骤1:新建一个类并实现ApplicationContextInitializer接口(与之前的完全一样)
步骤2:修改SpringBoot启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(Application.class);
        //添加到初始化配置项中
        springApplication.addInitializers(new TestInitializer());
        springApplication.run(args);
    }
}
(3) 配置文件(application.properties)注册

步骤1:新建一个类并实现ApplicationContextInitializer接口(与之前的完全一样)
步骤2:修改SpringBoot的配置文件(Application.properties)

#等号后面是需要加载的类,就是我们实现了ApplicationContextInitializer的类的全路径
context.initializer.classes=com.gyx.test.TestInitializer
(4)注意事项
  1. 多个ApplicationContextInitializer可以使用@Order注解指定执行优先级,Order值越小越有限执行
  2. 配置文件(application.properties)的注册方式优先于其他的方式
2、将属性从容器中读出
@Component
public class TestRead implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }
    
    public String test(){
        return applicationContext.getEnvironment().getProperty("key1");
    }
}
3、源码分析

说明ApplicationContextInitializer使用的时候,我们采用了3种方式,逐一说明,各个击破

(1)、方法一(spring.factories文件注入)

之前说过ApplicationContextInitializer接口在刷新上下文(refreshContext)之前起作用,我们看一下在哪里起作用了。

步骤1:还是一样查看SpringBoot启动类,从头开始理的清楚

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        //进入run方法
        SpringApplication.run(Application.class, args);
    }
}

步骤2:这里直接进入同名方法

public static ConfigurableApplicationContext run(Class<?> primarySource,
            String... args) {
       //继续戳run方法
        return run(new Class<?>[] { primarySource }, args);
}

步骤3:这里开始有区别了,我们现在需要进入run方法,之前是进入SpringApplication的构造函数

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
            String[] args) {
       //这里将SpringBoot的启动分成了两个重要的部分
       //创建一个SpringApplication对象,这个构造方法是在配置SpringBoot自己的一些基本参数,做一些基本数据的获取
       //run方法,正式的启动,开始管理SpringBoot的生命周期
       //点这个run方法
        return new SpringApplication(primarySources).run(args);
}

步骤4:这是SpringBoot最重要的一个方法!!!超级重要

public ConfigurableApplicationContext run(String... args) {
    //创建一个计时器,用来记录SpringBoot的运行时间
   StopWatch stopWatch = new StopWatch();
   //启动计时器
   stopWatch.start();
   //配置应用上下文的控制器,可进行一些基础配置的操作,设置上下文ID,设置父应用上下文,添加监听器和刷新容器相关的操作等
   //目前只需要知道SpringBoot有这么一个管理者就完全OK了,具体作用不用过于在意,实际上SpringBoot有完整的一条管理者体系,这些我们放在以后了解
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   // 配置属性
   configureHeadlessProperty();
   //获取监听器  从路径MEAT-INF/spring.factories中找到所有的SpringApplicationRunListener
   //是不是和之前说的SpringFactoriesLoader有些相似,可谓是一招鲜吃遍天
   SpringApplicationRunListeners listeners = getRunListeners(args);
   //监视器启动!
   listeners.starting();
   try {
       //封装一下环境
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      // 准备环境  不管他,跳过
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
      //打印banner  就是每次启动输出的SpringBoot的图标
      Banner printedBanner = printBanner(environment);
      //创建应用程序上下文,常用的有2种  web环境的和普通环境的,根据条件不同,生成的结果不一样
      context = createApplicationContext();
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
      //准备上下文,下面那个就是刷新上下文
      //回忆下之前是怎么说的
      //ApplicationContextInitializer接口在刷新上下文(refreshContext)之前起作用
      //就是这里没跑了,点进去
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      //刷新上下文
      refreshContext(context);
      //2次刷新
      afterRefresh(context, applicationArguments);
      //计时器停止
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      listeners.started(context);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }

   try {
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

步骤5:这里的重点只有一句话

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
      SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
   context.setEnvironment(environment);
   postProcessApplicationContext(context);
   //注意力集中过来
   //方法名直译过来“应用初始值设定”
   //传入的参数就是之前获取的web环境的应用程序上下文,进入!!
   //ApplicationContextInitializer也就是从这里要开始起作用了
   applyInitializers(context);
   listeners.contextPrepared(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // Add boot specific singleton beans
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   // Load the sources
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   load(context, sources.toArray(new Object[0]));
   listeners.contextLoaded(context);
}

步骤6:敲黑板!!注意记笔记 重点来了

protected void applyInitializers(ConfigurableApplicationContext context) {
        //getInitializers() 就是简单的拿出initializers属性,然后根据order属性进行排序
        //initializers属性在代码中是一个list,有兴趣可以点进去看一看,如下
        //"private List<ApplicationContextInitializer<?>> initializers;"
        //重点是list元素是如何添加的进去的
        //大家都是天才程序员
        //工厂加载机制解析步骤10中,生成的结果集合最终就被赋值给了Initializers
        //赋值的方法,在工厂加载机制解析步骤5中的setInitializers方法(全文搜索setInitializers快速查看),具体的实现也是简单的赋值,和我们写的getset方法基本上一样
        for (ApplicationContextInitializer initializer : getInitializers()) {
           //这里会获取实现ApplicationContextInitializer接口时填写的泛型
           //如果记不得了 ,全文搜索一下“TestInitializer”,可以看到我们实现ApplicationContextInitializer时填写的泛型正是“ConfigurableApplicationContext”
            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
                    ApplicationContextInitializer.class);
           //判断填入的泛型是否为ConfigurableApplicationContext类或者是它的子类,如果不是停止运行,并报错
           //工作过一段时间的话,可能见过这个提示"Unable to call initializer." ,
           //现在透彻理解了报错原因,以后就可以愉快的装逼了
            Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            //考大家一个问题,面向对象的3大基本特征是什么?
            //封装、继承、多态,这里就是使用的多态
            //这里调用了ApplicationContextInitializer接口强制实现的initialize方法
            //如果遗忘了,全文搜索一下“TestInitializer”,实现接口的同时,重写了initialize方法
            initializer.initialize(context);
        }
    }
(2)、方法二(SpringApplication手动注入)

步骤1:一样从启动类开始

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(Application.class);
        //添加到初始化配置项中
        //不同似乎只有这里,此事必有蹊跷,前去一看究竟
        springApplication.addInitializers(new TestInitializer());
        springApplication.run(args);
    }
}

步骤2:手工将实例化的Initializers存入集合

//第一次看到这里的时候,感觉智商收到了侮辱
//编程往往就是这么简单,手动把Initializers存入集合,把步骤1中的TestInitializer的实现放入集合
//没有理解的小朋友,回看2个地方
//1、工厂加载机制解析-》步骤5中的setInitializers方法(全文搜索setInitializers快速查看)
//2、系统初始化器解析-》方法一的步骤6中的getInitializers方法(全文搜索getInitializers快速查看)
public void addInitializers(ApplicationContextInitializer<?>... initializers) {
        this.initializers.addAll(Arrays.asList(initializers));
    }

之后的操作和方法一就没有区别了

(3)、方法三(配置文件注册)

配置文件注册的方式是通过DelegatingApplicationContextInitializer类来实现的

public class DelegatingApplicationContextInitializer implements
        ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered

可看到DelegatingApplicationContextInitializer也实现了ApplicationContextInitializer接口,这个类被SpringBoot预先写入到spring.factories文件中,大家都是认真的宝宝,认认真真的阅读了“方法一”的解析,知道DelegatingApplicationContextInitializer会被调用initialize方法,跳转至initialize方法代码,走你
步骤1:initialize方法

    @Override
    public void initialize(ConfigurableApplicationContext context) {
        //获取上下文环境变量,就是配置文件里面的数据
        ConfigurableEnvironment environment = context.getEnvironment();
        //环境变量中查找initializer所有类的类型,跳转到步骤2
        List<Class<?>> initializerClasses = getInitializerClasses(environment);
        if (!initializerClasses.isEmpty()) {
           //跳转到步骤3
            applyInitializerClasses(context, initializerClasses);
        }
    }

步骤2:获取配置中设置的Initializer

private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) { 
        //从环境变量(我们的配置文件)中查找一个叫PROPERTY_NAME的属性
        //PROPERTY_NAME是个常量,值为“context.initializer.classes”
        //全局搜索context.initializer.classes,看看出现在哪了,就是配置文件中的属性
        String classNames = env.getProperty(PROPERTY_NAME);
        List<Class<?>> classes = new ArrayList<>();
        if (StringUtils.hasLength(classNames)) {
           //将设置的值根据“,”切割  所以我们配置多个ApplicationContextInitializer实现类的时候需要用“,”隔开
            for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
              //getInitializerClass同名方法,方法的重载,根据字符查找对应的类,并判断是否为ApplicationContextInitializer的实现类
              //然后添加到返回对象中
                classes.add(getInitializerClass(className));
            }
        }
        //回到步骤1
        return classes;
    }

步骤3:实例化ApplicationContextInitializer实现类

private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
        Class<?> contextClass = context.getClass();
        List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
        for (Class<?> initializerClass : initializerClasses) {
           //instantiateInitializer方法是使用BeanUtils工具类实例化ApplicationContextInitializer的实现类
           //将实例到集合中
            initializers.add(instantiateInitializer(contextClass, initializerClass));
        }
        //调用这些实例 跳转到步骤4
        applyInitializers(context, initializers);
    }

步骤4:将找到的ApplicationContextInitializer运用起来

private void applyInitializers(ConfigurableApplicationContext context,
      List<ApplicationContextInitializer<?>> initializers) {
   //根据order属性进行排序   
   initializers.sort(new AnnotationAwareOrderComparator());
   //循环刚刚的实例集合
   for (ApplicationContextInitializer initializer : initializers) {
       //调用ApplicationContextInitializer的接口强制实现的initialize方法
      //完成ApplicationContextInitializer实现类的起作用
      initializer.initialize(context);
   }
}

了解了所有的运行原理之后,回头思考一下为什么配置文件的方式会优先于其他的方式执行,自己打开源码的仔细阅读的小伙伴们有没有注意到DelegatingApplicationContextInitializer类中定义的order属性为0,而我们自定义的TestInitializer的order属性为1,所以通过配置文件设置的Initializer会优先执行,如果想要优先于配置文件的方式执行只需要将order属性设置的小于0就可以了

那么ApplicationContextInitializer是如何被注册到SpringBoot容器中的呢?

SpringFactoriesLoader抽象类

SpringFactoriesLoader是个内敛害羞的汉子,是springboot框架中通用工厂加载机制的一种实现,会从classpath下多个jar包指定的位置读取文件并实例化类,读取的文件内容必须是key-value形式的,key是接口或者抽象的权限定名,value必须是对应的实现,可以用“,”分割。
  SpringFactoriesLoader是SpringBoot门派的守门大将,几乎是SpringBoot初始化最先接触到的类,让我们从SpringBoot的启动类跟着运行顺序看看能在哪里逮到SpringFactoriesLoader。

1、工厂加载机制源码解析

通过这次阅读会了解SpringFactoriesLoader完整的调用过程,以及SpringFactoriesLoader与ApplicationContextInitializer接口是怎样互动的

步骤1:查看SpringBoot启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        //进入run方法的源码,按着ctrl戳它
        SpringApplication.run(Application.class, args);
    }
}

步骤2:这里可以看到一层简单的调用

public static ConfigurableApplicationContext run(Class<?> primarySource,
            String... args) {
       //进入这个同名方法,继续戳run方法,两个参数分别是传入的启动类和java的启动配置
        return run(new Class<?>[] { primarySource }, args);
}

步骤3:这里就比较有意思了,注意一下注释

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
            String[] args) {
       //这里将SpringBoot的启动分成了两个重要的部分
       //创建一个SpringApplication对象,这个构造方法是在配置SpringBoot自己的一些基本参数,做一些基本数据的获取
       //run方法,正式的启动,开始管理SpringBoot的生命周期
       //点这个SpringApplication构造方法
        return new SpringApplication(primarySources).run(args);
}

步骤4:没有什么用的封装,对构成函数复用

public SpringApplication(Class<?>... primarySources) {
       //点this,查看最重要的构造函数
        this(null, primarySources);
}

步骤5:这里我们可以看到两个熟悉的名字getSpringFactoriesInstances方法和ApplicationContextInitializer接口

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
       //目光聚焦到这里,上面的部分先不要管他,就是在做简单的赋值
       //setInitializers方法:设置初始化项
       //getSpringFactoriesInstances方法:怎么获取需要的初始化项,当然是通过SpringFactoriesInstances来获取,所以我们先要获取到SpringFactoriesInstances
       //我们要告诉SpringFactoriesInstances这些初始化项都要哪些特征啊,哦!原来他们都是ApplicationContextInitializer接口的实现类
       //点进 getSpringFactoriesInstances方法
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
}

步骤6:又是一层同名封装,SpringBoot难看懂有一大半原因就是他喜欢叠千层饼

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
       //不读多说直接戳这个getSpringFactoriesInstances方法
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }

步骤7:这里正式出现了SpringFactoriesLoader,仔细阅读,先来看一下这3个参数,
Class type:是之前传进来的“ApplicationContextInitializer.class”
Class<?>[] parameterTypes:是空的数组
Object… args:干脆啥都没有传

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
       //获取类加载器,不需要特别在意
        ClassLoader classLoader = getClassLoader();
        // 这个set里面是所有的SpringFactoriesLoader方法的实现的全限定名
        Set<String> names = new LinkedHashSet<>(
              //查找所有的SpringFactoriesLoader方法的实现的全限定名
              //进入loadFactoryNames,跳至步骤8
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
       //将这些实现类全部创建出来,我们获得到了一群“内敛害羞的汉子”
       //进入createSpringFactoriesInstances,跳至 步骤10
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
       //在根据Order对实例进行排序(@Order注解或者Order接口)
        AnnotationAwareOrderComparator.sort(instances);
       //返回排序后的结果,SpringFactoriesInstances的源码告一段落,再往后就是另一位嘉宾的故事了
        return instances;
    }

步骤8:这里我们终于初次窥见SpringFactoriesLoader的真面目,这里开始正式的进入SpringFactoriesLoader类的内部

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
       //还记factoryClass是什么吗?没错!是ApplicationContextInitializer.class
       //获取它的类名,这里是ApplicationContextInitializer接口和SpringFactoriesLoader第一次牵手
        String factoryClassName = factoryClass.getName();
       //点击loadSpringFactories进入 步骤9
       //getOrDefault方法:找到key是ApplicationContextInitializer接口的集合,有就返回这个集合,如果没有就返回一个空集合
       //返回到 步骤7,到这里就走出了SpringFactoriesLoader
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

步骤9:看起来loadSpringFactories是个很复杂的方法,其实不然,loadSpringFactories非常简单,仔仔细细的一起来读一下

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
       //SpringFactoriesLoader内部的缓存,第一次进来的时候为空,后面进来就可以直接返回结果,优化加载速度
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
           //在步骤7中获取到了classLoader,所以classLoader不为空
           //用类加载器读取资源
           //FACTORIES_RESOURCE_LOCATION这常量是啥呢??如果小伙伴们已经迫不及待的点进去看了,可以很惊奇的看到这不就是"META-INF/spring.factories"!
           //文章往上翻(或者搜索)找到“spring.factories文件注入”的步骤2,我们是不是创建的就是这个文件,路径和文件名完全一样,我们似乎要接近真相了
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
           //这里把所有找到的资源循环
           //聪明的小朋友可能就有疑惑了!为什么这里要用循环啊,我们不是只有一个spring.factories文件吗?
           //相信所有的小伙伴们都有开发经验,当我们引入一个叫做“XXX-stater”的jar包时,jar包里面就会有一个spring.factories文件,并且SpringBoot自己也有一个spring.factories文件
           //别着急,spring.factories文件的功能可不止注入ApplicationContextInitializer的实现类,我们以后还会和他见面
            while (urls.hasMoreElements()) {
              //获取一条数据(这里占时也只能拿到2条数据,一个我们写的,一个是SpringBoot自身的)
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
              //借助Properties类型的文件读取工具,读取spring.factories文件中配置的ApplicationContextInitializer的实现类,当然现在他们还只是一条条可怜的全限定名 
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
              //循环所有的获取到的内容
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                  //先获取key,就是接口或抽象类的全限定名(就是我们写在spring.factories文件中的“org.springframework.context.ApplicationContextInitializer”等号前面的这段)
                    String factoryClassName = ((String) entry.getKey()).trim();
                 //使用工具根据“,”将value进行切割成字符串集合(不知道原因的小伙伴在文章中搜索“value必须是对应的实现,可以用“,”分割”,然后仔细读几遍)
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    //逐一的添加到结果集中
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
          //将结果集放入缓存
            cache.put(classLoader, result);
           //返回
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

步骤10:经过步骤9,获取到所有的指定的ApplicationContextInitializer实现类的全限定名,这这一步中把它们逐一创建出来
回顾一下参数:
Class type:是之前传进来的“ApplicationContextInitializer.class”
Class<?>[] parameterTypes:是空的数组
Object[] args:干脆啥都没有传是个null
names:ApplicationContextInitializer所有实现类的全限定名

private <T> List<T> createSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
            Set<String> names) {
       //结果集合,放实例对象的
        List<T> instances = new ArrayList<>(names.size());
        //把全限定名,一个个的循环处理
        for (String name : names) {
            try {
              //根据全限定名获取到类对象
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
              //这是一个检测,判断获取到的类型是否与需要的类一样  
                Assert.isAssignable(type, instanceClass);
              //根据参数获取构造方法,实际上获取到的就是默认的无参构造方法
                Constructor<?> constructor = instanceClass
                        .getDeclaredConstructor(parameterTypes);
              //使用反射,通过构造方法实例化对象
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
              //将对象放入结果集
                instances.add(instance);
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException(
                        "Cannot instantiate " + type + " : " + name, ex);
            }
        }
       //最后返回结果集,回到 步骤7
        return instances;
}

二、监听器相关接口

(一)、监听器模式介绍

在学习的路上遵循一些原则,可以更高效的学习,其中就有这么一条“循循渐进”,在深入SpringBoot之前先要了解清楚什么是监听器,监听器是如何实现的,这些都是对付大魔王的神兵利器,和RPG游戏一样打boss之前先要打小怪提升等级,爆出“屠龙宝刀”。
  伊泽瑞尔作为瓦罗拉大陆上组名的探险家在探险的路上,却总是受到天气的影响无法冒险,所以他拜托我帮他写一个软件,辅助他关注天气。

1、监听器模式小demo!天气监听器

步骤1:创建抽象类WeatherEvent(天气状态)

public abstract class weatherEvent{
    //获取天气状态
    public abstract String getWeather();
}

步骤2:实现下雪和下雨事件
下雪事件

public class SnowEvent extends WeatherEvent{
    @Overide
    public String getWeather(){
        return "下雪了";
    }
}

下雨事件

public class RainEvent extends WeatherEvent{
    @Overide
    public String getWeather(){
        return "下雨了";
    }
}

步骤3:创建天气监听器接口

public interface WeatherListener{
    void onWeatherEvent(WeatherEvent event);
}

步骤4:实现监听器,分别处理下雪和下雨的天气
下雪的时候需要穿上大棉袄,带上手套御寒

public class SnowListener implements WeatherListener{
    @Override
    public void onWeatherEvent(WeatherEvent event){
        if(event instanceof SnowEvent){
            event.getWeather();
            System.out.println("今天下雪!请增加衣物,做好御寒保护!");
        }
    }
}

下雨的时候需要带雨伞,穿雨鞋

public class RainListener implements WeatherListener{
    @Override
    public void onWeatherEvent(WeatherEvent event){
        if(event instanceof RainEvent){
            event.getWeather();
            System.out.println("今天下雨!出门请带好雨伞");
        }
    }
}

步骤5:创建广播器接口

public interface EventMulticaster{
    //广播事件
    void multicastEvent(WeatherEvent event);
    //添加监听器 
    void addListener(WeatherListener weaterListener);
    //删除监听器 
    void removeListener(WeatherListener weaterListener);
}

步骤6:抽象类实现广播接口

public abstract class AbstractEventMulticaster implements EventMulticaster{
    //存放监听器的集合,所有需要监听的事件都存在这里
    private List<WeaterListener> listenerList = new ArrayList<>();

    @Override
    public void multicastEvent(WeatherEvent event){
        //采用模板方法,子类可以实现的doStart和doEnd,在调用监听器之前和之后分别作出扩展
        //SpringBoot中有着大量相似的操作
        //SpringBoot中的前置处理器和后置处理器,就是这样实现的
        doStart();
        //循环所有调用所有监听器的onWeatherEvent方法
        listenerList.forEach(i -> i.onWeatherEvent(evnet));
        doEnd();
    }
    
    @Override
    public void addListener(WeatherListener weaterListener){
        listenerList.add(weaterListener);
    }
    
    @Override
    public void removeListener(WeatherListener weaterListener){
        listenerList.remove(weaterListener);
    }
    
    abstract void doStart();
    abstract void doEnd();
}

步骤7:实现天气事件的广播

public class WeatherEventMulticaster extends AbstractEventMulticaster{
    @Override
    void doStart(){
        System.out.println("开始广播天气预报!");
    }
    
    @Override
    void doEnd(){
        System.out.println("广播结束!Over!");
    }
}

步骤8:测试并触发广播

public class Test{
    public static void main(String[] args){
        //创建广播器
        WeatherEventMulticaster eventMulticaster = new WeatherEventMulticaster();
        //创建监听器
        RainListener rainListener = new RainListener();
        SnowListener snowListener = new SnowListener();
        //添加监听器
        eventMulticaster.addListener(rainListener);
        eventMulticaster.addListener(snowListener);
        
        //触发下雨事件
        eventMulticaster.multicastEvent(new RainEvent());
        //除非下雪事件
        eventMulticaster.multicastEvent(new SnowEvent());
    }
}
2、黑默丁格大讲堂,监听器模式机制讲解

伊泽瑞尔的探险活动终于不再受到天气的骚扰了,可是他并不明白小小的玩意为什么如此神奇,多次询问过我,可是无赖我语言贫乏,无法将如此复杂的思想表达清楚,只要求助老友黑默丁格,帮忙说明。

ps:工作中不仅要能实现功能,还要注重表达能力,在面试的时候能把思想表达的清楚可以拿到更高的薪资,在和测试交流的时候可以帮助测试理解实现原理,测试出隐藏在深处的bug,当然作为天才程序员的大伙是没有bug的,肯定是环境问题或者操作不当导致的。

黑默丁格拿到代码,简单看了两眼就分析出了各个模块的作用:

  • 事件:步骤1和步骤2,通过对天气进行抽象,并实现下雨和下雪的天气状态
  • 监听器:步骤3和步骤4,规范对天气监听的模式,并且规范对应天气下,需要如何处理
  • 广播器:步骤5、步骤6和步骤7,当有事件发生的时候,广播器发出信号,告知所有的监听器,监听器根据事件作出相应的处理。触发下雨事件的时候,下雨监听器收到消息,它抬头一看乌云密布电闪雷鸣,微微一愣,大喊一句:“打雷下雨收衣服啊!!”,广播器继续通知下一个监听器下雪监听器,下雪监听器看看天空,摆摆手,说:“这事与我无关去找别人”
  • 触发机制:步骤8,demo中采用的硬编码的形式触发的,在实际运用中,可能是湿度仪检测到湿度暴涨开始下雨了,触发广播。

在23种设计模式中是没有监听器模式的,监听器模式是观察者模式的一种实现,这两个名字都容易让人产生一些误导,在“监听”、“观察”很容易让人觉得是监听器发现了事件,然后行动。实际上是广播器把事件推送给所有的监听器,每个监听器都对事件做出判断和处理。

(二)、SpringBoot事件监听器的实现

1、ApplicationListener接口

ApplicationListener是Spring事件机制的一部分,与抽象类ApplicationEvent类配合来完成ApplicationContext的事件机制,实现ApplicationListener接口的类,会在SpringBoot加入到广播器中,当ApplicationContext触发了一个事件,就用广播器通知所有实现ApplicationListener接口的类。

//这个注解表示,当前类只有一个方法
@FunctionalInterface
//传入的泛型,说明这个监听器,需要监听的事件类型
//继承的EventListener类,是个空类,主要是声明继承它的类是个事件监听器,面向对象编程的思想体现
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);

}

不难发现ApplicationListener的接口与我们实现的天气监听器的步骤3几乎一样,如果理解了小demo这个类的作用肯定已经了解的明明白白。

2、ApplicationEventMulticaster接口

ApplicationEventMulticaster是Spring事件机制的广播器接口,所有的广播器都需要实现此接口,主要作用是管理所有的监听器,以及推送事件给监听器。

public interface ApplicationEventMulticaster {
    
    //添加一个监听器
    void addApplicationListener(ApplicationListener<?> listener);

    //根据beanName添加一个监听器
    void addApplicationListenerBean(String listenerBeanName);

    //移除一个监听器
    void removeApplicationListener(ApplicationListener<?> listener);

    //根据beanName移除一个监听器
    void removeApplicationListenerBean(String listenerBeanName);

    //移除所有监听器
    void removeAllListeners();

    //广播事件的方法
    void multicastEvent(ApplicationEvent event);

    void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}
3、SpringBoot的7大事件
  • EventObject:事件顶级对象,所有事件对象的根对象
  • ApplicationEvent:应用事件
  • SpringApplicationEvent:Spring自己的事件,Spring框架自身的事件都会实现这个接口
  • ApplicationStartingEvent:启动事件,框架刚刚启动就会发出这个事件
  • ApplicationEnvironmentPreparedEvent:环境在变完成,系统属性和用户指定已经加载完成
  • ApplicationContextInitializedEvent:已经创建好了上下文,并且还没有加载任何bean之前发出这个事件
  • ApplicationPreparedEvent:在Bean定义开始加载之后,尚未完全加载之前,刷新上下文之前触发
  • ApplicationStartedEvent:bean已经创建完成,上下文已经刷新完成,但是ApplicationRunner和CommandLineRunne两个扩展接口并未执行
  • ApplicationReadyEvent:ApplicationRunner和CommandLineRunne两个扩展接口执行完成之后触发
  • ApplicationFailedEvent:在启动发生异常时触发
(1)、事件发生顺序

启动 —》ApplicationStartingEvent —》ApplicationEnvironmentPreparedEvent —》ApplicationContextInitializedEvent —》 ApplicationPreparedEvent —》ApplicationStartedEvent —》 ApplicationReadyEvent —》启动完毕

中间发生异常 —》ApplicationFailedEvent —》启动失败

4、事件监听器的源码分析
(1)监听器注册流程

与ApplicationContextInitializer接口完全一样的流程进行注册的,只是把ApplicationContextInitializer接口换成了ApplicationListener接口

我们还是从最开始的main方法一步步看。
步骤1:查看SpringBoot启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        //进入run方法的源码
        SpringApplication.run(Application.class, args);
    }
}

步骤2:这里可以看到一层简单的调用

public static ConfigurableApplicationContext run(Class<?> primarySource,
            String... args) {
       //进入这个同名方法,继续戳run方法
        return run(new Class<?>[] { primarySource }, args);
}

步骤3:这里就比较有意思了,注意一下注释

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
            String[] args) {
       //点这个SpringApplication构造方法
        return new SpringApplication(primarySources).run(args);
}

步骤4:没有什么用的封装,对构成函数复用

public SpringApplication(Class<?>... primarySources) {
       //点this,查看构造函数
        this(null, primarySources);
}

步骤5:这里我们可以看到两个熟悉的名字getSpringFactoriesInstances方法和ApplicationContextInitializer接口

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
       //这里就是上一篇文章说的ApplicationContextInitializer接口注册
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
       //这里就是ApplicationListener注册的位置,可以看出主要区别就是查询的接口类不同
       //setListeners是找到的对象存到容器中,存到一个list属性中,方便以后使用
       //这个存放对象的list,对应的是小demo的AbstractEventMulticaster类中list,作用是一样一样的
       //getSpringFactoriesInstances方法详解参考文章《SpringBoot功能扩展接口的使用与源码分析》
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
}
(2)监听器触发流程

步骤1:查看SpringBoot启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

步骤2:ConfigurableApplicationContext类

public static ConfigurableApplicationContext run(Class<?> primarySource,
            String... args) {
        return run(new Class<?>[] { primarySource }, args);
}

步骤3:这次进入run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
            String[] args) {
       //点击run方法
        return new SpringApplication(primarySources).run(args);
}

步骤4:每次看到这个方法,都感觉它罪孽深重,多少人从它开始看起,踏上阅读源码的不归路
代码较长,这次就不写所有的注释了,具体注释看这里https://www.jianshu.com/p/11b38582dfa4

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        //获取事件运行器
        //SpringApplicationRunListeners内部包含一个SpringApplicationRunListener(这里s没有了)的集合
        //SpringApplicationRunListener有7大事件的执行方法,在对应的地点会被调用,SpringBoot通过这个实现事件的触发
        //SpringBoot自带一个实现,这个实现分别会执行定义好的7大事件
        //使用者可以通过实现SpringApplicationRunListener的接口,定义在对应事件所需执行的命令
        //总体流程还是很简单的,留给大家自己阅读
        SpringApplicationRunListeners listeners = getRunListeners(args);
        //监听器的故事从这里开始,我们这次的故事也从这里起航
        //进入starting方法
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

步骤5:没有千层套路

public void starting() {
    //listeners里面存放了所有的SpringApplicationRunListener(事件触发器)
   for (SpringApplicationRunListener listener : this.listeners) {
        //循环执行事件触发器的starting方法
        //点击进入看看SpringBoot自带的事件触发器是如何运行的
      listener.starting();
   }
}

步骤6:广播器发送事件

@Override
    public void starting() {
        //initialMulticaster是广播器
        this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
    }

步骤7:广播器发送事件

@Override
    public void starting() {
        //initialMulticaster是广播器
        //进入multicastEvent方法
        this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
    }

步骤8:广播事件的时候要判断这个事件的类型,判断需不需要在这个时间点执行

@Override
public void multicastEvent(ApplicationEvent event) {
    //resolveDefaultEventType方法,解析事件的默认类型
    //进入resolveDefaultEventType方法,步骤9
    //进入multicastEvent方法,步骤11
   multicastEvent(event, resolveDefaultEventType(event));
}

步骤9:获取事件类型

private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
    //获取事件类型
    //进入forInstance方法,步骤10
    return ResolvableType.forInstance(event);
}

步骤10:通过接口判断时间类型

public static ResolvableType forInstance(Object instance) {
    //断路判断,如果instance是个空,就停止SpringBoot的启动,并报错
    Assert.notNull(instance, "Instance must not be null");
    //判断有没有实现ResolvableTypeProvider这个接口
    //ResolvableTypeProvider接口,表明这个类的事件类型可以被解析
    if (instance instanceof ResolvableTypeProvider) {
        //强转成ResolvableTypeProvider类型,然后获取事件类型
        ResolvableType type = ((ResolvableTypeProvider) instance).getResolvableType();
        if (type != null) {
            //事件类型不为空,就直接返回
            return type;
        }
    }
    //返回一个默认类型,传进来的instance是什么类型,就把这个类型包装成ResolvableType,然后返回
    //返回步骤8
    return ResolvableType.forClass(instance.getClass());
}

步骤11:开始广播
两个参数:event:需要执行的事件    eventType:事件的类型

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    //如果事件类型为空,执行resolveDefaultEventType方法(步骤9和步骤10)
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    //获取任务的执行的线程池
    //如果没有特别指定,返回为null,SpringBoot这里就是空的
    Executor executor = getTaskExecutor();
    //getApplicationListeners方法,获取对这个事件感兴趣的监听器
    //点击进入getApplicationListeners方法,进入步骤12
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        if (executor != null) {
            //在指定线程上执行触发
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            //默认方式执行触发
            invokeListener(listener, event);
        }
    }
}

步骤12:获取对这个事件感兴趣的监听器(缓存获取逻辑)
参数说明:
event:当前发生的事件,这个方法就是找到对这个事件感兴趣的监听器
eventType:事件类型

protected Collection<ApplicationListener<?>> getApplicationListeners(
      ApplicationEvent event, ResolvableType eventType) {
    //获取事件发生的源头类,这里就是SpringApplication
   Object source = event.getSource();
   //获取原头类的类型
   Class<?> sourceType = (source != null ? source.getClass() : null);
   //获取缓存的key
   ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

   //快速执行,从缓存中获取监听器,如果这个方法已经执行了过了,就不要在获取一次了,直接拿到缓存
   ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
   if (retriever != null) {
        //返回对当前事件感兴趣的监听器
      return retriever.getApplicationListeners();
   }

   if (this.beanClassLoader == null ||
         (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
               (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
      //通过key上锁,这是上锁的一个很有效的方式,定义一个属性作为锁的key
      synchronized (this.retrievalMutex) {
        //上锁之后再次检查,有没有其他地方触发了当前事件,把监听器的列表放入了缓存中
        //写过双层验证的单例模式对这里不会陌生,主要原理是一样的
         retriever = this.retrieverCache.get(cacheKey);
         if (retriever != null) {
            //返回对当前事件感兴趣的监听器
            return retriever.getApplicationListeners();
         }
         retriever = new ListenerRetriever(true);
         //真正的查找逻辑被封装在这里
         //SpringBoot这种千层套路,是有规律可循的,这一次是缓存的封装,下一次是实际的调用
         //我们编程的时候可以学习一下,比如封装缓存的查询,再去数据库,降低耦合度
         //点retrieveApplicationListeners方法进入 步骤13
         Collection<ApplicationListener<?>> listeners =
               retrieveApplicationListeners(eventType, sourceType, retriever);
         //存入缓存中
         this.retrieverCache.put(cacheKey, retriever);
         return listeners;
      }
   }
   else {
      //不需要加锁的,并且不需要缓存的查询方式
      //这个方法中有两处调用了retrieveApplicationListeners方法,在方法的内部对有无缓存,做了不同的处理
      //个人观点:应该把内部的缓存逻辑移到这层中,否则耦合度依旧很高
      return retrieveApplicationListeners(eventType, sourceType, null);
   }
}

步骤13:真正获取监听器的逻辑

private Collection<ApplicationListener<?>> retrieveApplicationListeners(
        ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {

    List<ApplicationListener<?>> allListeners = new ArrayList<>();
    Set<ApplicationListener<?>> listeners;
    Set<String> listenerBeans;
    synchronized (this.retrievalMutex) {
        //获取所有的监听器实例
        listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
        //获取所有监听器的beanName
        listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
    }
    //对所有的监听器进行逐一的循环
    for (ApplicationListener<?> listener : listeners) {
        //判断监听器是否对这个事件感兴趣
        //点击supportsEvent方法进入  步骤14
        if (supportsEvent(listener, eventType, sourceType)) {
            if (retriever != null) {
                //如果监听器功能开启了缓存,就存到缓存中
                retriever.applicationListeners.add(listener);
            }
            //不管有没有缓存都会存到这里
            allListeners.add(listener);
        }
    }
    //通过工厂方式,获取监听器,一般情况不会走这里
    if (!listenerBeans.isEmpty()) {
        //获取bean工厂
        BeanFactory beanFactory = getBeanFactory();
        //循环监听器beanName
        for (String listenerBeanName : listenerBeans) {
            try {
                //更具beanName,获取监听器的类型
                Class<?> listenerType = beanFactory.getType(listenerBeanName);
                // 判断监听器是否对这个事件感兴趣
                if (listenerType == null || supportsEvent(listenerType, eventType)) {
                    //获取bean实例,这个方法写作getBean,读作createBean
                    //这是ioc中非常重要的一块逻辑,当获取不到bean的时候,就会创建一个bean对象
                    //具体的我们在后续ioc源码分析的时候讲解
                    ApplicationListener<?> listener =
                            beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                    if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
                        //也是判断是否有缓存的逻辑
                        if (retriever != null) {
                            //多一个判断是否单例的逻辑
                            if (beanFactory.isSingleton(listenerBeanName)) {
                                retriever.applicationListeners.add(listener);
                            }
                            else {
                                //原形bean这里,想起来以前有个组员说这个叫“多例”,最好还是叫“原型”
                                retriever.applicationListenerBeans.add(listenerBeanName);
                            }
                        }
                        allListeners.add(listener);
                    }
                }
            }
            catch (NoSuchBeanDefinitionException ex) {
               
            }
        }
    }
    //进行排序,SpringBoot的常规操作了,根据Order接口或者注解进行排序
    AnnotationAwareOrderComparator.sort(allListeners);
    //对缓存进行一次刷新,把以前的结果清空,将这次运行的结果缓存
    if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
        retriever.applicationListeners.clear();
        retriever.applicationListeners.addAll(allListeners);
    }
    //返回获取到的监听器
    //返回  步骤12
    return allListeners;
}

步骤14:判断监听器是否对当前事件感兴趣

protected boolean supportsEvent(
        ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
    //判断监听器,是否是GenericApplicationListener的子类
    //starting的事件并不是其子类
    //GenericApplicationListener使用了装饰器模式
    //著名的装饰器模式是java中io流(inputStream这些)
    //GenericApplicationListener中可以解析ApplicationListener接口中的泛型参数,接口如下:
    //“ApplicationListener<E extends ApplicationEvent>”要是还想不起来,回头看一下上面小Demo中的使用,和对这个接口的介绍
    GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
            (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
    //下面就变得简单了,虽然内部的判断很繁杂,总体只做了两件事情
    //supportsEventType:判断监听器是否支持当前事件
    //supportsSourceType:监听器是否对这个事件的发起来类感兴趣
    //返回一个总的bool值,返回  步骤13
    return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}
5、自定义SpringBoot监听器
(1)通过spring.factories注入

步骤1:创建监听器,并实现ApplicationListener接口

//我们让这个监听器对ApplicationStartedEvent事件感兴趣
@Order(1)
public class TestListener implements ApplicationListener<ApplicationStartedEvent>{
    @Ovrride
    public void onApplicationEvent(ApplicationStartedEvent event){
        System.out.println("hello,  Application start is over");
    }
}

步骤2:在spring.factories中添加实现类的指引
这里涉及上一讲的内容,还不会的小伙伴们猛戳这里,赶紧补习一下:
https://www.jianshu.com/p/11b38582dfa4

#com.gyx.test.Listener是刚刚写的监听器的全路径名
org.springframework.context.ApplicationListener=com.gyx.test.TestListener

然后运行程序,就可以发现打印的语句出现了

(2)SpringApplication手动注入

步骤1:创建监听器,并实现ApplicationListener接口,和上面的完全一样
步骤2:修改SpringBoot启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(Application.class);
        //添加到初始化配置项中
        springApplication.addListeners(new TestListener());
        springApplication.run(args);
    }
}
(3)SpringBoot的配置文件中注册

步骤1:创建监听器,并实现ApplicationListener接口,和上面的完全一样
步骤2:修改配置文件

context.listener.classes=com.gyx.test.TestListener

看过上一课的小伙伴们,是不是发现了,和之前ApplicationContextInitializer的注册方式完全一样!!!是不是有点感觉了,趁热打铁赶紧吧上一讲再去回顾一下吧

(4)多事件监听,实现SmartApplicationListener接口

这种方法只是实现的接口不一样,注入的方式是一样的,上面的三种注入方式都可以使用
步骤1:创建监听器,并实现SmartApplicationListener接口

@Order(1)
public class TestSmartListener implements SmartApplicationListener{
    @Ovrride
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType){
        //这里是类型判断,判断监听器感兴趣的事件
        //可以对多个事件感兴趣,这里就配置了两个事件
        return ApplicationStartedEvent.class.isAssignableFrom(eventType) 
            || ApplicationPreparedEvent.class.isAssignableFrom(eventType);
    }
    @Ovrride
    public void onApplicationEvent(ApplicationStartedEvent event){
        System.out.println("hello,  This is smartApplicationListener");
    }
}

文章转载:SpringBoot源码初学者(一):SpringBoot功能扩展接口的使用与源码分析

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值