首页 > 编程语言 >SpringBoot源码初学者(二):SpringBoot事件监听器

SpringBoot源码初学者(二):SpringBoot事件监听器

时间:2024-07-26 21:28:40浏览次数:19  
标签:SpringBoot 步骤 void event 源码 事件 监听器 public

ps:真正适合阅读源码的新手来看的SpringBoot源码讲解,如果你真的想读懂SpringBoot源码,可以按照以下推荐的方式来阅读文章

  1. 打开ide,打开SpringBoot源码,跟着文章一起写注释,写自己的注释
  2. 不要过于纠结没讲到的地方,毕竟SpringBoot源码那么多,想全讲完是不可能的,只要跟着文章认真阅读,SpringBoot是如何运行的一定可以有一个较为深刻的理解
  3. 文章适合通篇阅读,不适合跳读,跳跃性的阅读很容易错过重要的东西
  4. 同样的如果之前的文章没有读过,还是最好先去看之前的文章
  5. 阅读源码必然少不了大段大段的源码,一定要耐心,不要翻翻了事,往往是那些最长的方法中才是真正需要学习的
  6. 如果断更了请用点赞、收藏、评论的方式激励我

系列文章链接:
《SpringBoot源码初学者(一):SpringBoot功能扩展接口的使用与源码分析》

一、监听器模式

  在学习的路上遵循一些原则,可以更高效的学习,其中就有这么一条“循循渐进”,在深入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大事件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cu6pwkNo-1585491460589)(en-resource://database/2523:1)]

  • 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)监听器注册流程

如果看过之前的文章
《 SpringBoot源码初学者(一):SpringBoot功能扩展接口的使用与源码分析》:https://www.jianshu.com/p/11b38582dfa4
这里就很容易理解,不想完整的阅读可以只看一下工厂加载机制源码解析的部分
与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,步骤,void,event,源码,事件,监听器,public
From: https://blog.csdn.net/qq_24428851/article/details/140722822

相关文章

  • 体积计算器(三种语言)源码、效果图
    声明⚠️:英文、藏语翻译均来源网络,若不准确,请于本人联系,请勿抄袭!源码#A-1V=0#体积r=0#小半径R=0#大半径a=0#棱长&长b=0#宽h=0#高DMJ=0#底面积PI=3.14#π(保留两位小数)four_three=1/4*3#四分之三three_one=1/3*1#三分之一R......
  • 零基础STM32单片机编程入门(二十二) ESP8266 WIFI模块实战含源码
    文章目录一.概要二.ESP8266WIFI模块主要性能参数三.ESP8266WIFI模块芯片内部框图四.ESP8266WIFI模块原理图五.ESP8266WIFI模块与单片机通讯方法1.硬件连接2.ESP8266模块AT指令介绍六.STM32单片机与ESP8266WIFI模块通讯实验1.硬件准备2.软件工程3.软件主要代码4.实验......
  • 【一手源码展示】Java代码TikTok内嵌商城代码程序,TikTok跨境电商系统源码,TK商城源码
    这套程序已经做了很久了我这边修复二开优化也好几个版本搭建起来做起来确实费劲前后端分离的程序 二开效果页面展示:......
  • 使用git工具管理泰山派内核源码目录及抽打补丁简易流程
    目录使用git工具管理泰山派内核源码目录及抽打补丁简易流程一、使用git维护源码二、git常用的一些操作三、抽补丁四、打补丁五、补充使用git工具管理泰山派内核源码目录及抽打补丁简易流程最近,在做linux开发的过程中入手了一块泰山派RK3566的开发板,在官方提供的各......
  • VirtualBox源码编译
    由于VirtualBox项目在6.0版本后仅支持64位。因此,本次编译的版本号为5.2.44,最后一个同时支持32位和64位的版本。虽然有官方的编译指南,但是该指南并不清晰,并且并不适应所有版本。同样,本指南也并不适用于所有版本,仅在5.2.44上测试通过。PrerequisitesWIN10最好是win10虚拟机,因为......
  • 【MATLAB源码-第154期】基于matlab的OFDM系统多径信道下块状和梳妆两种导频插入方式误
    操作环境:MATLAB2022a1、算法描述OFDM(OrthogonalFrequencyDivisionMultiplexing,正交频分复用)是一种高效的无线信号传输技术,广泛应用于现代通信系统,如Wi-Fi、LTE和5G。OFDM通过将宽带信道划分为多个正交的窄带子载波来传输数据,有效地提高了频谱利用率并降低了多径传播引起......
  • 【MATLAB源码-第159期】基于matlab的胡桃夹子优化算法(NOA)机器人栅格路径规划,输出做短
    操作环境:MATLAB2022a1、算法描述胡桃夹子优化算法(NutcrackerOptimizationAlgorithm,NOA)是一个灵感来源于胡桃夹子的故事的元启发式优化算法。这个故事中,胡桃夹子是一个能够将坚果壳轻易地破开以获取内部果仁的工具。在优化算法的语境下,这个过程被比喻为寻找问题解决方案......
  • 【工具】SpringBoot项目如何查看某个maven依赖是否存在以及依赖链路
    当我在SpringBoot项目中想加个依赖,但是不确定现有依赖的依赖的依赖.....有没有添加过这个依赖,怎么办呢?如果添加过了但是不知道我需要的这个依赖属于哪个依赖的下面,怎么查呢?IDEA中提供了很方便的视图可以满足我们的需求第一步点击项目右侧的maven第二步选中Dependencies第三......
  • 二叉树及其存储实现C语言(附上源码)
    1.什么是二叉树        二叉树是一种特殊的树型结构,其特点是每个结点至多只有两棵子树(即二叉树不存在度大于二的结点),并且二叉树的子树有左右之分,次序不可颠倒【有序树】。 2.二叉树的定义二叉树T:一个有穷的结点集合。    -这个集合可以为空;    -......
  • Telegram纸飞机统计机器人源码,TG群记账群发机器源码人,TG自动记账全开源版本
    Telegram纸飞机统计机器人源码,TG群记账群发机器源码人,TG自动记账全开源版本一.thinkphp6框架开发按thinkphp6部署+伪静态+php7.4数据库连接信息在config文件夹database.php文件修改后台账号:admin密码123321.(有个小数点)二.机器人部署1.去tg官方申请机器人获取到token......