Spring版本
5.2.5.RELEASE
源碼解析
1. 加載自定義bean.xml
首先看一下平時我們是怎么手動加載bean.xml文件的劫侧,示例代碼如下:
ClassPathResource resource = new ClassPathResource("bean.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
可以先看到窑邦,加載BeanDefinition入口在XmlBeanDefinitionReader#loadBeanDefinitions
2. loadBeanDefinitions
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 進行編碼带迟,不過這里只傳入了resource一個參數(shù)篱瞎,所以并沒有指定具體的編碼方式和字符集
return loadBeanDefinitions(new EncodedResource(resource));
}
可以看到這里對resource進行了一個資源的編碼,因為我們加載BeanDefinition其實是一個解析xml文件的過程镶骗,那么讀取文件就會涉及到編碼滋饲。
不過這里并沒有明確指定具體的編碼方式和字符集:
public EncodedResource(Resource resource) {
this(resource, null, null);
}
private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
super();
Assert.notNull(resource, "Resource must not be null");
this.resource = resource;
this.encoding = encoding;
this.charset = charset;
}
編碼結束后交由重載方法loadBeanDefinitions
繼續(xù)處理:
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);
}
// 加入緩存中
// 增加緩存的原因:避免bean定義異常的時候?qū)е滤姥h(huán),即有可能存在這樣的情況:
// bean A開始加載屠缭,加載過程中又由于bean定義的時候有問題箍鼓,再次要求加載Bean A,第二次加載Bean A走到這一步就會被攔截呵曹,避免一直死循環(huán)下去
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 開始記載bean定義
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
// 移除緩存
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
這里的邏輯主要是利用緩存對加載流程做了一個限制款咖,防止重復加載同一個bean導致的死循環(huán),通過判斷之后奄喂,可以看到交由doLoadBeanDefinitions
來做真正的解析工作:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 加載bean定義的xml文檔
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
// 其余代碼均為異常處理铐殃,此處略
}
該方法邏輯主要有倆步:
- 解析xml,獲取一個Document對象
- 注冊Bean
2.1 doLoadDocument:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
// getValidationModeForResource 獲取bean校驗模型跨新,校驗bean的定義是否符合規(guī)范
// getEntityResolver用于獲取xml文件校驗規(guī)則的獲取方式富腊,比如說
// 我們在xml文件可以看到這樣的聲明:http://www.springframework.org/schema/beans/spring-beans.xsd
// 這種聲明是用來尋找XSD的定義,以便對xml進行驗證域帐,而默認的方式就是通過網(wǎng)絡下載(實際上這個聲明就是一個url)
// 而尋找XSD這個行為赘被,就是交由EntityResolver去實現(xiàn)的
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
這里通過documentLoader.loadDocument
去解析xml是整,在調(diào)用之前,主要做了倆件事:
2.1.1. getEntityResolver
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
// Determine default EntityResolver to use.
ResourceLoader resourceLoader = getResourceLoader();
// ResourceEntityResolver 是 DelegatingEntityResolver 的子類
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
}
else {
this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
}
}
return this.entityResolver;
}
EntityResolver
主要作用在于提供一個獲取驗證模型的規(guī)則民假,我們驗證一個xml文檔是否正確贰盗,簡單理解就是xml語法是否正確,而我們往往都能在xml文檔的開頭處看到類似這樣的一個聲明: http://www.springframework.org/schema/beans/spring-beans.xsd
這種聲明就是用來指定xml文檔的校驗規(guī)則的阳欲,則默認方式是通過網(wǎng)絡方式獲取規(guī)則,所以是一個url陋率,而獲取規(guī)則這么一件差事球化,就是交由EntityResolver
去處理的
2.1.2. getValidationModeForResource
protected int getValidationModeForResource(Resource resource) {
// 獲取用戶通過set方法指定的驗證模型
int validationModeToUse = getValidationMode();
// 如果不是自動驗證模式,返回該值
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
// 否則瓦糟,檢測應該使用哪種驗證模型
// 檢測方法:通過驗證文檔是否存在DOCTYPE節(jié)點筒愚,如果是,判斷為DTD菩浙,否則XSD
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// 使用XSD作為默認值
// 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).
return VALIDATION_XSD;
}
/**
* Detect which kind of validation to perform on the XML file identified
* by the supplied {@link Resource}. If the file has a {@code DOCTYPE}
* definition then DTD validation is used otherwise XSD validation is assumed.
* <p>Override this method if you would like to customize resolution
* of the {@link #VALIDATION_AUTO} mode.
*/
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.");
}
InputStream inputStream;
try {
inputStream = resource.getInputStream();
}
catch (IOException ex) {
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?", ex);
}
try {
// 讀取輸入流巢掺,確定是否存在DOCTYPE節(jié)點,若存在劲蜻,返回DTD模式陆淀,否則返回XSD模式
// 內(nèi)部代碼相對簡單,不擴展解析
return this.validationModeDetector.detectValidationMode(inputStream);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
resource + "]: an error occurred whilst reading from the InputStream.", ex);
}
}
該方法的主要邏輯在于獲取xml文件的校驗模型先嬉,校驗模型可以是:
XSD
-
DTD
具體使用哪種方式首先由用戶指定轧苫,若沒有指定,則通過判斷xml文件是否存在DOCTYPE
疫蔓,若存在含懊,使用DTD
模式,否則使用XSD
模式
現(xiàn)在回過頭來看看loadDocument的具體實現(xiàn)邏輯:
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
/**
* Create the {@link DocumentBuilderFactory} instance.
* @param validationMode the type of validation: {@link XmlValidationModeDetector#VALIDATION_DTD DTD}
* or {@link XmlValidationModeDetector#VALIDATION_XSD XSD})
* @param namespaceAware whether the returned factory is to provide support for XML namespaces
* @return the JAXP DocumentBuilderFactory
* @throws ParserConfigurationException if we failed to build a proper DocumentBuilderFactory
*/
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(namespaceAware);
if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
factory.setValidating(true);
if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
// Enforce namespace aware for XSD...
factory.setNamespaceAware(true);
try {
factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
}
catch (IllegalArgumentException ex) {
ParserConfigurationException pcex = new ParserConfigurationException(
"Unable to validate using XSD: Your JAXP provider [" + factory +
"] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
"Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
pcex.initCause(ex);
throw pcex;
}
}
}
return factory;
}
/**
* Create a JAXP DocumentBuilder that this bean definition reader
* will use for parsing XML documents. Can be overridden in subclasses,
* adding further initialization of the builder.
* @param factory the JAXP DocumentBuilderFactory that the DocumentBuilder
* should be created with
* @param entityResolver the SAX EntityResolver to use
* @param errorHandler the SAX ErrorHandler to use
* @return the JAXP DocumentBuilder
* @throws ParserConfigurationException if thrown by JAXP methods
*/
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
@Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
throws ParserConfigurationException {
DocumentBuilder docBuilder = factory.newDocumentBuilder();
if (entityResolver != null) {
docBuilder.setEntityResolver(entityResolver);
}
if (errorHandler != null) {
docBuilder.setErrorHandler(errorHandler);
}
return docBuilder;
}
代碼塊稍長衅胀,但還是很清晰的岔乔,代碼已經(jīng)很清楚地展示了對應的邏輯含義,最后解析的核心在于:
builder.parse(inputSource)
這里的實現(xiàn)類使用了apache的包去做了解析滚躯,不在本文探討范圍內(nèi)雏门,就不展開了,解析完畢之后哀九,封裝成一個Document對象剿配,供后續(xù)注冊使用
到這里為止,我們僅僅只是驗證了xml文件是否合法阅束,并且將輸入流轉化為一個Document對象呼胚,尚未解析成BeanDefinition,也還沒有進行注冊息裸,對于這倆部分內(nèi)容蝇更,在下一篇 《Spring源碼解析(二)-BeanDefinition注冊》進行講解