Spring框架九大核心功能全面解读(三):探寻功能之巅

目录

ApplicationContext

事件

1、什么是Spring Event 事件

ApplicationEvent

ApplicationListener​编辑

ApplicationEventPublisher​编辑

话不多说,上代码

2、Spring内置的事件

3、Spring事件的传播特性

上代码

传播特性的一个小坑

总结


作者介绍:✌️大厂全栈码农|毕设实战开发,专注于大学生项目实战开发、讲解和毕业答疑辅导。

 推荐订阅精彩专栏 👇🏻 避免错过下次更新

Springboot项目精选实战案例

更多项目:CSDN主页YAML墨韵

学如逆水行舟,不进则退。学习如赶路,不能慢一步。

spring九大核心功能(二):权威解析Spring框架九大核心功能(续篇)

ApplicationContext

终于讲到了ApplicationContext,因为前面说的那么多其实就是为ApplicationContext做铺垫的

先来看看ApplicationContext的接口

你会惊讶地发现,ApplicationContext继承的几个接口,除了EnvironmentCapable和ApplicationEventPublisher之外,其余都是前面说的。

EnvironmentCapable这个接口比较简单,提供了获取Environment的功能

说明了可以从ApplicationContext中获取到Environment,所以EnvironmentCapable也算是前面说过了

至于ApplicationEventPublisher我们留到下一节说。

ApplicationContext也继承了ListableBeanFactory和HierarchicalBeanFactory,也就说明ApplicationContext其实他也是一个BeanFactory,所以说ApplicationContext是IOC容器的说法也没什么毛病,但是由于他还继承了其它接口,功能比BeanFactory多多了。

所以,ApplicationContext是一个集万千功能为一身的接口,一旦你获取到了ApplicationContext(可以@Autowired注入),你就可以用来获取Bean、加载资源、获取环境,还可以国际化一下,属实是个王炸。

虽然ApplicationContext继承了这些接口,但是ApplicationContext对于接口的实现是通过一种委派的方式,而真正的实现都是我们前面说的那些实现

什么叫委派呢,咱写一个例子你就知道了

public class MyApplicationContext implements ApplicationContext {

    private final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

    @Override
    public Resource[] getResources(String locationPattern) throws IOException {
        return resourcePatternResolver.getResources(locationPattern);
    }
    
}

如上,其实是一段伪代码

因为ApplicationContext继承了ResourcePatternResolver接口,所以我实现了getResources方法,但是真正的实现其实是交给变量中的PathMatchingResourcePatternResolver来实现的,这其实就是委派,不直接实现,而是交给其它真正实现了这个接口的类来处理

同理,ApplicationContext对于BeanFactory接口的实现其实最终也是交由DefaultListableBeanFactory来委派处理的。

委派这种方式在Spring内部还是用的非常多的,前面提到的某些接口在的实现上也是通过委派的方式来的

ApplicationContext有一个子接口,ConfigurableApplicationContext

从提供的方法看出,就是可以对ApplicationContext进行配置,比如设置Environment,同时也能设置parent,说明了ApplicationContext也有子父的概念

我们已经看到了很多以Configurable开头的接口,这就是命名习惯,表示了可配置的意思,提供的都是set、add之类的方法

ApplicationContext的实现很多,但是他有一个非常重要的抽象实现AbstractApplicationContext,因为其它的实现都是继承这个抽象实现

这个类主要是实现了一些继承的接口方法,通过委派的方式,比如对于BeanFactory接口的实现

并且AbstractApplicationContext这个类也实现了一个非常核心的refresh方法

所有的ApplicationContext在创建之后必须调用这个refresh方法之后才能使用,至于这个方法干了哪些事,后面有机会再写一篇文章来着重扒一扒。

事件

上一小节在说ApplicationContext继承的接口的时候,我们留下了一个悬念,那就是ApplicationEventPublisher的作用,而ApplicationEventPublisher就跟本节要说的事件有关。

