PS:本文参照《Spring源码深度解析2》与spring官方,仅作个人学习
这章节将会学到什么?
- 我们如何自定义一个spring的标签?
- 了解spring自身的标签比如beans是如何配置的
- spring是如何去解析我们配置的自定义标签
上一章分析了解析spring的默认标签的部分,先回顾一下这里我们要分析的代码根源是从哪里开始的
/**
* 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) {
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);
}
}
此外,我们首先得知道什么是自定义标签?
一、自定义Spring标签的使用
这里我们首先定义一个自定义的标签,然后进行基本使用,最后再对自定义标签进行源码追踪和分析
自定义标签的基本步骤:
- 创建一个扩展的组件(即创建一个POJO)
- 定义一个XSD文件描述组件的内容
- 创建一个文件,实现接口,用来解析XSD文件中的定义和组件定义
- 创建一个Hadle文件,扩展自,目的是将组件注册到spring容器
- 编写Spring.handlers和Spring.schemas
下面我们将具体按照步骤实现:
第一步、创建POJO
package com.trg;
public class User {
private String name;
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
}
这个就是一个单纯的pojo,没啥可说的
第二步、编写xsd文件
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.trg.com/schema/user"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.trg.com/schema/user"
elementFormDefault="qualified">
<xsd:element name="user">
<xsd:complexType>
<xsd:attribute name="id" use="optional" type="xsd:string"/>
<xsd:attribute name="name" use="optional" type="xsd:string"/>
<xsd:attribute name="email" use="optional" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
这里得提一嘴,当时没搞清楚,也是搞了半天。
这里我们在写xsd文件的时候,可以参考下spring官方写的spring-beans.xsd的。
//这里是先定义了一个叫beans的标签
<xsd:element name="beans">
//写了下注解,说明这个标签的作用是什么
<xsd:annotation>
<xsd:documentation></xsd:documentation>
</xsd:annotation>
//定义一个复合类型,它决定了一组元素和属性值的约束和相关信息
<xsd:complexType>
//指定了元素要按照下面的顺序依次出现
<xsd:sequence>
//定义了一个description属性
<xsd:element ref="description" minOccurs="0"/>
//choice:表示其中的元素只能出现一个,选择性的显示
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="import"/>
<xsd:element ref="alias"/>
<xsd:element ref="bean"/>
<xsd:any namespace="##other" processContents="strict" minOccurs="0" maxOccurs="unbounded"/>
</xsd:choice>
<xsd:element ref="beans" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="profile" use="optional" type="xsd:string">
<xsd:annotation>
//.....省略部分代码
unbounded:可选。规定 any 元素在父元素中可出现的最大次数。该值可以是大于或等于零的整数。若不想对最大次数设置任何限制,请使用字符串 “unbounded”。 默认值为 1
minOccurs:可选。规定 any 元素在父元素中可出现的最小次数。该值可以是大于或等于零的整数。若要指定该 any 组是可选的,请将此属性设置为零。 默认值为 1。
差不多参照这种形式,我们可以定义一个属于自己的user.xsd文件
至于第二行的 xmlns=“http://www.trg.com/schema/user” 这个路径,是我们定义在本地的Spring.schemas,它表示,当我们去访问这个路径的时候,从本地文件的Spring.schemas中找到META-INF/user.xsd文件
http\://www.trg.com/schema/user.xsd=META-INF/user.xsd
第四行 targetNamespace="http://www.trg.com/schema/user"表示:指定了当前命名空间的处理逻辑类
http\://www.trg.com/schema/user=com.trg.UserHadle
第三步、创建一个文件来解析解析XSD文件
package com.trg;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
public class UserBeanDeinitionParser extends AbstractSimpleBeanDefinitionParser {
private static final String USER_NAME_ATTRIBUTE = "name";
private static final String USER_EMAIL_ATTRIBUTE = "email";
/**
* 先指定element对应的类
*
* @param element
* @return
*/
@Override
protected Class<?> getBeanClass(Element element) {
return User.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
//从element中解析并提取对应的元素
String name = element.getAttribute(USER_NAME_ATTRIBUTE);
String email = element.getAttribute(USER_EMAIL_ATTRIBUTE);
if (StringUtils.hasText(name)) {
builder.addPropertyValue(USER_NAME_ATTRIBUTE, name);
}
if (StringUtils.hasText(email)) {
builder.addPropertyValue(USER_EMAIL_ATTRIBUTE, email);
}
}
}
第四步、创建Hadle,将组件注册到spring容器中
package com.trg;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class UserHadle extends NamespaceHandlerSupport {
@Override
public void init() {
System.out.println("当前进入user的自定义标签!");
registerBeanDefinitionParser("user", new UserBeanDeinitionParser());
}
}
第五步、编写Spring.handlers和Spring.schemas
Spring.schemas:
http\://www.trg.com/schema/user.xsd=META-INF/user.xsd
Spring.handlers:
http\://www.trg.com/schema/user=com.trg.UserHadle
第六步、在application.xml中写自定义组件
<?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:custom="http://www.trg.com/schema/user"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.trg.com/schema/user
http://www.trg.com/schema/user.xsd" >
<custom:user id = "trg" name="tangruiguo" email="1446232546@qq.com"></custom:user>
</beans>
注意这行:xmlns:custom=“http://www.trg.com/schema/user”
custom表示我们自定义的标签前缀
user是我们在xsd标签中定义的element的name
http://www.trg.com/schema/user 这个地址是我们自定义的地址,当spring在解析这个地址的时候,会去本地找的
第七步、创建一个测试类
package com.trg;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class SprngTest {
public void getBean(){
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
User bean = xmlBeanFactory.getBean(User.class);
System.out.println(bean);
}
public static void main(String[] args) {
SprngTest sprngTest = new SprngTest();
sprngTest.getBean();
}
}
输出结果为
11:11:35.127 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 1 bean definitions from class path resource [beanFactoryTest.xml]
11:11:35.134 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanFactory - Creating shared instance of singleton bean 'trg'
User{name='tangruiguo', email='1446232546@qq.com'}
Process finished with exit code 0
表示我们的自定义标签创建成功。
下面将对自定义标签的解析流程进行源码分析
二、自定义标签的解析
先撸下源码
/**
* Parse a custom element (outside of the default namespace).
* @param ele the element to parse
* @return the resulting bean definition
*/
@Nullable
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
/**解析自定义元素(在默认名称空间之外)
* Parse a custom element (outside of the default namespace).
* @param ele the element to parse
* @param containingBd the containing bean definition (if any)
* @return the resulting bean definition
*/
@Nullable
//containingBd为父类bean,对顶层元素的解析应该设置为Null
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
//获取当前自定义标签的命名空间
String namespaceUri = getNamespaceURI(ele);//namespaceUri:http://www.trg.com/schema/user
if (namespaceUri == null) {
return null;
}
//2.1 根据命名空间找到的对应的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去解析元素
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
2.1 获取标签的命名空间
String namespaceUri = getNamespaceURI(ele);
/**取所提供节点的名称空间URI
* Get the namespace URI for the supplied node.
* 默认实现使用Node.getNamespaceURI。子类可以覆盖默认实现以提供不同的名称空间标识机制。
* <p>The default implementation uses {@link Node#getNamespaceURI}.
* Subclasses may override the default implementation to provide a
* different namespace identification mechanism.
* @param node the node
*/
@Nullable
public String getNamespaceURI(Node node) {
return node.getNamespaceURI();
}
public interface Element extends Node {
注意Element是Node的子类哈
2.2 获取自定义标签的处理器
追踪代码
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
这个方法看下 this.readerContext.getNamespaceHandlerResolver()
/**
* Return the namespace resolver.
* @see XmlBeanDefinitionReader#setNamespaceHandlerResolver
*/
public final NamespaceHandlerResolver getNamespaceHandlerResolver() {
return this.namespaceHandlerResolver;
}
它是进了NamespaceHandlerResolver接口中,而它只有一个默认实现DefaultNamespaceHandlerResolver类
/**解析命名空间URI并返回定位的NamespaceHandler实现
* Locate the {@link NamespaceHandler} for the supplied namespace URI
* from the configured mappings.
* @param namespaceUri the relevant namespace URI
* @return the located {@link NamespaceHandler}, or {@code null} if none found
*/
@Override
@Nullable
public NamespaceHandler resolve(String namespaceUri) {
//获取所有已经配置的handler
//注意这个map中存储的key为命名空间信息,val为对应的handler解析类
Map<String, Object> handlerMappings = getHandlerMappings();
//获取当前命名空间的hadle解析类
Object handlerOrClassName = handlerMappings.get(namespaceUri);//com.trg.UserHadle
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;
}
//..省略异常捕获
}
}
当执行namespaceHandler.init();的时候就会去调用我们自己定义的UserHadle.java类中重写的init()方法了
还有一个函数getHandlerMappings()得清楚,这个函数就是从本地的Spring.handlers中去加载信息的
private Map<String, Object> getHandlerMappings() {
Map<String, Object> handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
synchronized (this) {
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
if (logger.isTraceEnabled()) {
logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
}
try {
//加载Spring.handlers文件到Properties
Properties的数据合并到 mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isTraceEnabled()) {
logger.trace("Loaded NamespaceHandler mappings: " + mappings);
}
handlerMappings = new ConcurrentHashMap<>(mappings.size());
//将Properties的数据合并到handlerMappings中
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;
}
2.3标签的解析
handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
/**
* Parses the supplied {@link Element} by delegating to the {@link BeanDefinitionParser} that is
* registered for that {@link Element}.
*/
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionParser parser = findParserForElement(element, parserContext);
return (parser != null ? parser.parse(element, parserContext) : null);
}
继续去追一下
》》》》第一行的代码
/**
* Locates the {@link BeanDefinitionParser} from the register implementations using
* the local name of the supplied {@link Element}.
*/
@Nullable
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
//获取 <custom:user中的user
String localName = parserContext.getDelegate().getLocalName(element);//user
//这里是根据localName去获取了我们写的UserBeanDeinitionParser
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
注意:上面的this.parsers是一个map容器,
private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();
那你是不是就想问了,这个容器中的数据是什么时候放进去的,回过头再看下之前的代码,我们是不是写了一个UserHadle类,实现了NamespaceHandlerSupport类
public class UserHadle extends NamespaceHandlerSupport {
@Override
public void init() {
System.out.println("当前进入user的自定义标签!");
registerBeanDefinitionParser("user", new UserBeanDeinitionParser());
}
}
对其中的registerBeanDefinitionParser方法追一下
/**
* Subclasses can call this to register the supplied {@link BeanDefinitionParser} to
* handle the specified element. The element name is the local (non-namespace qualified)
* name.
*/
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
没错,这里就是将我们的user和定义的组件放进去了,这是不是就又说通了。
》》》》第二行代码追踪
parser.parse(element, parserContext)
@Override
@Nullable
public final BeanDefinition parse(Element element, ParserContext parserContext) {
//①获取了一个AbstractBeanDefinition的实例
AbstractBeanDefinition definition = parseInternal(element, parserContext);
//当前实例不为空,且当前BeanDefinition不是被包含的BeanDefinition
if (definition != null && !parserContext.isNested()) {
try {
//解析自定义标签中的id id = "trg"
String id = resolveId(element, definition, parserContext);
if (!StringUtils.hasText(id)) {
//如果没有设置id的话,就回报错提示当我们用作顶级标签的时候,必须指定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()) {
//获取name属性
String name = element.getAttribute(NAME_ATTRIBUTE);
if (StringUtils.hasLength(name)) {
//将name属性放到别名的数组中。
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
}
}
//创建一个BeanDefinitionHolder实例,其实是用AbstractBeanDefinition转换成的
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
//去注册Bean,这里之后就跟自定义标签又一样了
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;
}
》》》①
/**
* Creates a {@link BeanDefinitionBuilder} instance for the
* {@link #getBeanClass bean Class} and passes it to the
* {@link #doParse} strategy method.
* @param element the element that is to be parsed into a single BeanDefinition
* @param parserContext the object encapsulating the current state of the parsing process
* @return the BeanDefinition resulting from the parsing of the supplied {@link Element}
* @throws IllegalStateException if the bean {@link Class} returned from
* {@link #getBeanClass(org.w3c.dom.Element)} is {@code null}
* @see #doParse
*/
@Override
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
//创建了一个BeanDefinitionBuilder的实例
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
//查看他有没得父类的元素
String parentName = getParentName(element);
if (parentName != null) {
builder.getRawBeanDefinition().setParentName(parentName);
}
//获取自定义标签的class类
Class<?> beanClass = getBeanClass(element);// com.trg.User
if (beanClass != null) {
// 将其赋值给builder
builder.getRawBeanDefinition().setBeanClass(beanClass);
}
else {
//若检查子类没有重写getBeanCLass方法,则检查子类是否重写了getBeanCLassName方法
String beanClassName = getBeanClassName(element);
if (beanClassName != null) {
builder.getRawBeanDefinition().setBeanClassName(beanClassName);
}
}
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
//这里就有点跟默认标签一样了,看下当前类有没有父类,如果有就使用父类的scope
BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
if (containingBd != null) {
// Inner bean definition must receive same scope as containing bean.
builder.setScope(containingBd.getScope());
}
//看自定义的bean是不是懒加载的
if (parserContext.isDefaultLazyInit()) {
// Default-lazy-init applies to custom bean definitions as well.
builder.setLazyInit(true);
}
//调用子类的doParse方法,解析提供的元素并根据需要填充提供的BeanDefinitionBuilder。
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}
这个方法主要干了个创建了BeanDefinitionBuilder的实例,然后将元素解析填充给了BeanDefinitionBuilder,最后返回了一个beanDefinition实例