07.Spring Bean 加載 - BeanDefinitionReader
基本概念
BeanDefinitionReader ,該接口的作用就是加載 Bean。
在 Spring 中,Bean 一般來說都在配置文件中定義。而在配置的路徑由在 web.xml 中定義。所以加載 Bean 的步驟大致就是:
-
加載資源,通過配置文件的路徑(Location)加載配置文件(Resource)
-
解析資源,通過解析配置文件的內容得到 Bean。
下面來看它的接口定義:
public interface BeanDefinitionReader {
BeanDefinitionRegistry getRegistry();
ResourceLoader getResourceLoader();
ClassLoader getBeanClassLoader();
BeanNameGenerator getBeanNameGenerator();
// 通過 Resource 加載 Bean
int loadBeanDefinitions(Resource resource)
throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources)
throws BeanDefinitionStoreException;
// 通過 location 加載資源
int loadBeanDefinitions(String location)
throws BeanDefinitionStoreException;
int loadBeanDefinitions(String... locations)
throws BeanDefinitionStoreException;
}
具體的繼承關系如下:
流程分析
首先來看 Spring Ioc 容器從啟動開始到調用 BeanDefinitionReader 加載 Bean 的過程如下:
註意:由於這裏采用 XML 文件作為 Spring 的配置文件,所以默認調用 XmlBeanDefinitionReader 來處理。
1.通過 BeanFactory 加載 Bean
在 Spring 容器(ApplicationContext)內部存在一個內部容器(BeanFactory)負責 Bean 的創建與管理。
在創建完 BeanFactory ,下一步就是要去加載 Bean。它由 loadBeanDefinitions 方法負責。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException {
// 1.創建 BeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader =
new XmlBeanDefinitionReader(beanFactory);
// 2.設置 BeanDefinitionReader 的相關屬性
// 2.1.設置 Environment,即環境,與容器的環境一致
beanDefinitionReader.setEnvironment(getEnvironment());
// 2.2.設置 ResourceLoader,即資源加載器,因為容器實現了該接口,具體加載資源的功能
beanDefinitionReader.setResourceLoader(this);
// 2.3.設置 EntityResolver,即實體解析器,這裏用於解析資源加載器加載的資源內容
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 3.初始化 BeanDefinitionReader ,空方法
initBeanDefinitionReader(beanDefinitionReader);
// 4.通過 BeanDefinitionReader 加載 Bean
loadBeanDefinitions(beanDefinitionReader);
}
// 構造函數
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
// 內部 BeanFactory 被當作 Bean 註冊器
super(registry);
}
觀察代碼,該方法的主要目的是創建了 BeanDefinitionReader ,並由它去加載 Bean。具體過程如下:
- 創建 BeanDefinitionReader
- 設置 BeanDefinitionReader 的相關屬性
- 初始化 BeanDefinitionReader
- 通過 BeanDefinitionReader 加載 Bean
2.通過 BeanDefinitionReader 加載 Bean
在創建完 BeanDefinitionReader 後,則就需要通過它來 加載 Bean,過程如下:
// 通過 BeanDefinitionReader 加載 Bean
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader)
throws IOException {
// 取得 Spring 容器的所有配置文件
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
// 調用 BeanDefinitionReader 加載 Bean
reader.loadBeanDefinitions(configLocation);
}
}
}
3.通過 Location 加載 Bean
加載的 Bean 的責任被交給了 BeanDefinitionReader ,下面來看看該類的 loadBeanDefinitions 方法。
public int loadBeanDefinitions(String location)
throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
public int loadBeanDefinitions(String location, Set<Resource> actualResources)
throws BeanDefinitionStoreException {
// 1.取得資源加載器,即容器本身
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
// 拋出異常...
}
// 判斷資源加載器類型
if (resourceLoader instanceof ResourcePatternResolver) {
// 說明該 ResourceLoader 可以基於路徑加載多個資源
try {
// 2.加載資源
Resource[] resources =
((ResourcePatternResolver) resourceLoader).getResources(location);
// 3.通過 Resource 加載 Bean
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
// 省略代碼...
return loadCount;
}catch (IOException ex) {
// 拋出異常...
}
}else {
// 表示 ResourceLoader 只能加載一個資源
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
// 省略代碼...
return loadCount;
}
}
觀察代碼,該方法的工作流程可分為四個步驟:
-
取得資源加載器
-
加載資源,通過 location 利用 ResourceLoader 加載
-
通過 Resource 加載 Bean
4.通過 Resource 加載 Bean
在得到資源(Resource)後,該方法對它進行了封裝,使其變成一個 EncodedResource 對象。
public int loadBeanDefinitions(Resource resource)
throws BeanDefinitionStoreException {
// EncodedResource 表示編碼類型的資源
return loadBeanDefinitions(new EncodedResource(resource));
}
// 構造函數
public EncodedResource(Resource resource, String encoding) {
// 封裝資源,並帶上編碼類型
this(resource, encoding, null);
}
5.通過 EncodedResource 加載 Bean
public int loadBeanDefinitions(EncodedResource encodedResource)
throws BeanDefinitionStoreException {
// 省略代碼...
// 1.取得[已加載的資源]的集合,用於存在已加載的資源。空,則創建。
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
// 2.將當前資源加入集合
if (!currentResources.add(encodedResource)) {
// 拋出異常...
}
try {
// 3.將資源轉換成流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//5.通過流創建 InputSource ,並設置編碼
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//6.通過 InputSource 加載 Bean
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}finally {
inputStream.close();
}
}catch (IOException ex) {
// 拋出異常...
}finally {
// 移除已完成解析的資源
currentResources.remove(encodedResource);
// 集合為空,則一並刪除
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
- 取得已加載的資源集合
- 將當前資源添加到集合
- 將資源轉換成流
- 通過流創建 InputSource ,並設置編碼
- 通過 InputSource 加載 Bean
6.通過 InputSource 加載 Bean
之所以把 Resource 轉換成 InputSource ,就是為了 SAX 解析作準備。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 1.解析 XML 文件
Document doc = doLoadDocument(inputSource, resource);
// 2.註冊 Bean
return registerBeanDefinitions(doc, resource);
}catch (BeanDefinitionStoreException ex) {
// 拋出異常...
}catch (SAXParseException ex) {
// 拋出異常...
}catch (SAXException ex) {
// 拋出異常...
}catch (ParserConfigurationException ex) {
// 拋出異常...
}catch (IOException ex) {
// 拋出異常...
}catch (Throwable ex) {
// 拋出異常...
}
}
// 解析 XML 文件,並返回 Document 對象。
protected Document doLoadDocument(InputSource inputSource, Resource resource)
throws Exception {
return this.documentLoader.loadDocument(
inputSource,
getEntityResolver(),
this.errorHandler,
getValidationModeForResource(resource),
isNamespaceAware());
}
3.Bean 註冊
上一步中 XmlBeanDefinitionReader 已經取得了 XML 的 Document 對象,完成了資源的解析過程。
下一步就是 Bean 的註冊過程。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 利用 documentReader 對配置文件的內容進行解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 取得已經註冊 BeanDefinition
int countBefore = getRegistry().getBeanDefinitionCount();
// 關鍵 -> 註冊 BeanDefinition (包含解析過程)
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
總結
分析完 BeanDefinitionReader 具體工作流程,最後通過一個圖簡單闡述:
07.Spring Bean 加載 - BeanDefinitionReader