前言

前文介绍了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

Spring源码系列 — BeanDefinition扩展点的更多相关文章

  1. Spring源码系列 — BeanDefinition

    一.前言 回顾 在Spring源码系列第二篇中介绍了Environment组件,后续又介绍Spring中Resource的抽象,但是对于上下文的启动过程详解并未继续.经过一个星期的准备,梳理了Spri ...

  2. Spring源码系列 — 注解原理

    前言 前文中主要介绍了Spring中处理BeanDefinition的扩展点,其中着重介绍BeanDefinitionParser方式的扩展.本篇文章承接该内容,详解Spring中如何利用BeanDe ...

  3. Ioc容器beanDefinition-Spring 源码系列(1)

    Ioc容器beanDefinition-Spring 源码系列(1) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器 ...

  4. Spring源码系列 — Bean生命周期

    前言 上篇文章中介绍了Spring容器的扩展点,这个是在Bean的创建过程之前执行的逻辑.承接扩展点之后,就是Spring容器的另一个核心:Bean的生命周期过程.这个生命周期过程大致经历了一下的几个 ...

  5. 事件机制-Spring 源码系列(4)

    事件机制-Spring 源码系列(4) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProcess ...

  6. Ioc容器依赖注入-Spring 源码系列(2)

    Ioc容器依赖注入-Spring 源码系列(2) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostPr ...

  7. Ioc容器BeanPostProcessor-Spring 源码系列(3)

    Ioc容器BeanPostProcessor-Spring 源码系列(3) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Io ...

  8. AOP执行增强-Spring 源码系列(5)

    AOP增强实现-Spring 源码系列(5) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProc ...

  9. Spring源码系列(二)--bean组件的源码分析

    简介 spring-bean 组件是 Spring IoC 的核心,我们可以使用它的 beanFactory 来获取所需的对象,对象的实例化.属性装配和初始化等都可以交给 spring 来管理. 本文 ...

随机推荐

  1. 关于excel中的vlookup就是查找当前列对应的下一列的值的使用

    关于excel中的vlookup就是查找当前列对应的下一列的值的使用 vlookup的使用一些说明 vlookup函数一个4个参数解释下 vlookup(查找的值,表格范围,表格范围中第几列的值,0是 ...

  2. 面试官,我会写二分查找法!对,没有 bug 的那种!

    前言科普 第一篇二分搜索论文是 1946 年发表,然而第一个没有 bug 的二分查找法却是在 1962 年才出现,中间用了 16 年的时间. 2019 年的你,在面试的过程中能手写出没有 bug 的二 ...

  3. 用.net core实现反向代理中间件

    最近在将一些项目的rest api迁移到.net core中,最开始是用的Nginx做反向代理,将已经完成切换的部分切入系统,如下图所示: 由于迁移过程中也在进行代码重构,需要经常比较频繁的测试,以保 ...

  4. 织梦DEDECMS本地后台操作卡顿的解决方法

    打开/data/common.inc.php,把默认的$cfg_dbhost = ‘localhost‘修改为$cfg_dbhost = ‘127.0.0.1’;保存.然后你会发现后台操作起来流畅多了 ...

  5. Java生鲜电商平台-API请求性能调优与性能监控

    Java生鲜电商平台-API请求性能调优与性能监控 背景 在做性能分析时,API的执行时间是一个显著的指标,这里使用SpringBoot AOP的方式,通过对接口添加简单注解的方式来打印API的执行时 ...

  6. Linux常用命令(2)

    3.帮助命令 A,帮助命令:man    B,其他帮助命令 3.1,格式:man [命令名] 查看命令拥有哪个级别的帮助:                                       ...

  7. SQL 带有output、inserted、deleted

    因需求的关系需要将修改的值返回,故查了些资料发现了OUTPUT这个好东西,现记录下来以防以后忘记 使用例子: 1.对于INSERT,可以引用inserted表以查询新行的属性.    insert i ...

  8. JavaScript继承的最初设想

    JavaScript没有真正的类(class)和实例(instance),而是靠一种奇特的原型链模式,来实现继承. 在Brendan Eich设计之初,Javascript里面都是对象,必须有一种机制 ...

  9. ble蓝牙扫描几种方式

    有空再更新内容 方式一BluetoothAdapter层扫描回调 在高版本api已过时 方式二BluetoothLeScanner层扫描回调 android>= 5.0之后的版本推荐使用 方式三 ...

  10. ES6变量的解构赋值(二)对象的解构赋值

    前面我们知道,数组的结构赋值需要按顺序进行赋值, let [a,,c] = [,,] console.log(a); console.log(c);//3 let [a,b] = [1];consol ...