Spring源码系列 — BeanDefinition扩展点

时间:2023-03-09 01:31:46
Spring源码系列 — BeanDefinition扩展点

前言

前文介绍了Spring Bean的生命周期,也算是XML IOC系列的完结。但是Spring的博大精深,还有很多盲点需要摸索。整合前面的系列文章,从Resource到BeanDefinition,再到容器扩展点,最后到Bean创键,这个过程中无处不存在Spring预留的扩展口。

本篇文章介绍Spring的另一种扩展点:BeanDefinition扩展点,该扩展点是为处理BeanDefinition而设计。本文主要从以下几点分析:

  • BeanDefinition扩展点的几种方式
  • BeanDefinition扩展点实战
  • BeanDefinition扩展点的原理

BeanDefinition扩展点的几种方式

Spring中针对向上下文中添加BeanDefinition、修改上下文中的BeanDefinition可谓是提供了丰富的扩展点。既有针对XML配置的,又有针对注解配置的Bean,甚至还有自定义XML标签的。这里总结了,共有以下几种方式:

  1. BeanDefinitionRegistryPostProcessor方式
  2. BeanFactoryPostProcessor方式
  3. ImportBeanDefinitionRegistrar方式
  4. BeanDefinitionParser方式
BeanDefinitionRegistryPostProcessor方式

从命名上也可以看出一些端倪,BeanDefinitionRegistryPostProcessor是BeanDefinition注册后置处理器,它本身是BeanFactoryPostProcessor的扩展,允许在BeanFactoryPostProcessor处理前向上下文中注册更多的BeanDefinition。

BeanFactoryPostProcessor方式

BeanFactoryPostProcessor是容器的扩展点,用于更进一步处理上下文中的BeanDefinition,如果对其还不甚了解,请移步至我的另一篇文章Spring源码系列 — 容器Extend Point(一)

ImportBeanDefinitionRegistrar方式

ImportBeanDefinitionRegistrar也是BeanDefinition注册器,用于向上下文注册更多的BeanDefinition。不过它是被应用在注解处理BeanDefinition的场景中,即自定义注解,然后利用ImportBeanDefinitionRegistrar其实现向上下文中注册自定义注解标注的Bean定义。

BeanDefinitionParser方式

BeanDefinitionParser是BeanDefinition解析器,它是Spring提供为扩展解析XML配置的Bean而设计。它不仅能够解析XML向上下文中注册更多BeanDefiniion,同时还支持自定义XML Tag。

BeanDefinition扩展点实战

上节整理了Spring中提供处理BeanDefinition的几种扩展方式,为了更好的理解和应用这些扩展点,本节将从实战的角度再度理解这些扩展方式。

Notes:

关于BeanFactoryPostProcessor的扩展实战本节不再做说明,在前文的容器扩张点中已经详细介绍其原理,并利用PropertySourcesPlaceholderConfigurer案例进行了分析。这里不再赘述。

基于BeanDefinitionRegistryPostProcessor扩展

首先定义BeanDefinitionRegistryPostProcessor实现类MyBdRegistryPostProcessor,实现其postProcessBeanDefinitionRegistry接口:

/**
* 用于演示BeanDefinitionRegistryPostProcessor扩展点
*
* @author huaijin
*/
public class MyBdRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
try {
// 创建自定义的BeanDefinition
String bdClassName = MyBeanUsedBdRegistryPostProcessor.class.getName();
AbstractBeanDefinition bd = BeanDefinitionReaderUtils
.createBeanDefinition(null, bdClassName, ClassUtils.getDefaultClassLoader());
// 设置BeanDefinition属性:单例、非惰性
bd.setScope(AbstractAutowireCapableBeanFactory.SCOPE_SINGLETON);
bd.setLazyInit(false);
// 设置Bean的属性值
MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
PropertyValue propertyValue = new PropertyValue("name", "myBeanUsedBdRegistryPostProcessor");
mutablePropertyValues.addPropertyValue(propertyValue);
// 将Bean的属性值添加到BeanDefinition中
bd.setPropertyValues(mutablePropertyValues);
// 注册该自定义的BeanDefinition,BeanName使用myBeanUsedBdRegistryPostProcessor
registry.registerBeanDefinition("myBeanUsedBdRegistryPostProcessor", bd);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}

