spring4.2.9 java專案環境下ioc原始碼分析(四)——refresh之obtainFreshBeanFactory方法(@2處理Resource、載入Document及解析前準備)
接上篇文章,上篇文章講到載入完返回Rescouce。先找到要解析的程式碼位置,在AbstractBeanDefinitionReader類的
loadBeanDefinitions(String location, Set<Resource> actualResources)的方法中,要繼續的是
int loadCount = loadBeanDefinitions(resources);
@Override public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int counter = 0; for (Resource resource : resources) { counter += loadBeanDefinitions(resource); } return counter; }
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
這裡再次對Resource進行了封裝,封裝成了EncodedResource,這個類是是對檔案資源的編碼處理,如果指定了編碼格式,在得到Reader時會給幾編碼格式返回。這裡暫時沒用到。繼續看程式碼
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()); } //resourcesCurrentlyBeingLoaded是個ThreadLocal<Set<EncodedResource>>變數 //得到變數中存放的值,注意每個執行緒得到的都是一個拷貝 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); //第一次載入是null if (currentResources == null) { currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } //把資源放進set中,紀錄已載入內容,不能載入重複內容 if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { //得到該Resource的InputStream InputStream inputStream = encodedResource.getResource().getInputStream(); try { //用InputSource封裝InputStream,此類是org.xml.sax包下的 InputSource inputSource = new InputSource(inputStream); //如果encodedResource的編碼格式不為空,設定InputSource編碼格式 if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //do..開始真正的解析 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(); } } }
基本邏輯很簡單,註釋已經很詳細了這裡不多說。
這裡就幹了兩件事。真正的做事要開始了。咱們一個一個分析。先看doLoadDocument,這是根據上面得到的InputSource和Resource去生成Document物件。即把XML檔案解析為Document。protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //載入Xml得到Document物件 Document doc = doLoadDocument(inputSource, resource); //根據Document註冊BeanDefinitions return registerBeanDefinitions(doc, resource); } }
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
這裡具體的解析交給了DefaultDocumentLoader物件。這裡有兩個比較重要的方法getEntityResolver與getValidationModeForResourcegetEntityResolver返回的是EntityResolver。那什麼是EntityResolver呢?作用是什麼呢?
首先說下xml解析,一般情況下,SAX先去讀取XML上的文件說明做一個驗證,一般情況都是從網上下載,但是如果沒有網會報錯,這就是為什麼在沒有網的時候我們自己配置xsd或dtd檔案。
EntityResolver做了一個本地化,就是先在本地找約束檔案,找不到再在網上去找。
先說一下大體驗證流程。
以xsd檔案為例,首先為什麼要找xsd檔案,要知道我們要解析XML要按照一定模式去解析,xsd就是模式,他告訴你有哪些標籤可以用,標籤的屬性是什麼,子標籤是什麼等等。。這樣才可以去解析。但是如果你自己寫的標籤不屬於xsd定義的,則驗證失敗,造成無法解析的後果。
spring所有的xsd約束檔案都在META-INF/spring.schemas下(DTD這裡不說了)
getValidationModeForResource是獲取驗證模式,如果只有XSD那也就不分什麼模式了,但是XSD之前還有個DTD約束。我們要保證約束的匹配性,我不能用XSD約束去驗證DTD標籤是吧?
下面看下程式碼
protected int getValidationModeForResource(Resource resource) {
//得到指定的驗證模式,當然這裡沒有指定預設是VALIDATION_AUTO
int validationModeToUse = getValidationMode();
if (validationModeToUse != VALIDATION_AUTO) {
//如果指定了就用指定的
return validationModeToUse;
}
//檢查驗證模式,根據xml文件頭進行檢驗
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
//如果不等於VALIDATION_AUTO,就用檢驗後的
return detectedMode;
}
// Hmm, we didn't get a clear indication... Let's assume XSD,
// since apparently no DTD declaration has been found up until
// detection stopped (before finding the document's root tag).
//直接用VALIDATION_XSD
return VALIDATION_XSD;
}
protected int detectValidationMode(Resource resource) {
InputStream inputStream;
try {
inputStream = resource.getInputStream();
}
//檢測
try {
return this.validationModeDetector.detectValidationMode(inputStream);
}
}
public int detectValidationMode(InputStream inputStream) throws IOException {
// Peek into the file to look for DOCTYPE.
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
String content;
//一行一行讀取
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
//為空的話直接跳過
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
//是否包含DOCTYPE
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
//讀取開始的符號
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}
private boolean hasDoctype(String content) {
return content.contains(DOCTYPE);
}
上面可以看到如果包含DOCTYPE內容就是DTD.然後看XSD的private boolean hasOpeningTag(String content) {
if (this.inComment) {
return false;
}
int openTagIndex = content.indexOf('<');
return (openTagIndex > -1 && (content.length() > openTagIndex + 1) &&
Character.isLetter(content.charAt(openTagIndex + 1)));
}
上面這個程式碼是啥?如果是註釋就一直返回false.
重點看程式碼
Character.isLetter(content.charAt(openTagIndex + 1))
這裡的意思是<開始符號的下一個字元是不是字母,如果是返回true,如果不是比如!?就返回false.那麼看下DTD和XSD的區別是什麼?
這是DTD
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
這是XSD
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
注意:只要不是包含DOCTYPE的都是XSD!!我只是舉了個例子,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);
}
上面程式碼就不說了,解析XML的固定方法。接下來看registerBeanDefinitions方法。程式碼如下public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//例項化BeanDefinitionDocumentReader的實現類DefaultBeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//記錄註冊之前的數值
int countBefore = getRegistry().getBeanDefinitionCount();
//執行註冊
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//註冊的數值
return getRegistry().getBeanDefinitionCount() - countBefore;
}
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//獲取rootElement
Element root = doc.getDocumentElement();
//do。。真正進行註冊
doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
//專用來解析的類
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
//判斷root的名稱空間是不是預設的
if (this.delegate.isDefaultNamespace(root)) {
//處理profile屬性
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);
//解析
parseBeanDefinitions(root, this.delegate);
//擴充套件,解析後處理
postProcessXml(root);
this.delegate = parent;
}
上面三段程式碼重點看下第三段,這裡判斷root的名稱空間是不是預設的。也就是beans的預設名稱空間,預設名稱空間是什麼呢。xmlns="http://www.springframework.org/schema/beans"
上面就是預設的名稱空間。這裡判斷相同後進行profile屬性處理。首先說下在日常中我們怎麼用這個屬性。這個屬性可以配置多個一般是不同環境配置對應的,比如在web環境中我們配置spring.profiles.active屬性為dev,那麼在查詢beans的時候如果制定了profile屬性,只加載其屬性值為dev的beans.下面根據程式碼去看
if (this.delegate.isDefaultNamespace(root)) {
//取得屬性值
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
//判斷是否存在
if (StringUtils.hasText(profileSpec)) {
//profile可以指定多個
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
//去系統環境中去找是否匹配設定,不存跳過解析,存在就解析
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
public boolean acceptsProfiles(String... profiles) {
Assert.notEmpty(profiles, "Must specify at least one profile");
for (String profile : profiles) {
if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {
if (!isProfileActive(profile.substring(1))) {
return true;
}
}
//如果系統包含profile屬性返回true
else if (isProfileActive(profile)) {
return true;
}
}
return false;
}
protected boolean isProfileActive(String profile) {
validateProfile(profile);
//獲取系統環境下配置的profile屬性
Set<String> currentActiveProfiles = doGetActiveProfiles();
return (currentActiveProfiles.contains(profile) ||
(currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
}
protected Set<String> doGetActiveProfiles() {
synchronized (this.activeProfiles) {
//只獲取一次即可,系統配置的屬性profile載入一次即可
if (this.activeProfiles.isEmpty()) {
//從系統中獲取ACTIVE_PROFILES_PROPERTY_NAME==spring.profiles.active
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}
說下大體流程,第一:系統配置的spring.profiles.active只會載入一次,如果有多個beans直接取到比對即可。
第二:一個xml檔案中可包含多個beans標籤,如果都配置了profile,拿到profile的屬性與系統得到的比對,不同則跳過解析,相同進行解析。
繼續看parseBeanDefinitions方法
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)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
這裡就開始解析了。具體解析下長再將