說到Spring 大家可能都知道,也都用過 ,那么今天就給大家來解析一下,Spring 從解析Xml 到最后的創(chuàng)建Bean 都用到了 什么類,用到了那些模式>
BeanFactor beanfactor = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));
功能分析:
1.讀取配置文件beanFactory.xml
2.根據(jù)beanFacotry.xml 找到配置所對(duì)應(yīng)的類,并且初始化。
3.調(diào)用后實(shí)例化。
Spring 解析Xml讀取Bean 核心類介紹
-DefaultListableBeanFacotry 和XmlBeanDefintionReader
1.XmlBeanFactory
XmlBeanFacotry繼承自DefaultListableBeanFactory ,而DefaultListableBeanFacotry是整個(gè)加載bean的核心部分,是Spring注冊(cè)及加載bean的默認(rèn)實(shí)現(xiàn),而XmlBeanfactory與DefaultListableBeanFactory不同的地方在于 XmlBeanFactory 實(shí)現(xiàn)了自定義的Xml讀取器XmlBeanDefinationReader實(shí)現(xiàn)了BeanDefinitionReader讀取,
2.XmlBeanDefinitionReader
XmlBeanDefintionReader 讀中有資源文件讀取,解析以及注冊(cè)bean 的類 ,如:ResourceLoader,BeanDefinitionReader DocumentLoader 等等我就不列舉了
容器的基礎(chǔ)XmlBeanFactory
1.對(duì)xml文件的封裝
Spring 通過對(duì)配置文件的讀取封裝到對(duì)應(yīng)的類中,如,New ClassPathResource("beanfacotry.xml");那么ClassPathResouce完成了什么功能呢?
1.將當(dāng)前URL封裝到Resouce 我們知道URL 沒有默認(rèn)定義ClassPath或者SerevletContext 等資環(huán)的handler,此時(shí)Spring 自己底層封裝了Resouce 即URl封裝底層資源。代碼如下
public interface Resource extends InputStreamSource{
boolean exists();
boolean isReadable();
boolean isOpen();
URL getURL() throws IOException;
URI getURI() throws IOException;
.......
}
這里我們可以借鑒一下,Spring讀取xml的方式
Resource resource = new ClassPathResource("bean.xml");
InputStream inputStream = resouce.getInputStream();
Spring 是如何通過 .xml 得到ClassPathResource對(duì)象呢 ?其實(shí)很簡單,我們用IDEA編輯器打開ClassPathResource 對(duì)象看看
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
} else {
is = this.classLoader.getResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(this.getDescription() + " cannot be opened because it does not exist");
} else {
return is;
}
}
當(dāng)通過Resource 相關(guān)類對(duì)xml文件進(jìn)行封裝后具體對(duì)配置文件的讀取工作就交個(gè)XmlBeanDefinitionReader來處理了。
我們可以用IDEA 打開XmlBeanFacotry debug下
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader;
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, (BeanFactory)null);//調(diào)用XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) 的構(gòu)造方法
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);//調(diào)用父類的構(gòu)造器初始化忽略給定接口的裝配功能
this.reader = new XmlBeanDefinitionReader(this);
this.reader.loadBeanDefinitions(resource);//loadBeanDefinitions 才是加載bean的真正位置
}
}
調(diào)用loadBeanDefinitions 加載Bean 跟蹤一下
XmlBeanDefinitionReader.java
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (this.logger.isInfoEnabled()) {
this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
//通過屬性來記錄 已近加載的資源
Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!((Set)currentResources).add(encodedResource)) {
throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
} else {
int var6;
try {
InputStream inputStream = encodedResource.getResource().getInputStream();//從encodedResource中獲取已近封裝的Resource對(duì)象 并在此從Resource中獲取InpuStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());//設(shè)置編碼
}
// 這里才是真正的加載bean的部分
var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
inputStream.close();
}
} catch (IOException var15) {
throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
} finally {
((Set)currentResources).remove(encodedResource);
if (((Set)currentResources).isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
return var6;
}
}
調(diào)用doLoadBeanDefinitions 加載bean 再次進(jìn)入到doLoadBeanDefinitions 具體的方法中
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
int validationMode = this.getValidationModeForResource(resource);//獲取xml的驗(yàn)證模式
Document doc = this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, validationMode, this.isNamespaceAware());//加載xml文件 獲取Document 對(duì)象
return this.registerBeanDefinitions(doc, resource);//獲取Document 注冊(cè)bean信息
} catch (BeanDefinitionStoreException var5) {
throw var5;
} catch (SAXParseException var6) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var6.getLineNumber() + " in XML document from " + resource + " is invalid", var6);
} catch (SAXException var7) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var7);
} catch (ParserConfigurationException var8) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var8);
} catch (IOException var9) {
throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var9);
} catch (Throwable var10) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var10);
}
}
1.獲取xml驗(yàn)證模式
我們知道xml驗(yàn)證 有種驗(yàn)證方式XSD(Xml Schemas Definition)和DTD (Document type Definition)兩種驗(yàn)證方式
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
這種為Dtd的聲明方式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
這種為Xsd 的聲明方式
其中,如果沒有指明 聲明方式 就是用默認(rèn)的聲明方式
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = this.getValidationMode();
if (validationModeToUse != 1) { //如果手動(dòng)了制定了驗(yàn)證模式則使用指定的驗(yàn)證模式
return validationModeToUse;
} else { //否則就是 默認(rèn)的驗(yàn)證方式使用自動(dòng)檢測
int detectedMode = this.detectValidationMode(resource);
return detectedMode != 1 ? detectedMode : 3;
}
}
protected int detectValidationMode(Resource resource) {
if (resource.isOpen()) {
throw new BeanDefinitionStoreException("Passed-in Resource [" + resource + "] contains an open stream: " + "cannot determine validation mode automatically. Either pass in a Resource " + "that is able to create fresh streams, or explicitly specify the validationMode " + "on your XmlBeanDefinitionReader instance.");
} else {
InputStream inputStream;
try {
inputStream = resource.getInputStream();
} catch (IOException var5) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " + "Did you attempt to load directly from a SAX InputSource without specifying the " + "validationMode on your XmlBeanDefinitionReader instance?", var5);
}
try {
return this.validationModeDetector.detectValidationMode(inputStream);//這里才是真正的獲取驗(yàn)證模式
} catch (IOException var4) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: an error occurred whilst reading from the InputStream.", var4);
}
}
}
那我們可以看detectValidationMode 里面是如何處理的
public int detectValidationMode(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
while(true) {
String content;
if ((content = reader.readLine()) != null) {
content = this.consumeCommentTokens(content);
if (this.inComment || !StringUtils.hasText(content)) {//如果是注釋或者行或者是空的則跳過
continue;
}
if (this.hasDoctype(content)) { // hasDoctype 如果 content.indexOf("DOCTYPE") > -1; 則為DTD 驗(yàn)證
isDtdValidated = true;
} else if (!this.hasOpeningTag(content)) {//否則就是XSD
continue;
}
}
int var6 = isDtdValidated ? 2 : 3;
return var6;
}
} catch (CharConversionException var9) {
;
} finally {
reader.close();
}
return 1;
}
獲取Document
經(jīng)過了獲取驗(yàn)證模式 就可以進(jìn)行 Document的加載了 同樣在XmlBeanFacotry類中,對(duì)于文檔的讀取并沒有真正的而在這里解析而是委托給 了DocumentLoader去執(zhí)行
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
//創(chuàng)建DocumentBuilderFactory 對(duì)象 其中調(diào)用newsInstance 方法//FactoryFinder.find(
DocumentBuilderFactory.class, // "javax.xml.parsers.DocumentBuilderFactory"
"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");返回DocumentBuilderFactory 的實(shí)現(xiàn)類 DocumentBuilderFactoryImpl
DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);//根據(jù)DocumentBuilderFactoryImpl 創(chuàng)建DocumentBuilder對(duì)象
return builder.parse(inputSource);//真正的創(chuàng)建Document 對(duì)象
}
1.加載Document 這里也簡單 就是獲取DocumentBuilderFactory 的實(shí)現(xiàn)類DocumentBuilderFactoryImpl
2.根據(jù)DocumentBuilderFactoryImpl 獲取DocumentBuilder
3.通過DocumentBuilder 來獲取Document
這里有必要說明一下 在調(diào)用loadDocument 之前調(diào)用的getEntityResolver()方法
調(diào)用getEntityResolver 方法 返回 EntityResolver 對(duì)象,EntityResolver的作用
1.獲取SystemId 和publicId
如果 為XSD 的驗(yàn)證模式
SystemId : http://www.springframework.org/schema/beans/spring-beans.xsd
publicId :null
DTD的驗(yàn)證模式
SystemId: http://www.springframework.org/schema/beans/spring-beans.xsd
publicId: //SPRING//DTD BEAN 2.0//EN
解析及注冊(cè)BeanDefinitions
當(dāng)把xml文件轉(zhuǎn)換成Document 后,接下來 就是提取和注冊(cè)bean 了
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();//實(shí)例化BeanDefinitionDocumentReader
documentReader.setEnvironment(this.getEnvironment());//將環(huán)境變量設(shè)置其中
int countBefore = this.getRegistry().getBeanDefinitionCount();//記錄統(tǒng)計(jì)前BeanDefinitions的加載個(gè)數(shù)
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));//加載和注冊(cè)bean
return this.getRegistry().getBeanDefinitionCount() - countBefore;//記錄統(tǒng)計(jì)后BeanDefinitions的加載個(gè)數(shù)
}
其中document 是上一節(jié) loadDocument加載轉(zhuǎn)換出來的,在這個(gè)方法中很好的體現(xiàn)了 單一制則原則,將邏輯委托給單一的類處理, 而這個(gè)邏輯處理類就是BeanDefinitionReaderDocumentReader 。而 BeanDefinitionReaderDocumentReader 的類型已經(jīng)是DefaultBeanDefinitionReaderDocumentReader 了,進(jìn)入registerBeanDefinitions 的方法 其實(shí)目的只有一個(gè)就是提取Root
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
this.logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
this.doRegisterBeanDefinitions(root);//這里才是真正的注冊(cè)bean
}
山路18彎 ,如果說我們以前一直是XMl解析的準(zhǔn)備階段,那么現(xiàn)在, 我們真正的開始解析了,
protected void doRegisterBeanDefinitions(Element root) {
String profileSpec = root.getAttribute("profile");//處理profile
if (StringUtils.hasText(profileSpec)) {
Assert.state(this.environment != null, "environment property must not be null");
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
if (!this.environment.acceptsProfiles(specifiedProfiles)) {
return;
}
}
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createHelper(this.readerContext, root, parent);
this.preProcessXml(root); //解析前 對(duì)bean進(jìn)行處理 留給子類實(shí)現(xiàn)
this.parseBeanDefinitions(root, this.delegate);//這里專門解析
this.postProcessXml(root);//解析后,對(duì)bean進(jìn)行處理 留給子類實(shí)現(xiàn)
this.delegate = parent;
}
通過上面的代碼我們看到了處理流程,首先是對(duì)profile的處理,然后看是解析,可是當(dāng)我們更加preProcessXml和postProcessXml的時(shí)候 發(fā)現(xiàn)里面是空的,,如果你有了解過設(shè)計(jì)模式 那么這就是模板方法模式,如果子類需要對(duì)加載bean 之前和加載 bean 之后進(jìn)行處理 則子類需要繼承DefaultBeanDefinitionDocumentReader
處理profile 標(biāo)簽
<beans profile="dev"></beans>
<beans profile="product"></beans>
繼承到web環(huán)境是,在web.xml 加入以下代碼,
<context-param>
<param-name>Spring.profile.active</param-name>
<param-value>dev</param-value>
</context-param>
從這里我們就可以看出 在配置文件中部署兩套配置 來試用于測試環(huán)境和生產(chǎn)環(huán)境,這樣就可以方便的進(jìn)行切換
解析并且注冊(cè)BeanDefinition
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for(int i = 0; i < nl.getLength(); ++i) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element)node;
if (delegate.isDefaultNamespace(ele)) {
this.parseDefaultElement(ele, delegate);//對(duì)bean進(jìn)行處理
} else {
delegate.parseCustomElement(ele);//對(duì)bean進(jìn)行處理
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
在Springde Xml 的配置中有兩大類Bean聲明一種是默認(rèn)的 如:
<bean id ="user" class="com.zhh.User"/>
另一種就是自定義的
<tx:annotation-driven/>
如果是自定以的Spring知道該怎么做,則采用this.parseDefaultElement方法進(jìn)行解析,否則則采用 delegate.parseCustomElement(ele)方法 對(duì)自定義 命名空間進(jìn)行解析,而判斷是否默認(rèn)命名空間還是自定義空間的辦法是用 delegate.isDefaultNamespace(ele) 中的 node.getNamespaceURI() 來獲取命名空間,并且于Spring中固定的命名空間進(jìn)行對(duì)比http://www.springframework.org/schema/beans,如果一樣則是默認(rèn)的 否則 則不是
對(duì)于默認(rèn)標(biāo)簽解析于自定義標(biāo)簽解析 我們下次再說