Spring事件是一种观察者模式的实现,他的作用主要是用来解耦合的。

当发生了某件事,只要发布一个事件,对这个事件的监听者(观察者)就可以对事件进行响应或者处理。

举个例子来说,假设发生了火灾,可能需要打119、救人,那么就可以基于事件的模型来实现,只需要打119、救人监听火灾的发生就行了,当发生了火灾,通知这些打119、救人去触发相应的逻辑操作。

1、什么是Spring Event 事件

Spring Event 事件就是Spring实现了这种事件模型,你只需要基于Spring提供的API进行扩展,就可以轻易地完成事件的发布与订阅

Spring事件相关api主要有以下几个:

  • ApplicationEvent

  • ApplicationListener

  • ApplicationEventPublisher

ApplicationEvent

事件的父类,所有具体的事件都得继承这个类,构造方法的参数是这个事件携带的参数,监听器就可以通过这个参数来进行一些业务操作。

ApplicationListener

事件监听的接口,泛型是需要监听的事件类型,子类需要实现onApplicationEvent,参数就是监听的事件类型,onApplicationEvent方法的实现就代表了对事件的处理,当事件发生时,Spring会回调onApplicationEvent方法的实现,传入发布的事件。

ApplicationEventPublisher

上一小节留下来的接口,事件发布器,通过publishEvent方法就可以发布一个事件,然后就可以触发监听这个事件的监听器的回调。

ApplicationContext继承了ApplicationEventPublisher,说明只要有ApplicationContext就可以来发布事件了。

话不多说,上代码

就以上面的火灾为例

创建一个火灾事件类

火灾事件类继承ApplicationEvent

// 火灾事件
public class FireEvent extends ApplicationEvent {

    public FireEvent(String source) {
        super(source);
    }

}

创建火灾事件的监听器

打119的火灾事件的监听器:

public class Call119FireEventListener implements ApplicationListener<FireEvent> {

    @Override
    public void onApplicationEvent(FireEvent event) {
        System.out.println("打119");
    }

}

救人的火灾事件的监听器:

public class SavePersonFireEventListener implements ApplicationListener<FireEvent> {

    @Override
    public void onApplicationEvent(FireEvent event) {
        System.out.println("救人");
    }

}

事件和对应的监听都有了,接下来进行测试:

public class Application {

    public static void main(String[] args) {
        //创建一个Spring容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        //将 事件监听器 注册到容器中
        applicationContext.register(Call119FireEventListener.class);
        applicationContext.register(SavePersonFireEventListener.class);
        applicationContext.refresh();

        // 发布着火的事件,触发监听
        applicationContext.publishEvent(new FireEvent("着火了"));
    }

}

将两个事件注册到Spring容器中,然后发布FireEvent事件

运行结果:

打119
救人

控制台打印出了结果,触发了监听。

如果现在需要对火灾进行救火,那么只需要去监听FireEvent,实现救火的逻辑,注入到Spring容器中,就可以了,其余的代码根本不用动。

2、Spring内置的事件

Spring内置的事件很多,这里我罗列几个

事件类型触发时机
ContextRefreshedEvent在调用ConfigurableApplicationContext 接口中的refresh()方法时触发
ContextStartedEvent在调用ConfigurableApplicationContext的start()方法时触发
ContextStoppedEvent在调用ConfigurableApplicationContext的stop()方法时触发
ContextClosedEvent当ApplicationContext被关闭时触发该事件,也就是调用close()方法触发

在ApplicationContext(Spring容器)启动的过程中,Spring会发布这些事件,如果你需要这Spring容器启动的某个时刻进行什么操作,只需要监听对应的事件即可。

3、Spring事件的传播特性

Spring事件的传播是什么意思呢?

