spring源码学习三、Spring自定义标签的使用与解析原理

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标签的使用

这里我们首先定义一个自定义的标签,然后进行基本使用,最后再对自定义标签进行源码追踪和分析

自定义标签的基本步骤:

  1. 创建一个扩展的组件(即创建一个POJO)
  2. 定义一个XSD文件描述组件的内容
  3. 创建一个文件,实现接口,用来解析XSD文件中的定义和组件定义
  4. 创建一个Hadle文件,扩展自,目的是将组件注册到spring容器
  5. 编写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实例

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值