然后编写启动类,BeanDefinitionRegistryPostProcessorDemo,载入XML配置,从上下文中获取myBeanUsedBdRegistryPostProcessor名称的Bean,并执行其printMyName方法:

public class BeanDefinitionRegistryPostProcessorDemo {

    public static void main(String[] args) {
// 载入配置
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext(
"applicationContext-extendpoint/beans.xml");
// get bean
MyBeanUsedBdRegistryPostProcessor myBean = context.getBean(
"myBeanUsedBdRegistryPostProcessor", MyBeanUsedBdRegistryPostProcessor.class);
// 执行方法
myBean.printMyName();
}
}

beans.xml的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置自定义的BeanDefinitionRegistryPostProcessor为Bean -->
<bean class="com.learn.ioc.extendpoint.process.MyBdRegistryPostProcessor"></bean>
</beans>

方法调用执行结果如下:

my name is:myBeanUsedBdRegistryPostProcessor

BeanDefinitionRegistryPostProcessor中自定义的Bean成功的被上下文注册为单例。当然这里只是简单的示例,对于更复杂的需要进行依赖处理。

基于ImportBeanDefinitionRegistrar扩展

上节中介绍了ImportBeanDefinitionRegistrar是基于注解的方式BeanDefinition注册器,允许应用向上下文中注册更多的BeanDefinition。这里以笔者项目中的案例作为分析,帮助理解ImportBeanDefinitionRegistrar。

笔者在spring-boot工程的项目中使用了Elastic-Job v1.1.1版本,由于该版本Elastic-Job不支持不支持注解式配置Job Bean,笔者嫌在spring-boot中再引入XML不够方便和友好,故简单自己实现了Elastic-Job对注解支持的模块。其中就使用到了Spring提供的ImportBeanDefinitionRegistrar扩展点。

原有的Elastic——Job的XML配置主要分为两大类,第一类是任务注册中心的配置,第二类是Job相关的配置。其中Job分为多种,每种Job的配置方式不一样,这里只实现了对SimpleJob的支持。

首先分析SimpleJob的配置,同Spring Bean的配置差异不大。也是代表Job的标签,然后就是属性的配置,再者就是子元素的Bean的配置。如:

<job:simple id="..." class="..."
registry-center-ref="..."
overwrite="..."
cron="..."
sharding-total-count="..."
sharding-item-parameters="..."
monitor-execution="..."
monitor-port="..."
failover="..."
description="...."
disabled="...">
<job:listener class="..." started-timeout-milliseconds="..." completed-timeout-milliseconds="..."></job:listener>
</job:simple>

一个job:simple用于定义一个Job的配置,这样可以抽象一个注解来描述该Job配置,其中子元素job:listener又是属于这个Job的监听器子元素配置,同样也需要抽象出一个注解用于定义该监听器,如:

