Spring官網閱讀(十二)ApplicationContext詳解(中)

在上篇文章中我們已經對ApplicationContext的一部分內容做了介紹,ApplicationContext主要具有以下幾個核心功能:

  1. 國際化
  2. 藉助Environment接口,完成了對Spring運行環境的抽象,可以返回環境中的屬性,並能出現出現的佔位符
  3. 藉助於Resource系列接口,完成對底層資源的訪問及加載
  4. 繼承了ApplicationEventPublisher接口,能夠進行事件發佈監聽
  5. 負責創建、配置及管理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獲取資源有兩種方式

  1. 使用Class對象的getResource(String path)獲取資源URL,getResourceAsStream(String path)獲取資源流。 參數既可以是當前class文件相對路徑(以文件夾或文件開頭),也可以是當前class文件的絕對路徑(以“/”開頭,相對於當前classpath根目錄)
  2. 使用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)都是直接繼承了DefaultResourceLoadergetResource方法。代碼如下:

	@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("處理事件....");
		}
	}
}

在上面的例子中,主要涉及到了三個角色,也就是我們之前提到的

  1. 事件源:ApplicationEventPublisher
  2. 事件:MyEvent,繼承了ApplicationEvent
  3. 事件監聽器: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類圖

在這裏插入圖片描述

主要涉及到兩個類:

  1. AbstractApplicationEventMulticaster,這個類對ApplicationEventMulticaster這個接口基礎方法做了實現,除了其核心方法multicastEvent。這個類最大的作用是獲取監聽器,稍後我們會介紹。
  2. 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);
        }
    }
}

上面的代碼主要的實現邏輯可以分爲這麼幾步:

  1. 推斷事件類型
  2. 根據事件類型獲取對應的監聽器
  3. 執行監聽邏輯

我們一步步分析

  • 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對象獲取類型攜帶的信息(舉例如下):

  1. getSuperType():獲取直接父類型
  2. getInterfaces():獲取接口類型
  3. getGeneric(int…):獲取類型攜帶的泛型類型
  4. resolve():Type對象到Class對象的轉換

另外,ResolvableType的構造方法全部爲私有的,我們不能直接new,只能使用其提供的靜態方法進行類型獲取:

  1. forField(Field):獲取指定字段的類型
  2. forMethodParameter(Method, int):獲取指定方法的指定形參的類型
  3. forMethodReturnType(Method):獲取指定方法的返回值的類型
  4. forClass(Class):直接封裝指定的類型
  5. 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中以下兩點內容的學習

  1. 藉助於Resource系列接口,完成對底層資源的訪問及加載
  2. 實現事件的發佈

對於整個ApplicationContext體系,目前來說還剩一個很大的功能沒有涉及到。因爲我們也知道ApplicationContext也繼承了一系列的BeanFactory接口。所以它還會負責創建、配置及管理Bean。

BeanFactory本身也有一套自己的體系,在下篇文章中,我們就會學習BeanFactory相關的內容。雖然這一系列文章是以ApplicationContext命名的,但是其中的內容覆蓋面很廣,這些東西對於我們看懂Spring很重要。

希望大家跟我一起慢慢啃掉Spring,加油!共勉!

掃描下方二維碼,關注我的公衆號,更多精彩文章在等您!~~

公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章