文章目錄
在上篇文章中我們已經對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,加油!共勉!
掃描下方二維碼,關注我的公衆號,更多精彩文章在等您!~~