/**
* Elastic-job的Simple类型Job对应的注解
*
* @author huaijin
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Component
@Import(ElasticJobRegistrar.class)
public @interface ElasticSimpleJob { String id(); Class<?> classStr(); boolean overwrite() default true; String registryCenterRef(); String jobParameter() default ""; String cron(); String shardingTotalCount(); String shardingItemParameters() default ""; boolean jobFailover() default true; int monitorPort() default 9880; boolean monitorExecution() default false; String description() default ""; String maxTimeDiffSeconds() default ""; String misfire() default ""; String jobShardingStrategyClass() default ""; JobListener jobListener() default @JobListener(startedTimeoutMilliseconds = 0,
completedTimeoutMilliseconds = 0); @Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface JobListener { Class<?> classStr() default Class.class; long startedTimeoutMilliseconds(); long completedTimeoutMilliseconds();
}
}

其中该注解被@Component修饰,表示该注解标注的类是一个Spring Bean,能够被Spring的@Component注解处理检测加载该类的注解属性。使用@Import(ElasticJobRegistrar.class)该配置,表示该注解应用被哪个ImportBeanDefinitionRegistrar实现进行处理。

然后就是实现ImportBeanDefinitionRegistrar,用于处理ElasticSimpleJob注解,将其标注的类注册为Spring中特定类型的BeanDefinition。

/**
* 解析{@link ElasticSimpleJob},注册SpringJobScheduler和SimpleJobConfiguration
*
* @author huaijin
*/
@Component
public class ElasticJobRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware { private Environment environment; @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 获取ElasticSimpleJob注解的属性集合
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata
.getAnnotationAttributes(ElasticSimpleJob.class.getName()));
// 获取job id属性
String id = annoAttrs.getString(BeanDefinitionParserDelegate.ID_ATTRIBUTE);
// 使用Spring提供的建造者模式构造BeanDefinition,其中类型为SpringJobScheduler
BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(SpringJobScheduler.class);
// 设置初始化方法
factory.setInitMethodName("init");
// 增加该Bean的第一个构造参数引用,即对注册中心Bean的引用
factory.addConstructorArgReference(annoAttrs.getString("registryCenterRef"));
// 增加该Bean的第二个构造参数引用,对Job配置的引用
factory.addConstructorArgReference(createJobConfiguration(annoAttrs, registry));
// 增加第三个构造参数引用,是对job listener的引用
factory.addConstructorArgValue(createJobListeners(annoAttrs.getAnnotation("jobListener")));
// 注册该BeanDefinition
BeanDefinitionHolder holder = new BeanDefinitionHolder(factory.getBeanDefinition(), id + "SpringJobScheduler");
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
} @Override
public void setEnvironment(Environment environment) {
this.environment = environment;
} private String createJobConfiguration(final AnnotationAttributes annoAttrs, final BeanDefinitionRegistry registry) {
Class<?> simpleJobConfigurationDto;
try {
simpleJobConfigurationDto = Class.forName("com.dangdang.ddframe.job.spring.namespace.parser.simple." +
"SimpleJobConfigurationDto");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
factory.addConstructorArgValue(annoAttrs.getString(BeanDefinitionParserDelegate.ID_ATTRIBUTE));
factory.addConstructorArgValue(annoAttrs.getClass("classStr"));
factory.addConstructorArgValue(annoAttrs.getString("shardingTotalCount"));
factory.addConstructorArgValue(annoAttrs.getString("cron")); addPropertyValueIfExists(annoAttrs, "shardingItemParameters", factory);
addPropertyValueIfExists(annoAttrs, "jobParameter", factory);
addPropertyValueIfExists(annoAttrs, "jobMonitorExecution", factory);
addPropertyValueIfExists(annoAttrs, "monitorPort", factory);
addPropertyValueIfExists(annoAttrs, "maxTimeDiffSeconds", factory);
addPropertyValueIfExists(annoAttrs, "failover", factory);
addPropertyValueIfExists(annoAttrs, "misfire", factory);
addPropertyValueIfExists(annoAttrs, "jobShardingStrategyClass", factory);
addPropertyValueIfExists(annoAttrs, "description", factory);
String propertyName = "elastic.job.disabled";
addPropertyValueIfExists(environment, propertyName, factory);
addPropertyValueIfExists(annoAttrs, "overwrite", factory);
String result = annoAttrs.getString(BeanDefinitionParserDelegate.ID_ATTRIBUTE) + "Conf";
registry.registerBeanDefinition(result, factory.getBeanDefinition());
return result;
} public List<BeanDefinition> createJobListeners(AnnotationAttributes jobListener) {
List<BeanDefinition> listeners = new ManagedList<>();
Class<?> listenerClass = jobListener.getClass("classStr");
if (listenerClass == Class.class) {
return new ManagedList<>(0);
}
BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(listenerClass);
factory.setScope(BeanDefinition.SCOPE_PROTOTYPE);
if (AbstractDistributeOnceElasticJobListener.class.isAssignableFrom(listenerClass)) {
factory.addConstructorArgValue(jobListener.getNumber("startedTimeoutMilliseconds"));
factory.addConstructorArgValue(jobListener.getNumber("completedTimeoutMilliseconds"));
}
listeners.add(factory.getBeanDefinition());
return listeners;
} protected final void addPropertyValueIfExists(final AnnotationAttributes annoAttrs, final String propertyName,
final BeanDefinitionBuilder factory) {
if (annoAttrs.containsKey(propertyName)) {
Object attributeValue = annoAttrs.get(propertyName);
if (Objects.nonNull(attributeValue)) {
factory.addPropertyValue(propertyName, attributeValue.toString());
}
}
} protected final void addPropertyValueIfExists(final Environment env, final String propertyName,
final BeanDefinitionBuilder factory) {
String propertyValue = env.getProperty(propertyName);
if (propertyValue != null && !propertyValue.isEmpty()) {
factory.addPropertyValue("disabled", propertyValue);
}
}
}

