Spring官网阅读(十二)ApplicationContext详解(中)
文章目录
在上篇文章中我们已经对ApplicationContext的一部分内容做了介绍,ApplicationContext主要具有以下几个核心功能:
- 国际化
- 借助Environment接口,完成了对Spring运行环境的抽象,可以返回环境中的属性,并能出现出现的占位符
- 借助于Resource系列接口,完成对底层资源的访问及加载
- 继承了ApplicationEventPublisher接口,能够进行事件发布监听
- 负责创建、配置及管理Bean
在上篇文章我们已经分析学习了1,2两点,这篇文章我们继续之前的学习
文章目录
1、Spring的资源(Resource)
首先需要说明的是,Spring并没有让ApplicationContext直接继承Resource接口,就像ApplicationContext接口也没有直接继承Environment接口一样。这应该也不难理解,采用这种组合的方式会让我们的类更加的轻量,也起到了解耦的作用。ApplicationContext跟Resource相关的接口的继承关系如下
不管是ResourceLoader还是ResourcePatternResolver其实都是为了获取Resource对象,不过ResourcePatternResolver在ResourceLoader的基础上扩展了一个获取多个Resource的方法,我们在后文会介绍。
接口简介
Resouce接口继承了 InputStreamSource.
public interface InputStreamSource {
// 每次调用都将返回一个当前资源对应的java.io. InputStream字节流
InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
// 用于判断对应的资源是否真的存在
boolean exists();
// 用于判断对应资源的内容是否可读。需要注意的是当其结果为true的时候,其内容未必真的可读,但如果返回false,则其内容必定不可读
default boolean isReadable() {
return exists();
}
// 用于判断当前资源是否代表一个已打开的输入流,如果结果为true,则表示当前资源的输入流不可多次读取,而且在读取以后需要对它进行关闭,以防止内存泄露。该方法主要针对于实现类InputStreamResource,实现类中只有它的返回结果为true,其他都为false。
default boolean isOpen() {
return false;
}
// 当前资源是否是一个文件
default boolean isFile() {
return false;
}
//当前资源对应的URL。如果当前资源不能解析为一个URL则会抛出异常
URL getURL() throws IOException;
//当前资源对应的URI。如果当前资源不能解析为一个URI则会抛出异常
URI getURI() throws IOException;
// 返回当前资源对应的File。如果当前资源不能以绝对路径解析为一个File则会抛出异常。
File getFile() throws IOException;
// 返回一个ReadableByteChannel
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
// 返回资源的长度
long contentLength() throws IOException;
// 最后修改时间
long lastModified() throws IOException;
// 根据当前资源以及相对当前资源的路径创建一个新的资源,比如当前Resource代表文件资源“d:/abc/a.java”,则createRelative(“xyz.txt”)将返回表文件资源“d:/abc/xyz.txt”
Resource createRelative(String relativePath) throws IOException;
// 返回文件一个文件名称,通常来说会返回该资源路径的最后一段
@Nullable
String getFilename();
// 返回描述信息
String getDescription();
}
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,其接口定义如下
public interface ResourceLoader {
// classpath:
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
// 核心方法,从指定位置加载一个Resource
// 1.支持权限的的URL格式,如:file:C:/test.dat
// 2.支持classpath的格式,如:classpath:test.dat
// 3.支持文件相对路径,如:WEB-INF/test.dat
Resource getResource(String location);
// 返回用于加载该资源的ClassLoader
@Nullable
ClassLoader getClassLoader();
}
UML类图
对于一些不是很必要的类我都省略了,其实核心的类我们只需要关注DefaultResourceLoader
就可以了,因为其余子类(除了GenericApplicationContext
)都是直接继承了DefaultResourceLoader
的getResource
方法。代码如下:
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
// 正常来说protocolResolvers集合是空的,除非我们调用了它的addProtocolResolver方法添加了自定义协议处理器,调用addProtocolResolver方法所添加的协议处理器会覆盖原有的处理逻辑
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
// 如果是以“/”开头,直接返回一个classpathResource
if (location.startsWith("/")) {
return getResourceByPath(location);
}
// 如果是形如:classpath:test.dat也直接返回一个ClassPathResource
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// 否则将其解析为一个URL
URL url = new URL(location);
// 如果是一个文件,直接返回一个FileUrlResource,否则返回一个普通的UrlResource
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// 如果URL转换失败,还是作为一个普通的ClassPathResource
return getResourceByPath(location);
}
}
}
资源路径
ant-style
类似下面这种含有通配符的路径
/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml
classpath跟classpath*
classpath:用于加载类路径(包括jar包)中的一个且仅一个资源;
classpath*:用于加载类路径(包括jar包)中的所有匹配的资源,可使用Ant路径模式。
2、Spring中的事件监听机制(publish-event)
我们知道,ApplicationContext接口继承了ApplicationEventPublisher接口,能够进行事件发布监听,那么什么是事件的发布跟监听呢?我们从监听者模式说起
监听者模式
概念
事件源经过事件的封装传给监听器,当事件源触发事件后,监听器接收到事件对象可以回调事件的方法
Spring对监听者模式的实践
我们直接通过一个例子来体会下
public class Main {
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,继承了ApplicationEvent
- 事件监听器:EventListener,实现了ApplicationListener
我们通过ApplicationEventPublisher发布了一个事件(MyEvent),然后事件监听器监听到了事件,并进行了对应的处理。
接口简介
ApplicationEventPublisher
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
// 从版本4.2后新增的方法
// 调用这个方法发布的事件不需要实现ApplicationEvent接口,会被封装成一个PayloadApplicationEvent
// 如果event实现了ApplicationEvent接口,则会正常发布
void publishEvent(Object event);
}
对于这个接口,我只需要关注有哪些子类是实现了publishEvent(Object event)
这个方法即可。搜索发现,我们只需要关注org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object)
这个方法即可,关于这个方法在后文的源码分析中我们再详细介绍。
ApplicationEvent
继承关系如下:
我们主要关注上面4个类(PayloadApplicationEvent在后文源码分析时再介绍),下面几个都是Spring直接在内部使用到了的事件,比如ContextClosedEvent,在容器关闭时会被创建然后发布。
// 这个类在设计时是作为整个应用内所有事件的基类,之所以被设计成抽象类,是因为直接发布这个对象没有任何意义
public abstract class ApplicationEvent extends EventObject {
// 事件创建的事件
private final long timestamp;
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
public final long getTimestamp() {
return this.timestamp;
}
}
// 这个类是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 + "]";
}
}
// 这个类是2.5版本时增加的一个类,相对于它直接的父类ApplicationEvent而言,最大的区别就是
// 将source规定为了当前的容器。就目前而言的话这个类作用不大了,一般情况下我们定义事件也不一定需要继承这个ApplicationContextEvent
// 后面我会介绍注解的方式进行事件的发布监听
public abstract class ApplicationContextEvent extends ApplicationEvent {
public ApplicationContextEvent(ApplicationContext source) {
super(source);
}
public final ApplicationContext getApplicationContext() {
return (ApplicationContext) getSource();
}
}
ApplicationListener
// 事件监听器,实现了java.util包下的EventListener接口
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
// 根据接口申明的泛型类型处理对应的事件
// 比如在我们之前的例子中,通过《EventListener implements ApplicationListener<MyEvent>》
// 在接口中申明了泛型类型为MyEvent,所以能监听到MyEvent这一类事件
void onApplicationEvent(E event);
}
注解方式实现事件发布机制
在上面的例子中,我们通过传统的方式实现了事件的发布监听,但是上面的过程实在是有点繁琐,我们发布的事件需要实现指定的接口,在进行监听时又需要实现指定的接口。每增加一个发布的事件,代表我们需要多两个类。这样在项目的迭代过程中,会导致我们关于事件的类越来越多。所以,在Spring4.2版本后,新增一个注解,让我们可以快速的实现对发布的事件的监听。示例代码如下:
@ComponentScan("com.dmz.official.event")
public class Main02 {
public static void main(String[] args) {
ApplicationEventPublisher publisher = new AnnotationConfigApplicationContext(Main02.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
注解,直接标注了Listener类中的一个方法是一个事件监听器,并且通过方法的参数类型Event指定了这个监听器监听的事件类型为Event类型。在这个例子中,第一,我们事件不需要去继承特定的类,第二,我们的监听器也不需要去实现特定的接口,极大的方便了我们的开发。
异步的方式实现事件监听
对于上面的例子,我们只需要按下面这种方式添加两个注解即可实现异步:
@ComponentScan("com.dmz.official.event")
@Configuration
@EnableAsync // 1.开启异步支持
public class Main02 {
public static void main(String[] args) {
AnnotationConfigApplicationContext publisher = new AnnotationConfigApplicationContext(Main02.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)方法
,源码如下:
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
// 如果发布的事件是一个ApplicationEvent,直接发布
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
// 如果发布的事件不是一个ApplicationEvent,包装成一个PayloadApplicationEvent
applicationEvent = new PayloadApplicationEvent<>(this, event);
// 我们在应用程序中发布事件时,这个eventType必定为null
if (eventType == null) {
// 获取对应的事件类型
eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
}
}
// 我们在自己的项目中调用时,这个earlyApplicationEvents必定为null
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
// 获取事件发布器,发布对应的事件
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// 父容器中也需要发布事件
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
上面这段代码核心部分就是getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
,我们分成以下几个部分分析
- getApplicationEventMulticaster()方法
- multicastEvent()方法
getApplicationEventMulticaster()方法
代码如下:
ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
if (this.applicationEventMulticaster == null) {
throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
"call 'refresh' before multicasting events via the context: " + this);
}
return this.applicationEventMulticaster;
}
可以看到,只是简单的获取容器中已经初始化好的一个ApplicationEventMulticaster
,那么现在有以下几问题。
1、ApplicationEventMulticaster是什么?
- 接口定义
public interface ApplicationEventMulticaster {
// 添加事件监听器
void addApplicationListener(ApplicationListener<?> listener);
// 通过名称添加事件监听器
void addApplicationListenerBean(String listenerBeanName);
// 移除事件监听器
void removeApplicationListener(ApplicationListener<?> listener);
// 根据名称移除事件监听器
void removeApplicationListenerBean(String listenerBeanName);
// 移除注册在这个事件分发器上的所有监听器
void removeAllListeners();
// 分发事件
void multicastEvent(ApplicationEvent event);
// 分发事件,eventType代表事件类型,如果eventType为空,会从事件对象中推断出事件类型
void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}
- UML类图
主要涉及到两个类:
AbstractApplicationEventMulticaster
,这个类对ApplicationEventMulticaster
这个接口基础方法做了实现,除了其核心方法multicastEvent
。这个类最大的作用是获取监听器,稍后我们会介绍。SimpleApplicationEventMulticaster
,这是Spring默认提供的一个事件分发器,如果我们没有进行特别的配置的话,就会采用这个类生成的对象作为容器的事件分发器。
2、容器在什么时候对其进行的初始化
回到我们之前的一张图
可以看到,在第3-8
步调用了一个initApplicationEventMulticaster
方法,从名字上我们就知道,这是对ApplicationEventMulticaster
进行初始化的,我们看看这个方法做了什么。
- initApplicationEventMulticaster方法
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 判断容器中是否包含了一个名为applicationEventMulticaster的ApplicationEventMulticaster类的对象,如果包含,直接获取即可。
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
// 删除不必要的日志
}
// 如果没有包含,new一个SimpleApplicationEventMulticaster并将其注册到容器中
else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
// 删除不必要的日志
}
}
}
这段代码的含义就是告诉我们,可以自己配置一个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方法
在Spring容器中,只内置了一个这个方法的实现类,就是SimpleApplicationEventMulticaster。实现的逻辑如下:
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
上面的代码主要的实现逻辑可以分为这么几步:
- 推断事件类型
- 根据事件类型获取对应的监听器
- 执行监听逻辑
我们一步步分析
- resolveDefaultEventType(event),推断事件类型
private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
return ResolvableType.forInstance(event);
}
public static ResolvableType forInstance(Object instance) {
Assert.notNull(instance, "Instance must not be null");
if (instance instanceof ResolvableTypeProvider) {
ResolvableType type = ((ResolvableTypeProvider) instance).getResolvableType();
if (type != null) {
return type;
}
}
// 返回通过事件的class类型封装的一个ResolvableType
return ResolvableType.forClass(instance.getClass());
}
上面的代码涉及到一个概念就是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.forInstance 获取指定的实例的泛型信息
关于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
方法,执行我们的业务逻辑。但是值得注意的是,在调用invokeListener方法前,会先进行一个判断
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注解实现对事件的异步监听
我更加倾向于这种通过复写方法的方式进行实现,主要原因就是如果通过注解实现,那么所有加了这个注解的方法在异步执行都都是用的同一个线程池,这些加了注解的方法有些可能并不是进行事件监听的,这样显然是不合理的。而后面这种方式,我们可以确保创建的线程池是针对于事件监听的,甚至可以根据不同的事件类型路由到不同的线程池。这样更加合理。
3、总结
在这篇文章中,我们完成了对ApplicationContext中以下两点内容的学习
- 借助于Resource系列接口,完成对底层资源的访问及加载
- 实现事件的发布
对于整个ApplicationContext体系,目前来说还剩一个很大的功能没有涉及到。因为我们也知道ApplicationContext也继承了一系列的BeanFactory接口。所以它还会负责创建、配置及管理Bean。
BeanFactory本身也有一套自己的体系,在下篇文章中,我们就会学习BeanFactory相关的内容。虽然这一系列文章是以ApplicationContext命名的,但是其中的内容覆盖面很广,这些东西对于我们看懂Spring很重要。
希望大家跟我一起慢慢啃掉Spring,加油!共勉!
Spring官网阅读(十二)ApplicationContext详解(中)的更多相关文章
- Spring官网阅读(二)(依赖注入及方法注入)
上篇文章我们学习了官网中的1.2,1.3两小节,主要是涉及了容器,以及Spring实例化对象的一些知识.这篇文章我们继续学习Spring官网,主要是针对1.4小节,主要涉及到Spring的依赖注入.虽 ...
- Spring官网阅读(十六)Spring中的数据绑定
文章目录 DataBinder UML类图 使用示例 源码分析 bind方法 doBind方法 applyPropertyValues方法 获取一个属性访问器 通过属性访问器直接set属性值 1.se ...
- Spring官网阅读 | 总结篇
接近用了4个多月的时间,完成了整个<Spring官网阅读>系列的文章,本文主要对本系列所有的文章做一个总结,同时也将所有的目录汇总成一篇文章方便各位读者来阅读. 下面这张图是我整个的写作大 ...
- Spring官网阅读(十八)Spring中的AOP
文章目录 什么是AOP AOP中的核心概念 切面 连接点 通知 切点 引入 目标对象 代理对象 织入 Spring中如何使用AOP 1.开启AOP 2.申明切面 3.申明切点 切点表达式 excecu ...
- Spring官网阅读(十七)Spring中的数据校验
文章目录 Java中的数据校验 Bean Validation(JSR 380) 使用示例 Spring对Bean Validation的支持 Spring中的Validator 接口定义 UML类图 ...
- Spring官网阅读(三)自动注入
上篇文章我们已经学习了1.4小结中关于依赖注入跟方法注入的内容.这篇文章我们继续学习这结中的其他内容,顺便解决下我们上篇文章留下来的一个问题-----注入模型. 文章目录 前言: 自动注入: 自动注入 ...
- Spring官网阅读(一)容器及实例化
从今天开始,我们一起过一遍Spring的官网,一边读,一边结合在路神课堂上学习的知识,讲一讲自己的理解.不管是之前关于动态代理的文章,还是读Spring的官网,都是为了之后对Spring的源码做更全面 ...
- Spring官网阅读(十三)ApplicationContext详解(下)
文章目录 BeanFactory 接口定义 继承关系 接口功能 1.HierarchicalBeanFactory 2.ListableBeanFactory 3.AutowireCapableBea ...
- Spring官网阅读(十一)ApplicationContext详细介绍(上)
文章目录 ApplicationContext 1.ApplicationContext的继承关系 2.ApplicationContext的功能 Spring中的国际化(MessageSource) ...
随机推荐
- CVE-2019-0232 远程代码执行漏洞-复现
0x00 漏洞介绍 该漏洞是由于Tomcat CGI将命令行参数传递给Windows程序的方式存在错误,使得CGIServlet被命令注入影响. 该漏洞只影响Windows平台,要求启用了CGISer ...
- 通过String的不变性案例分析Java变量的可变性
阅读本文之前,请先看以下几个问题: 1.String变量是什么不变?final修饰变量时的不变性指的又是什么不变,是引用?还是内存地址?还是值? 2.java对象进行重赋值或者改变属性时在内存中是如何 ...
- Linux 下发送邮件
由于种种原因,需要由我这个兼职运维每天发送对账单文件给运营同学,故研究下 Linux 发送邮件,希望对大家有所帮助. 安装 # Centos,安装 mailx $ yum install -y mai ...
- Spring Data REST不完全指南(一)
简介 Spring Data REST是Spring Data项目的一部分,可轻松在Spring Data存储库上构建超媒体驱动的REST Web服务. Spring Data REST 构建在 Sp ...
- day23作业
# 作业: # 1.把登录与注册的密码都换成密文形式 info = {"tom":"202cb962ac59075b964b07152d234b70"} def ...
- Sprint 5 summary: UI 界面更新,Azure端部署和用户反馈分析 12/28/2015
本次sprint主要完成的任务有对手机APP的UI界面的更新,同时对Azure客户端的部署进行了相应的学习和有关的程序设计.同时对于ALPHA release的用户反馈做出相应的分析以确定接下来工作的 ...
- SPFA()判环
1 SPFA()判负环 SPFA()判负环的原理就是在求最短路的过程中,如果存在负环,比如说要求从A到a的最短距离,设为s,但是经过a->c->b->a可以更短,所以如果一直经过a- ...
- openssl进行RSA加解密(C++)
密钥对根据RSA的加密机制(自行查找RSA工作原理),通常可以私钥加密-公钥解密(多用于签名),公钥加密-私钥解密(多用于数据传输加密),私钥可以生成公钥. 密钥对生成生成私钥,长度为2048,默认格 ...
- Eureka源码分析
源码流程图 先上图,不太清晰,抱歉 一.Eureka Server源码分析 从@EnableEurekaServer注解为入口,它是一个标记注解,点进去看 注解内容如下 /** * 激活Eureka服 ...
- mongodb connection refused because too many open connections: 819
Env Debian 9 # 使用通用二进制方式安装 # mongod --version db version v3.4.21-2.19 git version: 2e0631f5e0d868dd5 ...