前面提到,ApplicationContext有子父容器的概念,而Spring事件的传播就是指当通过子容器发布一个事件之后,不仅可以触发在这个子容器的事件监听器,还可以触发在父容器的这个事件的监听器。

上代码
public class EventPropagateApplication {

    public static void main(String[] args) {

        // 创建一个父容器
        AnnotationConfigApplicationContext parentApplicationContext = new AnnotationConfigApplicationContext();
        //将 打119监听器 注册到父容器中
        parentApplicationContext.register(Call119FireEventListener.class);
        parentApplicationContext.refresh();

        // 创建一个子容器
        AnnotationConfigApplicationContext childApplicationContext = new AnnotationConfigApplicationContext();
        //将 救人监听器 注册到子容器中
        childApplicationContext.register(SavePersonFireEventListener.class);
        childApplicationContext.refresh();

        // 设置一下父容器
        childApplicationContext.setParent(parentApplicationContext);

        // 通过子容器发布着火的事件,触发监听
        childApplicationContext.publishEvent(new FireEvent("着火了"));

    }

}

创建了两个容器,父容器注册了打119的监听器,子容器注册了救人的监听器,然后将子父容器通过setParent关联起来,最后通过子容器,发布了着火的事件。

运行结果:

救人
打119

从打印的日志,的确可以看出,虽然是子容器发布了着火的事件,但是父容器的监听器也成功监听了着火事件。

而这种传播特性,从源码中也可以看出来

如果父容器不为空,就会通过父容器再发布一次事件。

传播特性的一个小坑

前面说过,在Spring容器启动的过程,会发布很多事件,如果你需要有相应的扩展,可以监听这些事件。

但是,不知道你有没有遇到过这么一个坑,就是在SpringCloud环境下,你监听这些Spring事件的监听器会执行很多次,这其实就是跟传播特性有关。

在SpringCloud环境下,为了使像FeignClient和RibbonClient这些不同服务的配置相互隔离,会为每个FeignClient或者是RibbonClient创建一个Spring容器,而这些容器都有一个公共的父容器,那就是SpringBoot项目启动时创建的容器

假设你监听了容器刷新的ContextRefreshedEvent事件,那么你自己写的监听器就在SpringBoot项目启动时创建的容器中

每个服务的配置容器他也是Spring容器,启动时也会发布ContextRefreshedEvent,那么由于传播特性的关系,你的事件监听器就会触发执行多次

如何解决这个坑呢?

你可以进行判断这些监听器有没有执行过,比如加一个判断的标志;或者是监听类似的事件,比如ApplicationStartedEvent事件,这种事件是在SpringBoot启动中发布的事件,而子容器不是SpringBoot,所以不会多次发这种事件,也就会只执行一次。

总结

到这到这整篇文章终于写完了,这里再来简单地回顾一下本文说的几个核心功能:

  • 资源管理:对资源进行统一的封装,方便资源读取和管理

  • 环境:对容器或者是项目的配置进行管理

  • 类型转换:将一种类型转换成另一种类型

  • 数据绑定:将数据跟对象的属性进行绑定,绑定之前涉及到类型转换

  • 泛型处理:一个操作泛型的工具类,Spring中到处可见

  • 国际化:对Java的国际化进行了统一的封装

  • BeanFactory:IOC容器

  • ApplicationContext:一个集万千功能于一身的王炸接口,也可以说是IOC容器

  • 事件:Spring提供的基于观察者模式实现的解耦合利器

当然除了上面,Spring还有很多其它核心功能,就比如AOP、SpEL表达式等等

由于AOP涉及到Bean生命周期,本篇文章也没有涉及到Bean生命周期的讲解,所以这里就不讲了,后面有机会再讲

至于SpEL他是Spring提供的表达式语言,主要是语法,解析语法的一些东西,这里也就不讲了

好了,本文就讲到这里,如果觉得本篇文章对你有所帮助,还请多多点赞、转发、在看,非常感谢!!

  • 25
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yaml墨韵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值