Spring源碼解讀之BeanDefinition讀取器
BeanDefinitionReader
1卵沉、這個接口的功能就是將資源文件(spring的配置文件) 中的信息轉(zhuǎn)換成BeanDefinition形式
2逼蒙、這個是個頂級接口沟沙,其繼承關(guān)系如下:
I BeanDefinitionReader
--- c AbastractBeanDefinitionReader
---- c PropertiesBeanDefinitionReader
-----c XmlBeanDefinitionReader
3拴魄、源碼如下谤狡,其中定義了一些接口
public interface BeanDefinitionReader {
//獲取BeanDefinitionRegistry對象竟终,這個類的主要作用將BeanDefinition注冊到BeanDefinition的注冊表中
BeanDefinitionRegistry getRegistry();
//獲取資源加載器并蝗,主要就是根據(jù)路徑和ClassLoader獲取Resource祭犯,之后通過Resource獲取輸入流讀取文件
ResourceLoader getResourceLoader();
//Bean的類加載器
ClassLoader getBeanClassLoader();
//Bean的名字生成器,為匿名bean生成一個名字滚停,就是id
BeanNameGenerator getBeanNameGenerator();
//加載 BeanDefiniton沃粗,從配置文件中通過ResouceLoader和Resource這個兩個類加載資源
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}
子類
AbstractBeanDefinitionReader
1、這個是抽象類铐刘,實現(xiàn)了BeanDefinitionReader這個接口
2陪每、下面將是這個類重要方法loadBeanDefinitions,加載資源,根據(jù)用戶輸入路徑結(jié)合資源加載器檩禾,獲取Resource對象或者數(shù)組挂签,之后將會調(diào)用loadBeanDefinitions(Resource resource)
,這個方法在當前類中都沒有定義盼产,但是在其兩個子類中定義了饵婆。
3、使用資源加載器分為加載單個資源的ResourceLoader
和加載多個資源的ResourcePatternResolver
? 1戏售、這個方法的主要思想就是根據(jù)用戶提供的資源加載器的類型判斷到底是加載單個資源還是加載多個資源
//加載資源的實際方法
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//獲取資源加載器侨核,主要的功能就是根據(jù)路徑和類加載器獲取Resource對象
ResourceLoader resourceLoader = getResourceLoader();
//判斷資源加載器是否為空
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//ResourcePatternResolver 這個是用于加載多個文件或者能夠加載Ant風格路徑的文件資源,這個是ResouceLoader的子類灌灾,同樣是接口
if (resourceLoader instanceof ResourcePatternResolver) {
try {
//使用ResourcePatternResolver這個接口中的getResouces方法搓译,返回一個Resouce[]數(shù)組,實際上就是調(diào)用類加載器的getResouces(path)方法
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//調(diào)用loadBeanDefinitions(Resource[] resource)的方法
int loadCount = loadBeanDefinitions(resources);
//如果actualResources不為空锋喜,將加載完成的數(shù)據(jù)添加到其中
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount; //返回加載的個數(shù)
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else { //加載單個文件資源
//直接使用ResouceLoader加載即可些己,不需要使用ResourcePatternReslover這個接口
Resource resource = resourceLoader.getResource(location);
//調(diào)用loadBeanDefinitions(Resource resource)
int loadCount = loadBeanDefinitions(resource);
//不為空,添加到其中
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount; //返回個數(shù)
}
}
4嘿般、從上面的源碼可以知道段标,想要繼續(xù)進行執(zhí)行下去,需要子類提供的方法loadBeanDefinitions(Resource resource)
炉奴。
XmlBeanDefinitionReader
1逼庞、這個類繼承了抽象類AbstractBeanDefinitionReader,定義了loadBeanDefinitions(Resource resource)
瞻赶,完成了抽象類的資源加載
2赛糟、這個類主要就是加載XML文件
3、下面來看看loadBeanDefinitions(Resource resource)
這個方法共耍。
? 1虑灰、返回值表示從配置文件中加載的BeanDefiniton的數(shù)量,主要的思想是:在未加載之前從統(tǒng)計注冊表中的數(shù)量痹兜,加載完成之后再次統(tǒng)計注冊表中的數(shù)量穆咐,相減即可
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//調(diào)用該類中的重載方法,源碼如下解析
return loadBeanDefinitions(new EncodedResource(resource));
}
4字旭、loadBeanDefinitions(EncodedResource encodedResource)
:EncodedResource是用來指定編碼解析XML文件資源
? 1对湃、實際的作用就是將資源封裝成InputSource對象,之后調(diào)用同類的doLoadBeanDefinitions(InputSource,Resource)
? 2遗淳、這個類用到了本地線程變量存儲當前正在加載的資源拍柒,應(yīng)該是開啟多線程加載資源,之后會仔細研究下
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());
}
//從本地線程變量中獲取當前的正在加載的資源
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
//如果本地線程變量中不存在正在加載的資源屈暗,那么將其添加進去
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
//如果encodedResource添加進入currentResources失敗拆讯,表明其中已經(jīng)存在這個資源脂男,只不過還沒有加載完成,拋出重復(fù)加載的異常
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
//獲取文件的輸入流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//封裝成InputSource种呐,其中指定了輸入流和編碼格式
InputSource inputSource = new InputSource(inputStream);
//如果存在編碼宰翅,那么將其添加進入InputSource中,
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//調(diào)用同類的方法繼續(xù)解析
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
//關(guān)閉輸入流
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
//最后爽室,加載完畢之后汁讼,從currentResources中移除
currentResources.remove(encodedResource);
//如果currentResources是空的,本地線程變量移除
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
5阔墩、繼續(xù)上面的解析嘿架,現(xiàn)在調(diào)用doLoadBeanDefinitions(InputSource inputSource, Resource resource)
方法
? 1、創(chuàng)建Document對象啸箫,其實相當于js中的文檔樹的概念耸彪,可以獲取文檔的所有根節(jié)點等等方法
? 2、調(diào)用registerBeanDefinitions方法忘苛,詳解看下一個
? 3搜囱、剩下的就是捕捉一些異常,并且拋出
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//創(chuàng)建Document對象柑土,這個HTML和XML的文檔對象,相當于js中document樹
//使用這個Document可以獲取XML文件中的節(jié)點并且創(chuàng)建節(jié)點等等绊汹,實際上js中的document就是這個原理
Document doc = doLoadDocument(inputSource, resource);
//執(zhí)行registerBeanDefinitions方法
return registerBeanDefinitions(doc, resource);
}
6稽屏、繼續(xù)深入的調(diào)用registerBeanDefinitions(Document doc, Resource resource)
這個方法
? 1、創(chuàng)建BeanDefinitionDocumentReader對象西乖,底層使用的BeanUtils
創(chuàng)建的狐榔,其實使用的是反射機制創(chuàng)建的,這個在后面將會講解到BeanUtils的源碼
? 2获雕、獲取未加載之前的注冊表中BeanDefiniton中的數(shù)量薄腻,保存在countBefore
//Document : 文檔對象 Resource : 資源對象
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//創(chuàng)建BeanDefinitionDocumentReader,這個是實際讀取BeanDefiniton從XML的DOM樹中
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//獲取注冊表beanDefinitionMap的數(shù)量届案,就是加載之前的注冊表中存在的BeanDefiniton的數(shù)量
int countBefore = getRegistry().getBeanDefinitionCount();
//從XML文件讀取內(nèi)容庵楷,并且注冊到BeanDefiniton注冊表中,詳情請看第7步
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
//創(chuàng)建XmlReaderContext楣颠,使用Resource尽纽,需要指定資源提取器,問題報告等等
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
7童漩、BeanDefinitionDocumentReader中的registerBeanDefinitions
方法弄贿,這只是一個接口,在他的默認時實現(xiàn)類org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
中有具體的實現(xiàn)
? 1矫膨、指定成員屬性readerContext
? 2差凹、獲取根節(jié)點
? 3期奔、繼續(xù)調(diào)用doRegisterBeanDefinitions方法
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
//指定上下文讀取對象
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//獲取根節(jié)點 root
Element root = doc.getDocumentElement();
//繼續(xù)調(diào)用doRegisterBeanDefinitions(root)方法,詳情看第8步
doRegisterBeanDefinitions(root);
}
8危尿、doRegisterBeanDefinitions方法呐萌,解析
? 1、BeanDefinitionParserDelegate :這個是BeanDefintion解析委托類脚线,其中定義了spring配置文件中的節(jié)點內(nèi)容搁胆,比如bean,property邮绿,byType等等
? 2渠旁、
protected void doRegisterBeanDefinitions(Element root) {
//BeanDefinitionParserDelegate :這個是BeanDefintion解析委托類,其中主要
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
//判斷這個根節(jié)點是否是默認的命名空間船逮,底層就是判斷這個根節(jié)點的nameSpaceUrl=="http://www.springframework.org/schema/beans"
if (this.delegate.isDefaultNamespace(root)) {
//獲取這個profile屬性的值顾腊,表示剖面,在springBoot中用于設(shè)置環(huán)境
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
//如果profileSpec有值
if (StringUtils.hasText(profileSpec)) {
//根據(jù)分隔符換換成數(shù)組
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
//判斷這個切面是否是激活的環(huán)境挖胃,如果不是直接返回杂靶,表示這個配置文件不是當前運行環(huán)境的配置文件
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
//在解析xml之前做的準備工作,其實什么也沒做
preProcessXml(root);
//調(diào)用這個方法酱鸭,解析
parseBeanDefinitions(root, this.delegate);
//后續(xù)處理的
postProcessXml(root);
this.delegate = parent;
}
//解析BeanDefiniton
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//如果是默認的命名空間
if (delegate.isDefaultNamespace(root)) {
//獲取根節(jié)點下的所有子節(jié)點
NodeList nl = root.getChildNodes();
//遍歷所有的子節(jié)點
for (int i = 0; i < nl.getLength(); i++) {
//取出節(jié)點
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
//判斷節(jié)點是否是吗垮。。凹髓,
if (delegate.isDefaultNamespace(ele)) {
//遞歸調(diào)用解析這個節(jié)點
parseDefaultElement(ele, delegate);
}
else {
//解析自定義元素
delegate.parseCustomElement(ele);
}
}
}
}
else {
//解析自定義元素
delegate.parseCustomElement(root);
}
}
BeanDefinitionDocumentReader
1烁登、這個類的主要作用就是從Document(相當于DOM樹,其中包含了XML文件中的全部信息)讀取信息轉(zhuǎn)換成BeanDefiniton蔚舀,并且將讀取到的BeanDefiniton注冊到注冊表中
2饵沧、這個接口只有一個方法,如下:
//注冊BeanDefiniton
void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
throws BeanDefinitionStoreException;
}
總結(jié)
1赌躺、BeanDefinitonReader主要就是加載配置文件轉(zhuǎn)換成BeanDefiniton的形式狼牺。其中還結(jié)合了一些類,如下:
? 1礼患、EncodeResource : 編碼形式的Resoruce
? 2是钥、SAX : 解析XML文件的類
? 3、BeanDefinitionDocumentReader : 從給定的Document中讀取信息到BeanDefiniton中讶泰,并且注冊到注冊表中咏瑟,最主要的一個方法就是void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
參考文章
1、https://www.2cto.com/kf/201609/551189.html
2痪署、https://www.cnblogs.com/jason0529/p/5239139.html
3码泞、SAX解析XML文檔https://blog.csdn.net/ydxlt/article/details/50183693