Spring源码阅读之-自定义配置的解析
上文书:《Spring源码阅读之-bean的解析与注册》说到,Spring根据delegate.parseCustomElement(ele);
方法来解析自定义命名空间的element节点,本篇博文即分析该解析方式是如何工作的。
入口接上文分析,代码在
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
protected void parseBeanDefinitions(Element root,
@NotNull BeanDefinitionParserDelegate delegate)
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
* @param root the DOM root element of the document
*/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 验证Doc文件的命名空间
if (delegate.isDefaultNamespace(root)) { //又来判断
//获得根元素(<beans>)的所有子元素
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)) {
//解析<beans>子元素的内容
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele); //否则解析自定义节点
}
}
}
}
else { //按自定义节点解析
delegate.parseCustomElement(root);
}
}
上边这段代码的含义上文已经分析过,这里主要看delegate.parseCustomElement(ele);
这句代码,其语义含义为使用委托类来解析自定义节点,该自定义节点是如何进行解析的,跟着代码往下看。
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);//获得元素的命名空间
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); //根据指定的命名空间找到一个NamespaceHandler
if (handler == null) { //如果没有找到对应的handler,打印错误日志
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));//返回handler的解析类
}
以上代码可已看出,Spring读取配置时根据上下文中维护的NamespaceHandlerResolver,调用resolver 来返回适用于该节点命名空间的handler,那么Spring是如何维护,namespaceUri与handler的对应关系的呢?
*这个parseCustomElement方法虽说返回一个BeanDefinition ,但是不难发现,Spring并没有进行处理。也就是说有可能需要自己在NamespaceHandler.parse实现中注册beanDefinition
namespaceUri与NamespaceHandler 的对应关系
接以上代码段,在context对象中找到对NamespaceHandlerResolver的维护,发现其在Context中是一个org.springframework.beans.factory.xml.NamespaceHandlerResolver
类型的实例变量,ctrl+alt+b 发现其只有一个实现类org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver
,其实该类的初始化是在创建context时完成的,初始化代码如下
/**
* Create the {@link XmlReaderContext} to pass over to the document reader.
*/
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
/**
* Lazily create a default NamespaceHandlerResolver, if not set before.
* @see #createDefaultNamespaceHandlerResolver()
*/
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
/**
* Create the default implementation of {@link NamespaceHandlerResolver} used if none is specified.
* Default implementation returns an instance of {@link DefaultNamespaceHandlerResolver}.
*/
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}
以上只为说明,在创建读取xml的上下文对象时readerContext中使用的DefaultNamespaceHandlerResolver的初始化过程。DefaultNamespaceHandlerResolver初始化时维护一个public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
把该文件打开看看
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
根据以上文件内容去观察这个意图就很明显了,这根本就是将namespaceUri与NamespaceHandler 的对应关系维护在一个”META-INF/spring.handlers”这样的文件中,然后DefaultNamespaceHandlerResolver的resolver方法在该配置文件中根据namespaceUri获得NamespaceHandler对象,resolver方法内部就不详细解释了,毕竟Spring各功能这么繁杂也就只有一个DefaultNamespaceHandlerResolver来处理各种handlers而已,根本不需要我们来实现,只需要知道Spring通过spring.handlers文件来声明自定义节点的命名空间和其对应的处理类关系即可(DefaultNamespaceHandlerResolver类代码很简单,有兴趣可以自己看看)。
注意:书写时注意要对‘:’符号做转义,类名要写全。注册自己的Handler也可以通过这种方式来实现,只需要在自己的包中同样位置放置一个spring.handlers 文件,Spring即可自行加载其中配置。
很多常用的框架采用这种方式,向Spring中注册自己的一些信息,例如dubbo,看看有哪些常用包有spring.handlers 文件
小结:要使Spring认识自己声明的命名空间,需要在jar包的META-INF目录下,放置一个spring.handlers文件,并以命名空间=类名的方式书写规则,注意:自己的命名空间Handler类要实现org.springframework.beans.factory.xml.NamespaceHandler
接口
命名空间下节点内容的解析
上节说名了如何注册一个自己的命名空间Handler,一个命名空间下往往有几个不同的节点定义,就拿Spring的“http://www.springframework.org/schema/context”空间说事吧,其定义了几种不同的element,例如大名鼎鼎的annotation-config和component-scan(具体其空间下有几种element可以去看对应的xsd文件)
这里用个偷懒的方式去看,上节我们知道spring对自定义命名空间的处理是通过在spring.handlers中维护nameSpaceUri与NamespaceHandler的对应关系来实现的。那么NamespaceHandler就一定会有针对该空间的element的处理。这里依然去看大名鼎鼎的“http://www.springframework.org/schema/context”其对应的处理类为“org.springframework.context.config.ContextNamespaceHandler”,这个类打开来也很简单,很好懂,8个字“欠债还钱,杀人偿命”等等,好像走错片场了。
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.config;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.context.annotation.AnnotationConfigBeanDefinitionParser;
import org.springframework.context.annotation.ComponentScanBeanDefinitionParser;
/**
* {@link org.springframework.beans.factory.xml.NamespaceHandler}
* for the '{@code context}' namespace.
*
* @author Mark Fisher
* @author Juergen Hoeller
* @since 2.5
*/
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}
不他娘的管继承了,直接去看他的行为就行了,反正最后都是org.springframework.beans.factory.xml.NamespaceHandler
接口中定义的行为,不过该内容也反映了几个特征,他针对了不同节点,注册了不同的Parser类进来。这里不多说,可能自己实现的时候不会跟其一样,但是这是一种很值得学习的思路。即不同的节点交给不同的解析类来解析,与框架中的委托类所做的很像。其解析器的统一实现的接口为org.springframework.beans.factory.xml.BeanDefinitionParser
感兴趣的时候可以看一下。这里主要还是看会被 delegate.parseCustomElement(root); 调用的NamespaceHandler接口的parse方法。渣英文不翻译了,很好理解,直接看看大佬们怎么玩的,更好说明问题。
/**
* Parse the specified {@link Element} and register any resulting
* {@link BeanDefinition BeanDefinitions} with the
* {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}
* that is embedded in the supplied {@link ParserContext}.
* <p>Implementations should return the primary {@code BeanDefinition}
* that results from the parse phase if they wish to be used nested
* inside (for example) a {@code <property>} tag.
* <p>Implementations may return {@code null} if they will
* <strong>not</strong> be used in a nested scenario.
* @param element the element that is to be parsed into one or more {@code BeanDefinitions}
* @param parserContext the object encapsulating the current state of the parsing process
* @return the primary {@code BeanDefinition} (can be {@code null} as explained above)
*/
BeanDefinition parse(Element element, ParserContext parserContext);
大佬:ContextNamespaceHandler 很牛逼的一个自定义节点处理类了,其支持了Annotation注解的方式,其处理逻辑在org.springframework.context.annotation.ComponentScanBeanDefinitionParser
类中,不多说他,总之就是扫描注解->转BeanDefinetion->注册到context中的Registry的过程。可能有时间得单独说明一下。以下是比较重要的一段。
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
protected void registerComponents(
XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {
//...................//
Set<BeanDefinitionHolder> processorDefinitions =
AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
//...................//
readerContext.fireComponentRegistered(compositeDef);
}
/**
* Register all relevant annotation post processors in the given registry.
* @param registry the registry to operate on
* @param source the configuration source element (already extracted)
* that this registration was triggered from. May be {@code null}.
* @return a Set of BeanDefinitionHolders, containing all bean definitions
* that have actually been registered by this call
*/
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, Object source) {
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
if (beanFactory != null) {
if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
}
if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
}
}
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4);
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
//...........................//
return beanDefs;
}
private static BeanDefinitionHolder registerPostProcessor(
BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) {
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(beanName, definition);
return new BeanDefinitionHolder(definition, beanName);
}
*可见:beanDefinition得自己注册到BeanFactory中,如果自己实现NamespaceHandler时不要遗漏注册的动作,否则白写了[滑稽]
大佬:dubbo
叱咤分布式系统中,风光无限,其对自己的nameSpace的管理,可以看到与Spring大佬的写法一致。也是针对不同的element搞了好多Parser,后续有机会在深究下
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.alibaba.dubbo.config.spring.schema;
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
public DubboNamespaceHandler() {
}
public void init() {
this.registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
this.registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
this.registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
this.registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
this.registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
this.registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
this.registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
this.registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
this.registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
this.registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
}
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
}