以上实现利用ImportBeanDefinitionRegistrar扩展点,获取ElasticSimpleJob注解的属性,然后将其解析填充到相应类型的BeanDefinition中,最后再将BeanDefinition注册到上下文中。这样就完成了使用ElasticSimpleJob注解配置Job,并能够让Spring正常的加载实例化Job。

基于BeanDefinitionParser扩展

BeanDefinitionParser是Spring提供的对XML解析生成BeanDefinition的扩展点,应用可以扩展该接口,提供自定义XML Tag的解析能力,并生成BeanDefinition注册至上下文中。

本节将通过定义自定义xsd,编写自定义的XML配置,编写BeanDefinitionParser扩展实现来展示基于BeanDefinitionParser扩展。主要分为以下几个步骤:

  • 定义应用自身的xsd(XML Schema)
  • 编写Spring XML配置
  • 编写BeanDefinitionParser实现
  • 编写自定义的NameSpaceHandler,其中需要注册以上实现的BeanDefinitionParser
  • 配置集成BeanDefinitionParser和xsd至Spring中

首先自定义的XML Schema,这里使用xsd方式(关于dtd,读者可以自行研究)。如下:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.huaijin.com/schema/MyBean"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.huaijin.com/schema/MyBean"
elementFormDefault="qualified"> <xsd:element name="MyBean">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string" use="required"></xsd:attribute>
<xsd:attribute name="name" type="xsd:string"></xsd:attribute>
<xsd:attribute name="class" type="xsd:string" use="required"></xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>

该xsd自定义了XML Tag MyBean的描述。MyBean有三个基本属性id,name,class。

然后再使用自定义的XML Tag定义Bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:Mybean="http://www.huaijin.com/schema/MyBean"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.huaijin.com/schema/MyBean http://www.huaijin.com/schema/MyBean/MyBean.xsd">
<!-- 利用自定义的XML Tag定义Bean -->
<Mybean:MyBean id="myHelloService" class="com.learn.ioc.bean.parser.extend.MyHelloService"></Mybean:MyBean>
</beans>

再编写BeanDefinitionParser实现:

