通俗理解spring源碼(二)—— 資源定位與載入 最開始學習spring的時候,一般都是這樣用: ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); User user = (User)con ...
通俗理解spring源碼(二)—— 資源定位與載入
最開始學習spring的時候,一般都是這樣用:
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); User user = (User)context.getBean("user");
這裡的ApplicationContext也是一個容器,只不過是引用了一個DefaultListableBeanFactory,暫時先不用管,真正的容器還是DefaultListableBeanFactory,我們還是以DefaultListableBeanFactory的初始化為例,可以這樣寫:
public static void main(String[] args) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
Resource resource = new ClassPathResource("spring.xml");
reader.loadBeanDefinitions(resource);
User user = (User)factory.getBean("user");
}
1、DefaultListableBeanFactory實例化
- new DefaultListableBeanFactory()這步操作實例化了一個工廠對象,初始化了一個容器,最終所有的bean都會放到這個容器中。
- 在它的構造器中,首先調用父類構造:
public DefaultListableBeanFactory() { super(); }
- 進入抽象類AbstractAutowireCapableBeanFactory中,AbstractAutowireCapableBeanFactory是DefaultListableBeanFactory的抽象父類,不記得的可以看看我上一篇博文,有個大致印象即可。在該抽象類構造中:
public AbstractAutowireCapableBeanFactory() { super(); //添加忽略給定介面的自動裝配功能 ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
- 這裡的super(),可以點進去看看,沒有任何操作。
- ignoreDependencyInterface(),表示添加忽略給定介面的自動裝配功能,暫不做詳細介紹,大家可以參考這篇文章。
2、Resource資源封裝
- spring將所有的資源都封裝成一個Resource,包括文件系統資源(FileSystemResource)、classpath資源(ClassPathResource)、url資源(UrlResource)等。
- Resource介面定義了獲取當前資源屬性的方法,如存在性(Exists)、可讀性(isReadable)、是否處於打開狀態(isOpen)等。
-
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}public interface Resource extends InputStreamSource { boolean exists(); default boolean isReadable() { return exists(); } default boolean isOpen() { return false; } default boolean isFile() { return false; } URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; default ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String relativePath) throws IOException; @Nullable String getFilename(); String getDescription();
-
2、XmlBeanDefinitionReader實例化
- xml配置文件的讀取是spring中最重要的功能,因為spring的大部分功能都是以配置作為切入點的。
-
XmlBeanDefinitionReader負責讀取和解析已封裝好xml配置文件的Resource,即ClassPathResource。
- 這裡先大致瞭解一下spring的資源載入體系:
-
- ResourceLoader:定義資源載入器,主要應用於根據給定的資源文件地址返回對應的Resource,如根據不同的首碼“file:”“http:”“jar:”等判斷不同的資源。
- BeanDefinitionReader:主要定義資源文件讀取並轉換為BeanDefinition的各個方法。BeanDefinition就是對你所定義的bean的class、id、alias、property等屬性的封裝,此時還沒有實例化bean。
- EnvironmentCapable:定義獲取Environment方法,Environment就是對當前所激活的profile的封裝。
- DocumentLoader:定義從資源文件載入到轉換為Document的功能。Document是對xml文檔的封裝,從中可以獲取各個節點的數據。
- AbstractBeanDefinitionReader:對EnvironmentCapable、BeanDefinitionReader類定義的功能進行實現。
- BeanDefinitionDocumentReader:定義讀取Document並註冊BeanDefinition功能。
- BeanDefinitionParserDelegate:定義解析Element的各種方法。真正解析xml的就是這個類,典型的委派模式。
3、loadBeanDefinitions方法
loadBeanDefinitions是整個資源載入的切入點,下麵是該方法執行時序圖:
在該方法中會調用XmlBeanDefinitionReader的重載方法loadBeanDefinitions(Resource resource)。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
EncodedResource是處理資源文件的編碼的,其中主要邏輯體現在getReader()方法中,我們可以在EncodedResource構造器中設置編碼,spring就會使用相應的編碼作為輸入流的編碼。
public Reader getReader() throws IOException { if (this.charset != null) { return new InputStreamReader(this.resource.getInputStream(), this.charset); } else if (this.encoding != null) { return new InputStreamReader(this.resource.getInputStream(), this.encoding); } else { return new InputStreamReader(this.resource.getInputStream()); } }
處理完編碼後,進入到loadBeanDefinitions(new EncodedResource(resource))中:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isTraceEnabled()) { logger.trace("Loading XML bean definitions from " + encodedResource); } //記錄已經載入的資源 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } //如果該資源已被載入,拋出異常 if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { //從encodedResource中取出原來的resource,得到輸入流inputStream InputStream inputStream = encodedResource.getResource().getInputStream(); try { //inputSource不屬於spring,是解析xml的一個工具,全路徑為org.xml.sax.InputSource InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //真正的核心部分 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
首先對傳入的resource參數做封裝,目的是考慮到resource可能存在編碼要求的情況,其次,用過SAX讀取xml文件的方式離開準備InputSource對象,最後將準備的數據通過參數傳入真正的核心處理部分doLoadBeanDefinitions(inputSource, encodedResource.getResource())。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //從資源文件轉換為document對象 Document doc = doLoadDocument(inputSource, resource); //解析document,並註冊beanDefiniton到工廠中 int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
這個方法的核心代碼就兩句,
//從資源文件轉換為document對象 Document doc = doLoadDocument(inputSource, resource); //解析document,並註冊beanDefiniton到工廠中 int count = registerBeanDefinitions(doc, resource);
其中registerBeanDefinitions(doc, resource)又是核心,邏輯非常複雜,先來看doLoadDocument(inputSource, resource)方法:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
這裡獲取對xml文件的驗證模式,載入xml文件,並得到對應的document,獲取xml的驗證模式將在下一篇博客中講解。
走的太遠,不要忘記為什麼出發!現在再來看看這段代碼:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); Resource resource = new ClassPathResource("spring.xml"); reader.loadBeanDefinitions(resource);
是不是清晰一些?總結如下:
- 實例化工廠,作為bean的容器;
- 實例化BeanDefinitionReader,負責讀取xml;
- 將資源文件路徑封裝為對應的resource對象;
- 調用reader.loadBeanDefinitions(resource),實際上會先將resouce轉換為document,再將document轉換為beanDefinition,這一步是整個資源載入的核心。
參考:spring源碼深度解析