對於經常使用spring框架的同學,對於下面的這段代碼肯定不會陌生
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
(1)讀取配置文件applicationContext
(2)找到配置文件中定義的配置並實例化
以上是Spring實現容器的基礎,雖然只有短短的一行,但是裏面卻包含了複雜的邏輯。爲了分析裏面包含的邏輯,我們可以使用他的父類BeanFactory來分析。下面代碼是Spring比較原始的實現容器的方法。
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));
代碼實現的邏輯爲:
1.使用ClassPathResource將beanFactory.xml封裝成Resource
2.完成XmlBeanFactory的初始化
ClassPathResource
在 Java中,將不同來源的資源抽象成URL,通過註冊不同的handler(URLStreamHandler)來處理不同來源間的資源讀取邏輯。而 URL中卻沒有提供一些基本方法來實現自己的抽象結構。因而Spring對其內部資源,使用了自己的抽象結構:Resource接口來封裝。而 ClassPathResource實現類即是對Resource的實現。而Resource實現了InputStreamSource。
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
不同的Resource實現代表了不同來源的資源,對於來自ClassPath的資源就是用ClassPathResource來實現。
XmlBeanDefinitionReader
將配置文件封裝好以後就需要對其讀取,這個工作由XmlBeanDefinitionReader來完成。XmlBeanDefinitionReader中由loadBeanDefinitions方法實現讀取的功能。loadBeanDefinitions方法的源代碼如下:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//loadBeanDefinitions的具體實現,而EncodedResource主要用於對資源文件的處理
return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
//通過屬性來記錄已經加載的資源
。。。。。。。。。。
// 調用DefaultResourceLoader的getResources方法完成具體的Resource定位
try {
//從EncodedResource中獲取已經封裝的Resource對象並再次從Resource中獲取inputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//真正的實現核心
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
。。。。。。。。。。。。。
}
對於上述源碼,只需要知道實現邏輯:
(1)對於resource做一次封裝,爲了考慮到resource可能存在的編碼問題,我們使用了new EncodedResource(resource)來實現。
(2)準備獲得InputSource對象。
(3)將封裝好的resource和InputSource傳遞給真正的核心部分:doLoadBeanDefinitions(inputSource, encodedResource.getResource());
doLoadBeanDefinitions
我們看一看doLoadBeanDefinitions(inputSource, encodedResource.getResource())中的實現方法:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
//這裏略去了冗餘的代碼,值需要注意三個方法
//獲得驗證模式
int validationMode = getValidationModeForResource(Resource resource)
//獲得document
Document document = loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
//註冊Bean信息
registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
}
doLoadBeanDefinitions只做了三件事:
1.獲得驗證模式
2.獲得document
3.註冊Bean信息
獲得驗證模式
驗證模式就兩種,DTD和XSD。如果xml文件中有則表示使用DTD驗證模式,否則使用XSD。源代碼如下,比較簡單。
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
//如果手動指定了驗證模式則使用指定的驗證模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
//如果沒有指定,則自動檢測
int detectedMode = detectValidationMode(resource); //自動檢測主要是在detectValidationMode(Resource resource)完成的
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
return VALIDATION_XSD;
}
獲得Document
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
邏輯:
1.創建DocumentBuilderFactory
2.通過DocumentBuilderFactory創建DocumentBuilder
3.DocumentBuilder解析inputSource返回一個Document對象。
registerBeanDefinitions
獲得了XML文檔文件的Document以後,就會執行BeanDefinitions的解析和註冊。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 得到BeanDefinitionDocumentReader來對XML的BeanDefinition進行解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 具體的解析過程在BeanDefinitionDocumentReader的registerBeanDefinitions方法中完成
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
registerBeanDefinitions對前一步獲得的Document進行了處理,實現註冊和解析由方法documentReader.registerBeanDefinitions(doc, createReaderContext(resource))實現。
documentReader中的registerBeanDefinitions方法如下:
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement(); // 獲得Document的根元素
//核心邏輯
doRegisterBeanDefinitions(root);
}
真正的實現XML文件解析和加載的核心部分——doRegisterBeanDefinitions方法:
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
preProcessXml(root); // 解析Bean定義之前, 增強解析過程的可擴展性
parseBeanDefinitions(root, this.delegate); // 從Document的根元素開始進行Bean定義的Document對象
postProcessXml(root); // 解析Bean定義之前, 增強解析過程的可擴展性
this.delegate = parent;
}
核心代碼邏輯如下:
1.對profile屬性的處理。不同的profile屬性可以理解爲:代表了xml中beans的不同namespace,一個namespace代表一套的配置方案。
2.preProcessXml方法。在解析之前,我們可以根據需要實現一些邏輯,需要我們自己實現。
3.parseBeanDefinitions方法,對xml配置文件的讀取和解析。
4.postProcessXml,在解析完成之後,我們可以根據需要實現一些邏輯,需要我們自己實現。
總結
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));
1.beanFactory.xml→ClassPathResource
2.使用XmlBeanDefinitionReader的loadBeanDefinitions(Resource resource)讀取Resource。
loadBeanDefinitions(resource)的實現邏輯:
2.1.封裝資源,new EncodeResource(resource);
2.2.獲得輸入流,並構造inputsource。
2.3.調用doLoadBeanDefinitions(inputsource,resource)。
doLoadBeanDefinitions(inputsource,resource)的實現:
2.3.1.獲得XML驗證模式:
方法:getValidationModeForResource
2.3.2.加載XML文件,獲得Document類。
方法:loadDocument
2.3.3.根據Document獲得Bean。
方法:registerBeanDefinitions(document,resource);
registerBeanDefinitions(document,resource)中的doRegisterBeanDefinitions方法是真正的核心實現。
doRegisterBeanDefinitions實現邏輯:
1.處理profile
2.preProcessXml
3.parseBeanDefinitions方法,對xml配置文件的讀取和解析
4.postProcessXml