/**
* 自定义Bean定义解析器
*
* @author huaijin
*/
public class MyBeanBeanDefinitionParser implements BeanDefinitionParser { private static final String TAG_ID = "id";
private static final String TAG_CLASS = "class"; @Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 获取id属性
String id = element.getAttribute(TAG_ID);
// 获取class属性
String classType = element.getAttribute(TAG_CLASS);
// 校验id和class属性
if (id == null || id.isEmpty()) {
throw new BeanDefinitionParsingException(new Problem("id must be not null.",
new Location(parserContext.getReaderContext().getResource())));
}
if (classType == null || classType.isEmpty()) {
throw new BeanDefinitionParsingException(new Problem("classType must be not null.",
new Location(parserContext.getReaderContext().getResource())));
}
// 使用class创建BeanDefintion
BeanDefinition beanDefinition;
try {
beanDefinition = BeanDefinitionReaderUtils.createBeanDefinition(null, classType,
parserContext.getReaderContext().getBeanClassLoader());
} catch (ClassNotFoundException e) {
throw new BeanDefinitionParsingException(new Problem("classType can't exist.",
new Location(parserContext.getReaderContext().getResource())));
}
// 使用id作为BeanName注册该BeanDefinition至上下文中
BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, id);
BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder, parserContext.getRegistry());
return beanDefinition;
}
}

然后便是编写NameSpaceHandler,注册以上的BeanDefinitionParser:

/**
* 自定义扩张的命名空间解析器
*
* @author huaijin
*/
public class MyBeanNamespaceHandler extends NamespaceHandlerSupport { @Override
public void init() {
// 注册BeanDefinitionParser
registerBeanDefinitionParser("MyBean", new MyBeanBeanDefinitionParser());
}
}

最后再配置xsd和自定义的BeanDefinitionParser至Spring中。这个过程需要在resource目录下配置两个文件/META-INF/spring.handlers和/META-INF/spring.schemas。

其中spring.handlers中定义命名空间和xsd文件位置的映射,使得Spring能够根据命名空间找xsd文件方便对XML配置进行格式校验;

spring.schemas中定义命名空间和NameSpaceHandler的映射,使得Spring在处理XML命名空间时能够获取具体的NameSpaceHandler,通过其获得注册的BeanDefinitionParser针对性处理该命名空间的XML配置。

spring.handlers中配置如下:

http\://www.huaijin.com/schema/MyBean=com.learn.ioc.bean.parser.extend.MyBeanNamespaceHandler

spring.schemas中配置如下:

http\://www.huaijin.com/schema/MyBean/MyBean.xsd=com/learn/ioc/bean/parser/extend/MyBean.xsd

最后再编写测试主类,从上下文中后去该自定义的配置的Bean,并调用方法执行验证

/**
* 自定义扩展解析器Demo
*
* @author huaijin
*/
public class ExtendBeanDefinitionParserDemo { public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("/bean.parser-extend/extend-parser.xml");
MyHelloService myHelloService = context.getBean("myHelloService", MyHelloService.class);
myHelloService.sayMyHello();
}
}

执行结果如下:

hello, you!

到这里,关于BeanDefinition的扩展点实战基本都详细介绍结束,其中关于各种方式都详细编码,如果需要了解更多详情,可以参考Spring官网对各种方式的描述。下节将从源码实现的角度分析这几种方式的扩展原理。

BeanDefinition扩展点的原理

本节针对以上的四种方式的扩展点原理展开介绍,关于BeanFactoryPostProcessor的原理在前文中已经介绍,这里不再赘述。关于BeanDefinitionRegistryPostProcessor的原理在BeanFactoryPostProcessor一文的源码分析中也有涉猎,即在Spring上下文创建完内部的BeanFactory,载入BeanDefinition后,在实例化和唤醒BeanFactoryPostProcessor的逻辑前,预留了BeanDefinitionRegistryPostProcessor的扩展,允许应用向BeanFactory中注册更多BeanDefinition,以背后续的BeanFactoryPostProcessor进行后置处理。

同时需要注意的是BeanDefinitionRegistryPostProcessor本身也是BeanFactoryProcessor的扩展抽象:

