上篇文章已经对ApplicationContext的一部分内容做了介绍,ApplicationContext主要具有以下几个核心功能:
- 国际化
- 借助Environment接口,完成了对 Spring运行环境的抽象,可以返回环境中的属性,并能出现占位符
- 借助于Resource系列接口,完成对底层资源的访问和加载
- 接触了ApplicationEventPublisher接口,能够进行事件发布监听
- 负责创建,配置和管理bean
上篇文章写了1,2,两点,这篇文章继续之前的内容
- Spring的资源(Resource)
首先需要说明的是,Spring并没有让ApplicationContext直接继承了Resource接口,就像ApplicationContext接口并没有直接继承Environment接口一样。这也不难理解,采用这种组合的方式会让我们的类更加轻量,也起到了解耦的作用,ApplicationContext和Resoucre相关的接口的继承关系如下:
不管是ResourceLoader还是ResourcePatternResolver都是为了获取Resource对象,不过ResourceLoader在ResourcePatternResolver的基础上扩展了一个获取多个Resource的方法,后面在介绍。
接口简介
Resource继承了InputStreamSource
UML类图
因为实现了Resource的接口的类很多,并且一些类我们用不到或者很简单,所以上图中省略了一些不重要的分支,接下来就一个个分析。
抽象基类AbstractResource
实现了Resource接口,是大多数Resource实现类的基类,提供了很多通用的方法。比如exists方法会检查是否一个文件或者输入流能够被打开,isOpen永远返回false,getURL和getFile方法会抛出异常,toString将会返回描述信息。
FileSystemResource
基于Java的文件系统封装成一个资源对象。
AbstractFileResolvingResource
将URL解析成文件引用,即会处理协议为:file的URL,也会处理JBoss的vfs协议。然后相应的解析成对应的文件系统引用。
ByteArrayResource
根据一个给定的字节数组构建的一个资源,同时给出一个对应的输入流。
BeanDefinitionResource
只是对BeanDefinition进行的一次描述性的封装
InputStreamResource
是针对输入流封装的资源,它的构建需要一个输入流,对于getInputStream操作将直接返回该字节流,因此只能读取一次该字节流,即isOpen永远返回true。
UrlResource
UrlResource代表URL资源,用于简化URL资源访问。
UrlResource一般支持如下资源访问:
-http:通过标准的http协议访问web的资源,如new UrlResource("http//地址");
-ftp:通过ftp协议访问资源,如new UrlResource("ftp://地址");
-file:通过file协议访问本地文件系统资源,如new UrlResource("file:d/test.txt");
ClassPathResource
JDK获取资源的两种方式:
- 使用Class对象的getResource(String path)获取资源的URL,getResourceAsStream(String path) 获取资源流。参数即可是当前class文件相对路径(以文件夹或文件开头),也可以是当前class文件的绝对路径(以“/”开头,相对于当前classpath根目录)
- 使用ClassLoader对象的getResource(String path)获取资源的URL,getResourceAsStream(String path) 获取资源流。参数只能是绝对路径,但不能与“/”开头
ClassPathResource代表classpath路径的资源,将使用给定的Class或ClassLoader进行加载classpath资源,isOpen永远返回false,表示可多次读取资源
ServletContextResource
是针对于ServletContext封装的资源,用于访问ServletContext环境下的资源。ServletContextResource持有一个ServletContext的引用,其底层是通过ServletContext的getResource方法getResourceAsStream方法来获取资源的。
ResourceLoader
接口简介
ResourceLoader接口被设计用来从指定的位置加载一个Resource,其接口定义如下
UML类图
对于一些不是很必要的类都忽略了,其核心的类只需要关注DefaultResourceLoader就可以了,因为其与子类(除了GenericApplicationContext)都是直接继承了DefaultResourceLoader的getRe方法,代码如下:
资源路径
ant-style
类似于下面这种含有通配符的路径
/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml
// 如果是以“/”开头,直接返回一个classpathResource
classpath和classpath*
classpath:用来加载类路径(包括jar包)中一个且仅一个资源;
classpath*:用来加载类路径(包括jar包)中所有配置的资源,可使用Ant路径模式。
- Spring中的事件监听机制(publish-event)
我们知道,ApplicationContext接口继承了ApplicationContextEventPublisher接口,能够进行事件发布监听,那么什么是事件的发布和监听呢?我们从监听者模式说起
监听者模式
概念
事件源经过事件的封装传给监听器,当事件源触发事件后,监听器接收到事件对象可以回调事件的方法。
Spring对监听者模式的实践
直接通过一个例子来体会:
public class Test {
public static void main(String[] args) {
// 创建一个事件发布器(事件源),为了方便,我这里直接通过传入EventListener.class来将监听器注册到容器中
ApplicationEventPublisher ac = new AnnotationConfigApplicationContext(EventListener.class);
// 发布一个事件
ac.publishEvent(new MyEvent("hello event"));
// 程序会打印如下:
// 接收到事件:hello event
// 处理事件....
}
static class MyEvent extends ApplicationEvent {
public MyEvent(Object source) {
super(source);
}
}
@Component
static class EventListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
System.out.println("接收到事件:" + event.getSource());
System.out.println("处理事件....");
}
}
}
在上面的例子中,主要涉及到了三个角色,也就是我们之前提到的
- 事件源:ApplicationEventPublisher
- 事件:MyEvent
- 事件监听器:EventListener ,实现了ApplicationListener
我们通过ApplicationEventPublisher发布了一个事件(MyEvent),然后事件监听器监听到了事件,并进行对应的处理。
接口简介
ApplicationEventPublisher
对于这个接口,只需要关注有哪些子类是实现了publishEvent(Object event)这个方法即可。
搜索发现,我们只需要关注org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object)这个方法即可,关于这个方法在后文的源码分析在详细介绍。
ApplicationEvent
继承关系如下:
我们主要关注上面4个类(PayloadApplicationEvent在后文源码分析时再介绍),下面几个都是Spring直接在内部使用到的事件,比如ContextClosedEvent,在容器关闭时被创建然后发布
// 这个类是java的util包下的一个类,java本身也具有一套事件机制
public class EventObject implements java.io.Serializable {
// 事件所发生的那个源,比如在java中,我们发起了一个鼠标点击事件,那么这个source就是鼠标
protected transient Object source;
public EventObject(Object source) {
if (source == null)
throw new IllegalArgumentException("null source");
this.source = source;
}
public Object getSource() {
return source;
}
public String toString() {
return getClass().getName() + "[source=" + source + "]";
}
}
ApplicationListener
注解方式实现事件发布机制
在上面的例子中,我们通过传统的方式实现了事件的发布监听,但是上面的过程是在有点繁琐,我们发布的事件需要实现指定的接口,在进行监听时又需要实现指定的接口。每增加一个发布的事件,代表需要多两个类,这样在项目的迭代过程中,会导致我们关于事件的类越来越大,所以在Spring4.2版本后,新增了一个注解,让我们可以快速的实现对发布的事件监听。示例代码:
@ComponentScan("com.wxx.official.event")
public class Test {
public static void main(String[] args) {
ApplicationEventPublisher publisher = new AnnotationConfigApplicationContext(Test.class);
publisher.publishEvent(new Event("注解事件"));
// 程序打印:
// 接收到事件:注解事件
// 处理事件
}
static class Event {
String name;
Event(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
@Component
static class Listener {
@EventListener
public void listen(Event event) {
System.out.println("接收到事件:" + event);
System.out.println("处理事件");
}
}
可以看到上面的例子中,我们使用一个@EventListener注解,直接标注了Listen类中的一个方法是一个事件监听器,并且通过方法的参数类型Event指定了这个监听器的事件类型为Event类型。在这个例子中,第一,我们的事件不需要去继承特定的类,第二,我们的监听器也不需要去实现特定的接口,极大的方便我们的开发。
异步的方式实现事件监听
对于上面的例子,只需要按下面这种方式添加两个注解即可完成异步:
@ComponentScan("com.wxx.official.event")
@Configuration
@EnableAsync // 1.开启异步支持
public class MyEvent {
public static void main(String[] args) {
ApplicationEventPublisher publisher = new AnnotationConfigApplicationContext(MyEvent.class);
publisher.publishEvent(new Event("注解事件"));
// 程序打印:
// 接收到事件:注解事件
// 处理事件
}
static class Event {
String name;
Event(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
@Component
static class Listener {
@EventListener
@Async
// 2.标志这个方法需要异步执行
public void listen(Event event) {
System.out.println("接收到事件:" + event);
System.out.println("处理事件");
}
}
对于上面的两个注解@EnableAsync以及@Async,我会在AOP系列的文章中再做介绍,目前而言,知道能通过这种方式开启异步支持即可。
对监听器进行排序
当我们发布一个事件时,可能会同时被两个监听器监听到,比如在我们上面的例子中如果同时存在两个监听器,如下:
@Component
static class Listener {
@EventListener
public void listen1(Event event) {
System.out.println("接收到事件1:" + event);
System.out.println("处理事件");
}
@EventListener
public void listen2(Event event) {
System.out.println("接收到事件2:" + event);
System.out.println("处理事件");
}
}
在这种情况下,我们可能希望两个监听器可以按顺序执行,这个时候需要用到另一个注解了:@Order
@Component
static class Listener {
@EventListener
@Order(2)
public void listen1(Event event) {
System.out.println("接收到事件1:" + event);
System.out.println("处理事件");
}
@EventListener
@Order(1)
public void listen2(Event event) {
System.out.println("接收到事件2:" + event);
System.out.println("处理事件");
}
}
注解中的参数越小,代表优先级越高,在上面的例子中,会先执行listen2方法再执行listen1方法
那Spring到底如何实现这一套事件发布机制呢?接下来我们进行源码分析
源码分析(publishEvent方法)
我们需要分析的代码主要是org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)方法,源码如下:
上面这段代码核心部分就是getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);,我们分成几个部分解析:
- getApplicationEventMulticaster()
- multicastEvent(applicationEvent, eventType)
getApplicationEventMulticaster()方法代码如下:
可以看到,只是简单的获取容器中已经初始化好的一个applicationEventMulticaster,那么现在有几个问题:
- applicationEventMulticaster是什么?
- 接口定义
- UML类图
主要涉及到两个类:
- AbstractApplicationEventMulticaster,这个类对ApplicationEventMulticaster这个接口基础方法做了实现,除了核心方法multicastEvent,这个类最大的作用是获取监听器。稍后在介绍.。
- SimpleApplicationEventMulticaster,这是Spring默认提供的一个事件分发器,如果我们没有进行特别配置的话,就会采用这个类生成的对象作为容器的事件分发器。
- 容器在什么时候对其进行的初始化
回到我们之前的图:
可以看到,在3-8调用了一个initApplicationEventMulticaster方法,从名字我们就知道,这是对ApplicationEventMulticaster进行的初始化,看看这个方法做了什么。
- initApplicationEventMulticaster方法
这段代码的含义就是告诉我们,可以自己配置一个ApplicationEventMulticaster,如果没有进行配置,那么将默认使用一个SimpleApplicationEventMulticaster。
接下来,我们尝试自己配置一个简单的ApplicationEventMulticaster,示例代码如下:
@Component("applicationEventMulticaster")
static class MyEventMulticaster extends AbstractApplicationEventMulticaster {
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public void multicastEvent(@NonNull ApplicationEvent event) {
ResolvableType resolvableType = ResolvableType.forInstance(event);
Collection<ApplicationListener<?>> applicationListeners = getApplicationListeners(event, resolvableType);
for (ApplicationListener applicationListener : applicationListeners) {
applicationListener.onApplicationEvent(event);
}
}
@Override
public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
System.out.println("进入MyEventMulticaster");
}
运行程序后会发现“进入MyEventMulticaster”这句话打印了两次,第一次是容器启动时会发布一个ContextStartedEvent事件,也会调用我们配置的事件分发器进行事件发布。
- multicastEvent(applicationEvent, eventType)
在Spring容器中,只内置了一个这个方法的实现类,就是SimpleApplicationEventMulticaster,实现的逻辑如下:
上面代码主要的实现逻辑可以分为三步:
- 推断事件类型
- 根据事件类型获取对应的监听器
- 执行监听逻辑
我们一步步分析
- resolveDefaultEventType(event),推断事件类型
上面代码涉及到一个概念就是ResolvableType,对于ResolvableType我们需要了解的是,ResolvableType为所有Java类型提供了统一的数据结构和API,换句话说,一个ResolvableType对象就对应着一种Java类型。可以通过ResolvableType对象获取类型携带的信息(举例如下):
- getSuperType():获取直接父类型
- getInterfaces():获取接口类型
- getGeneric(int...):获取类型携带的泛型类型
- resolve():Type对象到Class对象的转换
另外,ResolvableType的构造方法全部为私有,我们不能直接new,只能使用其提供的静态方法案进行类型获取:
- forField(Field):获取指定字段的类型
- forMethodParameter(Method, int):获取指定方法的指定形参的类型
- forMethodReturnType(Method):获取指定方法的返回值的类型
- forClass(Class):直接封装指定的类型
- ResolvableType.forInsance:获取指定的实例的泛型信息
关于ResolvableType和Java的类型中的关系请关注我的后续文章,限于篇幅原因在本文就不做过多介绍了。
- getApplicationListeners(event, type),获取对应的事件监听器
事件监听器主要分为两种,一种是我们通过实现接口直接注册到容器的bean,例如下面这种
@Component
static class EventListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
System.out.println("接收到事件:" + event.getSource());
System.out.println("处理事件....");
}
}
另外一种是通过注解的方式,就是下面这种
@Component
static class Listener {
@EventListener
public void listen1(Event event) {
System.out.println("接收到事件1:" + event);
System.out.println("处理事件");
}
}
对于实现接口的方式不用多说,因为实现了这个类本身就会被扫描然后加入到容器中。对于注解这种方式,Spring是通过一个回调方法实现的。大家关注下这个接口org.springframework.beans.factory.SmartInitializingSingleton,同时找到其实现类,org.springframework.context.event.EventListenerMethodProcessor。在这个类中,会先调用afterSingletonsInstantiated方法,然后调用一个processBean方法,在这个方法中会遍历所有容器中的bean,然后遍历bean中 的每一个方法判断方法上是否加了一个@EventListener注解。如果添加了这个注解,会将这个Method方法包装成一个ApplicationListenerMethodAdapter,这个类本身也实现了ApplicationListener接口,之后再添加到监听器的集合中。
- invokeListener,执行监听逻辑
本身这个方法没什么好说的,就是调用了ApplicationListener中的onApplicationEvent方法,执行我们的业务逻辑。但是值得注意的是,在调用onApplicationEvent方法前,会先进行一个判断
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
会先判断是否获取到一个Executor,如果能获取到那么会通过Executor异步执行监听的逻辑。所以基于这段代码,我们可以不通过@Async注解实现对事件的异步监听,而是复写SimpleApplicationEventMulticaster这个类的方法,如下:
@Component("applicationEventMulticaster")
public class MyEventMulticaster extends SimpleApplicationEventMulticaster {
@Override
public Executor getTaskExecutor() {
// 在这里创建自己的执行器
return executor();
}
}
相比于通过@Async注解实现对事件的异步监听,我更倾向于通过复写的方式进行实现,主要原因就是如果通过注解实现,那么所有加了这个注解的方法在异步执行都是用的同一个线程池,这些加了注解的方法有些可能并不是进行事件监听的,这样显然是不合理的。而后面这种方式,我们可以确保创建的线程池是针对事件监听的,甚至可以根据不同的事件类型路由到不同的线程池。
总结
在这篇文章中,完成了对ApplicationContext中以下两点内容
- 借助于Resource一系列接口,完成对底层资源的访问及加载
- 实现事件的发布
对于整个ApplicationContext体系,目前来说还剩一个很大的功能没涉及到。因为我们也知道ApplicationContext继承了一系列的BeanFactory接口,所以它还会负责创建,管理及配置bean。
BeanFactory本身也有一套自己的体系,在下篇文章中,就写BeanFactory相关的内容。虽然这一系列文章是以ApplicationContext命名的,但是其中的内容覆盖面很广,这些东西对于我们看懂Spring很重要
希望大家跟我一起慢慢啃掉Spring,加油!共勉!
标签:ApplicationContext,Spring,class,event,源码,事件,监听器,println,public From: https://blog.51cto.com/u_15668812/7373957