Nacos对于PropertySourceLoader的实现
如果你的项目正在用Nacos作为配置中心,那么刚刚好,Nacos已经实现json配置文件格式的解析。
Nacos不仅实现了json格式的解析,也实现了关于xml格式的配置文件的解析,并且优先级会比SpringBoot默认的xml格式文件解析的优先级高。至于Nacos为啥需要实现PropertySourceLoader?其实很简单,因为Nacos作为配置中心,不仅支持properties和yaml格式的文件,还支持json格式的配置文件,那么客户端拿到这些配置就需要解析,SpringBoot已经支持了properties和yaml格式的文件的解析,那么Nacos只需要实现SpringBoot不支持的就可以了。
3、ApplicationContextInitializer
ApplicationContextInitializer也是SpringBoot启动过程的一个扩展点。
在SpringBoot启动过程,会回调这个类的实现initialize方法,传入ConfigurableApplicationContext。
那怎么用呢?
依然是SPI。
然后遍历所有的实现,依次调用
这里就不演示了,实现接口,按照如下这种配置就行了
但是这里需要注意的是,此时传入的ConfigurableApplicationContext并没有调用过refresh方法,也就是里面是没有Bean对象的,一般这个接口是用来配置ConfigurableApplicationContext,而不是用来获取Bean的。
4、EnvironmentPostProcessor
EnvironmentPostProcessor在SpringBoot启动过程中,也会调用,也是通过SPI机制来加载扩展的。
EnvironmentPostProcessor是用来处理ConfigurableEnvironment的,也就是一些配置信息,SpringBoot所有的配置都是存在这个对象的。
说这个类的主要原因,主要不是说扩展,而是他的一个实现类很关键。
这个类的作用就是用来处理外部化配置文件的,也就是这个类是用来处理配置文件的,通过前面提到的PropertySourceLoader解析配置文件,放到ConfigurableEnvironment里面。
5、ApplicationRunner和CommandLineRunner
ApplicationRunner和CommandLineRunner都是在SpringBoot成功启动之后会调用,可以拿到启动时的参数。
那怎么扩展呢?
当然又是SPI了
这两个其实不是通过SPI机制来扩展,而是直接从容器中获取的,这又是为啥呢?
因为调用ApplicationRunner和CommandLineRunner时,SpringBoot已经启动成功了,Spring容器都准备好了,需要什么Bean直接从容器中查找多方便。
而前面说的几个需要SPI机制的扩展点,是因为在SpringBoot启动的时候,Spring容器还没有启动好,也就是无法从Spring容器获取到这些扩展的对象,为了兼顾扩展性,所以就通过SPI机制来实现获取到实现类。
所以要想扩展这个点,只需要实现接口,添加到Spring容器就可以了。
Spring Event 事件
Event 事件可以说是一种观察者模式的实现,主要是用来解耦合的。当发生了某件事,只要发布一个事件,对这个事件的监听者(观察者)就可以对事件进行响应或者处理。
举个例子来说,假设发生了火灾,可能需要打119、救人,那么就可以基于事件的模型来实现,只需要打119、救人监听火灾的发生就行了,当发生了火灾,通知这些打119、救人去触发相应的逻辑操作。
什么是Spring Event 事件
那么是什么是Spring Event 事件,就是Spring实现了这种事件模型,你只需要基于Spring提供的API进行扩展,就可以完成事件的发布订阅
Spring提供的事件api:
ApplicationEvent
事件的父类,所有具体的事件都得继承这个类,构造方法的参数是这个事件携带的参数,监听器就可以通过这个参数来进行一些业务操作。
ApplicationListener
事件监听的接口,泛型是子类需要监听的事件类型,子类需要实现onApplicationEvent,参数就是事件类型,onApplicationEvent方法的实现就代表了对事件的处理,当事件发生时,Spring会回调onApplicationEvent方法的实现,传入发布的事件。
ApplicationEventPublisher
事件发布器,通过publishEvent方法就可以发布一个事件,然后就可以触发监听这个事件的监听器的回调。
ApplicationContext实现了ApplicationEventPublisher接口,所以通过ApplicationContext就可以发布事件。
那怎么才能拿到ApplicationContext呢?
前面Bean生命周期那节说过,可以通过ApplicationContextAware接口拿到,甚至你可以通过实现ApplicationEventPublisherAware直接获取到ApplicationEventPublisher,其实获取到的ApplicationEventPublisher也就是ApplicationContext,因为是ApplicationContext实现了ApplicationEventPublisher。
话不多说,上代码
就以上面的火灾为例
第一步:创建一个火灾事件类
火灾事件类继承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) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//将 事件监听器 注册到容器中
applicationContext.register(Call119FireEventListener.class);
applicationContext.register(SavePersonFireEventListener.class);
applicationContext.refresh();
// 发布着火的事件,触发监听
applicationContext.publishEvent(new FireEvent("着火了"));
}
}
将两个事件注册到Spring容器中,然后发布FireEvent事件
运行结果:
打119
救人
控制台打印出了结果,触发了监听。
如果现在需要对火灾进行救火,那么只需要去监听FireEvent,实现救火的逻辑,注入到Spring容器中,就可以了,其余的代码根本不用动。
Spring内置的事件
Spring内置的事件很多,这里我罗列几个
在Spring容器启动的过程中,Spring会发布这些事件,如果你需要这Spring容器启动的某个时刻进行什么操作,只需要监听对应的事件即可。
Spring事件的传播
Spring事件的传播是什么意思呢?
我们都知道,在Spring中有子父容器的概念,而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这些不同的服务的配置相互隔离,会创建很多的子容器,而这些子容器都有一个公共的父容器,那就是SpringBoot项目启动时创建的容器,事件的监听器都在这个容器中。而这些为了配置隔离创建的子容器,在容器启动的过程中,也会发布诸如ContextRefreshedEvent等这样的事件,如果你监听了这些事件,那么由于传播特性的关系,你的这个事件的监听器就会触发多次。
如何解决这个坑呢?
你可以进行判断这些监听器有没有执行过,比如加一个判断的标志;或者是监听类似的事件,比如ApplicationStartedEvent事件,这种事件是在SpringBoot启动中发布的事件,而子容器不是SpringBoot,所以不会多次发这种事件,也就会只执行一次。
Spring事件的运用举例
1、在Mybatis中的使用
又来以Mybatis举例了。。Mybatis的SqlSessionFactoryBean监听了ApplicationEvent,然后判断如果是ContextRefreshedEvent就进行相应的处理,这个类还实现了FactoryBean接口。。
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (failFast && event instanceof ContextRefreshedEvent) {
// fail-fast -> check all statements are completed
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
}
说实话,这监听代码写的不太好,监听了ApplicationEvent,那么所有的事件都会回调这个类的onApplicationEvent方法,但是onApplicationEvent方法实现又是当ApplicationEvent是ContextRefreshedEvent类型才会往下走,那为什么不直接监听ContextRefreshedEvent呢?
2、在SpringCloud的运用
在SpringCloud的中,当项目启动的时候,会自动往注册中心进行注册,那么是如何实现的呢?当然也是基于事件来的。当web服务器启动完成之后,就发布ServletWebServerInitializedEvent事件。
然后不同的注册中心的实现都只需要监听这个事件,就知道web服务器已经创建好了,那么就可以往注册中心注册服务实例了。如果你的服务没往注册中心,看看是不是web环境,因为只有web环境才会发这个事件。
SpringCloud提供了一个抽象类 AbstractAutoServiceRegistration,实现了对WebServerInitializedEvent(ServletWebServerInitializedEvent的父类)事件的监听
一般不同的注册中心都会去继承这个类,监听项目启动,实现往注册中心服务端进行注册。
Spring Event事件在Spring内部中运用很多,是解耦合的利器。在实际项目中,你既可以监听Spring/Boot内置的一些事件,进行相应的扩展,也可以基于这套模型在业务中自定义事件和相应的监听器,减少业务代码的耦合。