// 继承BeanFactoryProcessor
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { // 该扩展点提供了BeanDefinitionRegistry,利用其可以向上下文中注册BeanDefinition
// 同时也能修改同时也能修改BeanDefinitionRegistry的属性
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

关于ImportBeanDefinitionRegistrar的原理,其中ImportBeanDefinitionRegistrar主要是Spring在处理@Configurer注解时的扩展点,需要了解Spring注解配置处理原理的基础,才能够清晰的理解,故本文中不做详细介绍,待后续文章中介绍Spring注解配置原理中再细说ImportBeanDefinitionRegistrar的原理。

本节主要针对BeanDefinitionParser的原理实现做详细介绍。

为了更好的讲解BeanDefinitionParser,这里先总结下几个与其相关的重要组件:

  • DefaultBeanDefinitionDocumentReader
  • BeanDefinitionParserDelegate
  • DefaultNamespaceHandlerResolver
  • NameSpaceHandler

DefaultBeanDefinitionDocumentReader和BeanDefinitionParserDelegate在前面的Spring源码系列 — BeanDefinition文章有过源码程度的分析。前者主要负责读取Document文档中的BeanDefinition配置,后者负责解析配置并负责委托处理其他的命名空间配置的解析。

DefaultNamespaceHandlerResolver是用于解析命名空间处理器,它主要提供根据XML命名空间解析NameSpaceHandler的能力。

NameSpaceHandler提供两个能力,其一是能够注册BeanDefinitionParser和XML Tag的映射关系;其二提供根据XML Tag寻找BeanDefinitionParser。

总结下,即DefaultNamespaceHandlerResolver包含XML命名空间和NameSpaceHandler的映射关系,NameSpaceHandler中包含XML Tag和BeanDefinitionParser的映射关系。

触发BeanDefinitionParser XML Tag的流程如下:

接下来就从源码的角度分析下这个流程。仍然回到DefaultBeanDefinitionDocumentReader中parseBeanDefinitions方法:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 判断XML根元素是否为默认Beans命名空间,如果是则按照默认方式解析
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;
// 判断子元素是否为默认的Beans命名空间,如果是则解析Beans
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
// 如果不是,则认为是自定义的
delegate.parseCustomElement(ele);
}
}
}
}
else {
// 如果不是,则认为是自定义的
delegate.parseCustomElement(root);
}
}

对于非Beans命名空间而言,主要进入delegate.parseCustomElement分支,解析自定义的XML Tag。

再来详细看parseCustomElement实现:

 // 解析BeanDefinition
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
// 获取该Element对应的命名空间,利用了Java XML提供的接口
String namespaceUri = getNamespaceURI(ele);
// 根据命名空间获取NameSpaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 利用NameSpaceHandler解析Element
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

其中主要就是根据命名空间获取NameSpaceHandler,然后利用handler解析XML ELemnent为BeanDefinition。主要关注NameSpaceHandler的获取过程:

@Override
public NamespaceHandler resolve(String namespaceUri) {
// 获取命名空间和NameSpaceHandler的映射关系
Map<String, Object> handlerMappings = getHandlerMappings();
// 根据命名空间获取NameSpaceHandler
Object handlerOrClassName = handlerMappings.get(namespaceUri);
// 如果为空,则返回null
if (handlerOrClassName == null) {
return null;
}
// 如果直接是NameSpaceHandler的实例,则直接返回
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
// 否则认为是NameSpaceHandler实现的类名
else {
// 转化为类名
String className = (String) handlerOrClassName;
try {
// 获取对应的Class对象
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
// 根据Class对象,创建NameSpaceHandler实例
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 执行初始化方法
namespaceHandler.init();
// 覆盖原有的映射关系,缓存作用
handlerMappings.put(namespaceUri, namespaceHandler);
// 返回NameSpaceHandler
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "] not found", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "]: problem with handler class file or dependent class", err);
}
}
}

以上的逻辑也非常简单,首先获取命名空间和NameSpaceHandler的映射关系,然后根据命名空间获取相应的NameSpaceHandler。这里主要需要关注的是如何获取命名空间和NameSpaceHandler的映射关系:

