spring源码深度解析—自定义标签解析
1. 概述
之前我们已经介绍了spring中默认标签的解析,解析来我们将分析自定义标签的解析,我们先回顾下自定义标签解析所使用的方法,如下图所示:
我们看到自定义标签的解析是通过BeanDefinitionParserDelegate.parseCustomElement(ele)进行的,解析来我们进行详细分析。
2. 自定义标签的使用
2.1 自定义标签的步骤
在spring中提供了扩展schema的支持,扩展Spring自定义标签配置大致需要以下几个步骤(前提是要把Spring的Core包加入项目中)。
(1)创建一个需要扩展的组件。
(2)定义一个XSD文件描述组件内容。
(3)创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义。
(4)创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器。
(5)编写Spring.handlers和Spring.schemas文件。
2.2 简单的实现自定义标签
接下来我们就按照流程在简单的实现自定义标签。具体代码如下:
(1)首先定义一个POJO,此POJO只是用来接收配置文件的
public class User {
private String name;
private String mobilePhone;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMobilePhone() {
return mobilePhone;
}
public void setMobilePhone(String mobilePhone) {
this.mobilePhone = mobilePhone;
}
}
(2)定义一个XSD文件,user.xsd来描述上述组件的内容,此文件需要放在META-INF文件夹下
<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.yhl.com/schema/user"
xmlns:tns="http://www.yhl.com/schema/user"
elementFormDefault="qualified">
<element name="user">
<complexType>
<attribute name="id" type="string"/>
<attribute name="name" type="string"/>
<attribute name="mobilePhone" type="string"/>
</complexType>
</element>
</schema>
在上述XSD文件中描述了一个新的targetNamespace,并且创建了一个element user。里面有三个attribute。主要是为了验证Spring配置文件中的自定义格式。再进一步解释,就是,Spring位置文件中使用的user自定义标签中,属性只能是上面的三种,有其他的属性的话,就会报错。
(3)创建文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义
public class UserBeanDefinitionParser implements BeanDefinitionParser {
protected Class getBeanClass(Element ele)
{
return User.class;
}
public BeanDefinition parse(Element element, ParserContext parserContext) {
String id = element.getAttribute("id");
String name = element.getAttribute("name");
String mobilePhone = element.getAttribute("mobilePhone");
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(User.class);
beanDefinition.getPropertyValues().addPropertyValue("name", name);
beanDefinition.getPropertyValues().addPropertyValue("mobilePhone", mobilePhone);
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
return beanDefinition;
}
}
(4)创建一个Handler文件,目的是将组件注册到spring容器,代码如下:
public class UserNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("user",new UserBeanDefinitionParser());
}
}
上述代码很简单,就是在遇到自定义标签类似:user:aaa这种情况下会使用UserBeanDefinitionParser去解析元素。
(5)编写Spring.handlers和Spring.schemas文件,默认位置放在工程的META-INF文件夹下
spring.handlers:
http\://www.yhl.com/schema/user=com.yhl.myspring.demo.custom.UserNamespaceHandler
spring.schemas:
http\://www.yhl.com/schema/user.xsd=META-INF/user.xsd
(6)创建测试配置文件,在配置文件中引入对应的命名空间及对应的XSD后就可以使用自定义标签了,如下代码:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:myname="http://www.yhl.com/schema/user"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.yhl.com/schema/user.xsd">
<myname:user id="custom" name="this is a test custom tag" mobilePhone="123456" />
</beans>
上述步骤就是自定义标签的使用步骤,接下来我们分析下自定义标签的解析。
3. 自定义标签的解析
了解了自定义标签的使用后,接下来我们分析下自定义标签的解析,自定义标签解析用的是方法:parseCustomElement(Element ele, @Nullable BeanDefinition containingBd),进入方法体:
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
上述代码的流程很简单,就是根据bean获取对应的命名空间,根据命名空间调用对应的处理器,根据用户自定义的处理器进行解析。接下来进行详细的分析。
3.1 获取标签命名空间
String namespaceUri = getNamespaceURI(ele);
@Nullable
public String getNamespaceURI(Node node) {
return node.getNamespaceURI();
}
3.2 读取自定义标签处理器
读取自定义标签处理器用的是如下方法:NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
NamespaceHandlerResolver是一个接口,跟踪到其实现类DefaultNamespaceHandlerResolver中实现的方法体:
@Override
@Nullable
public NamespaceHandler resolve(String namespaceUri) {
//获取所有已经配置的所有的handler映射
Map<String, Object> handlerMappings = getHandlerMappings();
//根据命名空间找打对应信息
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
//已做过解析的情况,直接从缓存读取
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
//没有做过解析,则返回的是类路径
String className = (String) handlerOrClassName;
try {
//使用反射将类路径转换成类
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");
}
//初始化类
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
//调用自定义的NamespaceHandler的初始化方法
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
"] for namespace [" + namespaceUri + "]", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
className + "] for namespace [" + namespaceUri + "]", err);
}
}
}
上面方法的流程非常简单,这里面的代码namespaceHandler.init()其实就是用到我们自定义的handler的init方法。我们具体看下getHandlerMappings()方法,进入到函数体:
private Map<String, Object> getHandlerMappings() {
Map<String, Object> handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
synchronized (this) {
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}
Map<String, Object> mappingsToUse = new ConcurrentHashMap<>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, mappingsToUse);
handlerMappings = mappingsToUse;
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
}
}
}
}
return handlerMappings;
}
我们看到代码中使用了PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader)进行了配置文件的读取,handlerMappingsLocation默认初始化为:META-INF/spring.handlers
3.3 标签解析
得到了解析器和分析的元素侯,Spring就可以将解析工作委托给自定义解析器去解析了,对于标签的解析使用的是:NamespaceHandler.parse(ele, new ParserContext(this.readerContext, this, containingBd))方法,进入到方法体内:
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionParser parser = findParserForElement(element, parserContext);
return (parser != null ? parser.parse(element, parserContext) : null);
}
@Nullable
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;
}
此方法就是寻找对应的解析器,进而调用解析器的parse方法。
String localName = parserContext.getDelegate().getLocalName(element);这行代码的作用就是获取元素名称,也就是我们的配置文件中的:
@Override
@Nullable
public final BeanDefinition parse(Element element, ParserContext parserContext) {
AbstractBeanDefinition definition = parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
try {
String id = resolveId(element, definition, parserContext);
if (!StringUtils.hasText(id)) {
parserContext.getReaderContext().error(
"Id is required for element '" + parserContext.getDelegate().getLocalName(element)
+ "' when used as a top-level tag", element);
}
String[] aliases = null;
if (shouldParseNameAsAliases()) {
String name = element.getAttribute(NAME_ATTRIBUTE);
if (StringUtils.hasLength(name)) {
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
}
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
registerBeanDefinition(holder, parserContext.getRegistry());
if (shouldFireEvents()) {
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
postProcessComponentDefinition(componentDefinition);
parserContext.registerComponent(componentDefinition);
}
}
catch (BeanDefinitionStoreException ex) {
String msg = ex.getMessage();
parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
return null;
}
}
return definition;
}
虽然说是对自定义配置文件的解析,但是我们可以看到在这个函数中大部分的大马士用来处理将解析后的AbstractBeanDefinition转换为BeanDefinitionHolder并注册的功能,而真正去做解析的事情委托了给parseInternal,真是这句代码调用了我们的自定义解析函数。在parseInternal中,并不是直接调用自定义的doParse函数,而是进行了一些列的数据准备,包括对beanClass,scope,lazyInit等属性的准备。 我们进入到AbstractSingleBeanDefinitionParser.parseInternal方法中:
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
String parentName = getParentName(element);
if (parentName != null) {
builder.getRawBeanDefinition().setParentName(parentName);
}
//获取自定义标签中的class,此时会调用自定义解析器,如UserBeanDefinitionParser中的getBeanClass方法
Class<?> beanClass = getBeanClass(element);
if (beanClass != null) {
builder.getRawBeanDefinition().setBeanClass(beanClass);
}
else {
//若子类没有重写getBeanClass方法则尝试检查子类是否重写getBeanClassName方法
String beanClassName = getBeanClassName(element);
if (beanClassName != null) {
builder.getRawBeanDefinition().setBeanClassName(beanClassName);
}
}
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
if (containingBd != null) {
// Inner bean definition must receive same scope as containing bean.
builder.setScope(containingBd.getScope());
}
if (parserContext.isDefaultLazyInit()) {
// Default-lazy-init applies to custom bean definitions as well.
builder.setLazyInit(true);
}
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}
通过以上代码的分析,我们已经对spring的自定义标签的使用以及解析有了一定的了解,到此为止我们已经了解spring将bean从配置文件解析到内存的全部过程,接下来我们就要详细介绍spring中bean的加载过程了。