一、資源文件定位和解析流程
加載和解析資源文件是在ApplicationContext子類調用refresh()方法時執行的,整個過程就是將資源文件讀入到內存中並且解析成Spring Bean對應的數據結構(BeanDefinition)。以ClassPathXmlApplicatinContext爲例整個調用流程如下圖:
二、解析流程詳細說明
1、AbstractApplicationContext 調用refresh()方法(前面已經有對refresh方法的分析),方法內通過
“ConfigurableListableBeanFactorybeanFactory =obtainFreshBeanFactory();”方法進入到BeanFactory的刷新和創建。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//這個方法會進入到loadBeanDefinitions的調用
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
2、AbstractApplicationContext子類實現refreshBeanFactory()方法。如AbstractRefreshableApplicationContext類的實現:
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//創建BeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
//個性BeanFactory
customizeBeanFactory(beanFactory);
//這次分析的正主來了,加載BeanDefinitions
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
3、AbstractApplicationContext調用loadBeanDefinitions(DefaultListableBeanFactory beanFactory) ,此方法根據首先創建XmlBeanDefinitionReader對象,然後配置該對象的上下文和資源加載環境,同時調用子類實現的initBeanDefinitionReader對XmlBeanDefinitionReader進行個性化配置,最近後入到initBeanDefinitionReader(beanDefinitionReader)的調用
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//根據給定的BeanFactory創建XmlBeanDefinitionReader 對象
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 配置beanDefinitionReader的上下文和資源加載環境
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 調用子類實現的initBeanDefinitionReader對XmlBeanDefinitionReader進行個性化配置initBeanDefinitionReader(beanDefinitionReader);
//調用載入Bean定義的方法,在當前類中只定義了抽象的loadBeanDefinitions方法,具體的實現調用子類容器
loadBeanDefinitions(beanDefinitionReader);
}
4、AbstractApplicationContext調用loadBeanDefinitions(beanDefinitionReader),這個方法是取得資源或資源路徑然後通過傳入的reader去加載BeanDefinitions
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//資源取得
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//資源取得
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
5、BeanDefinitionReader 的loadBeanDefinitions()方法調用,BeanDefinitionReader子類重載了loadBeanDefinitions()方法,其最終的目的就是通過Resource或者Location讀取輸入流轉換成xml Document進行解析。
6、BeanDefinitionReader 讀取到輸入流後就要開始解析了,看看XmlBeanDefinitionReader.doLoadBeanDefinitions()都幹了些啥
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//文件解析成Doc
Document doc = doLoadDocument(inputSource, resource);
//啓動資源解析的詳細過程
return registerBeanDefinitions(doc, resource);
}
……//省略的都是異常捕獲了
}
7、XmlBeanDefinitionReader.registerBeanDefinitions()代碼可以看出,XmlBeanDefinitionsReader說我把Xml解析成Doc了,Doc解析我就委託給BeanDefinitionDocumentReader來進行解析了。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
documentReader.setEnvironment(getEnvironment());
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
8、BeanDefinitionDocumentReader.registerBeanDefinitions(doc, createReaderContext(resource));一看DocReader竟然方法和XmlReader方法一樣,其實這是委派模式的一種體現(Spring Ioc容器資源文件解析和BeanDefinition加載是到處可見委派模式的使用啊),其實真正做事的DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(Element root)方法
protected void doRegisterBeanDefinitions(Element root) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
//創建委派類,DefaultBeanDefinitionDocumentReader表示他只能解析Spring Beans 模塊xsd文件說明的元素,要解析其他的還得委派給其他對象
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(this.readerContext, root, parent);
//留個子類實現的方法,個性化用
preProcessXml(root);
//解析資源
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
9、parseBeanDefinitions(Elementroot, BeanDefinitionParserDelegate delegate)實現具體的解析,到這就是真刀真槍要把一個個Doc元素給解析成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)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
三、解析具體實現
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
2、默認命名空間只能解析默認的元素,那麼如果配置了事務,配置了註解掃描這些該如何處理,平時用Spring這些照樣是得加載出來的,BeanDefinitionParserDelegate.parseCustomElement()就是來幹這些的
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
//取得命名空間
String namespaceUri = getNamespaceURI(ele);
//取得命名空間對應的handler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
//handler解析對應命名空間的元素,讓handler飛吧
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
handler我用context對應的命名空間(http://www.springframework.org/schema/context)來分析,其他的肯定原理是一樣的,只是內部做的具體事不一樣。對應的handler是org.springframework.context.config.ContextNamespaceHandler。
3、ContextNamespaceHandler.parse()方法,原來它也是找對應的Parser幫手來解析
public BeanDefinition parse(Element element, ParserContext parserContext) {
//找到對應的Parser然後解析
return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
//這個命名空間可以看看都用那些解析器
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
context的解析器是這些,
{property-override=org.springframework.context.config.PropertyOverrideBeanDefinitionParser,//屬性重載
annotation-config=org.springframework.context.annotation.AnnotationConfigBeanDefinitionParser,//註解解析
mbean-server=org.springframework.context.config.MBeanServerBeanDefinitionParser,//元數據解析
component-scan=org.springframework.context.annotation.ComponentScanBeanDefinitionParser,//組建掃描
load-time-weaver=org.springframework.context.config.LoadTimeWeaverBeanDefinitionParser,
property-placeholder=org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser,
spring-configured=org.springframework.context.config.SpringConfiguredBeanDefinitionParser,
mbean-export=org.springframework.context.config.MBeanExportBeanDefinitionParser}
這裏看一個通常配置比較多的component-scan 上面所有的都在Spring Beans模塊下,AnnotationConfigBeanDefinitionParser就進入了Spring context模塊了,AnnotationConfigBeanDefinitionParser就是context 模塊對應的Annotation包底下支持註解解析的類,它的parse()方法的代碼如下:
public BeanDefinition parse(Element element, ParserContext parserContext) {
//找到要掃描的包
String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// 真正進行bean掃描並且註冊beanDefinitions
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
//返回是空,這個可能是Spring Bean設計的遺留問題導致的,Spring Bean解析一個元素的方法都是返回單個BeanDefinition ,掃描是能夠掃描到多個的(具體看配置),但這除了使得AbstractBeanDefinitionReader. loadBeanDefinitions(String... locations)的返回值跟真實解析到的BeanDefinition個數不一致外沒其他影響。BeanDefinition 在調用registerComponents()方法就註冊到傳到的改方法的上下文了
return null;
}