private Map<String, Object> getHandlerMappings() {
Map<String, Object> handlerMappings = this.handlerMappings;
// 如果handlerMappings不为空,则直接返回,否则加载handlerMappings
if (handlerMappings == null) {
// 对handlerMappings的修改有数据竞态,同步
synchronized (this) {
// 双重锁定检查,如果仍然为空,则加载handlerMappings
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
if (logger.isDebugEnabled()) {
logger.debug("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
}
try {
// 根据handlerMappingsLocation指定的文章,使用工具加载properties
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}
// 将properties转为ConcurrentHashMap
handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
}
}
}
}
return handlerMappings;
}

需要注意的是,这里Spring使用了约定配置的做法,对于获取映射关系配置,是由Spring框架内置和应用扩展的。在spring中定义了默认的配置文件位置:

public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

即在类路径下的META-INF/spring.handlers中配置。这是spring约定。所以上节的案例中也配置该文件。同时在spring的其他模块,如:beans、context、aop中都有该配置文件。

beans模块中配置如下:

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

context模块中配置如下:

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

aop模块配置如下:

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

DefaultNameSpaceHandlerResovler中中持有命名空间和命名空间处理器的映射关系。在获取到相应的命名空间处理器后,需要进行初始化。初始化的过程就是注册BeanDefinitionParser的过程,该过程主要是建立XML Tag与BeanDefinitionParser的之间的映射关系。如上节的案例中,建立了"MyBean"的Tag和MyBeanDefinitionParser之间的关系。这里以ContextNamespaceHandler为例,讲解其init方法的细节:

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

    // 初始化,注册BeanDefinitionParser,建立XML Tag与BeanDefinitionParser之间的关系
@Override
public void init() {
// 注册PropertyPlaceholderBeanDefinitionParser,让其解析Tag:"property-placeholder"
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
// 注册PropertyOverrideBeanDefinitionParser,让其解析Tag:"property-override"
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
// 注册AnnotationConfigBeanDefinitionParser,让其解析Tag:"annotation-config"
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
// 注册ComponentScanBeanDefinitionParser,让其解析Tag:"ComponentScanBeanDefinitionParser"
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
// 注册LoadTimeWeaverBeanDefinitionParser,让其解析Tag:"load-time-weaver"
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
// 注册SpringConfiguredBeanDefinitionParser,让其解析Tag:"spring-configured"
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
// 注册MBeanExportBeanDefinitionParser,让其解析Tag:"mbean-export"
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
// 注册MBeanServerBeanDefinitionParser,让其解析Tag:"mbean-server"
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}

从以上ContextNameSpaceHandelr中可以看出Context命名空间下的各个XML Tag所对应的BeanDefinitionParser是什么。比如常用的component:scan标签由ComponentScanBeanDefinitionParser负责解析。关于这些BeanDefinitonParser的实现细节,将在下篇Spring中注解处理中挑一些详细介绍,这里不再详述。

再继续看handler.parse的实现,其中主要是根据元素的Tag寻找对应的BeanDefinitionParser,然后解析XML Element为对应的BeanDefinition。仍然以ContextNameSpaceHandler为例介绍:

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 查找BeanDefinitionParser,然后解析Element
return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
// 获取Element的Tag
String localName = parserContext.getDelegate().getLocalName(element);
// 根据Tag获取BeanDefinitionParser
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
// 返回Parser
return parser;
}

在NameSpaceHandler中是利用Map存储Tag与BeanDefinitionParser之间的映射关系的。

到这里,应该能从头至尾非常清楚的了解了BeanDefinitionParser支撑应用自定义扩展XML Tag解析BeanDefintion的原理了。

总结

本文主要介绍了Spring中BeanDefition中处理的扩展点。主要从扩展点的方式、实战案例、原理三个方面层层深入介绍。

参考

Extensible XML authoring