上篇博客我们讲到,spring在做xml解析的时候,将xml中的各种属性,都封装到一个GenericBeanDefinition对象中,那么在将xml标签进行解析的时候,会出现两种情况,一种是针对传统(默认)标签的解析,而spring也提供了另外一种自定义标签的解析。

首先我们先来看个自定义的例子。

/**
 * @author monco
 * @data 2020/9/27 15:28
 * @description :自定义需要注入到spring的对象
 */
public class CustomUser {

    private String id;

    private String username;

    private String email;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
/**
 * @author monco
 * @data 2020/9/27 15:37
 * @description : 解析自定义标签 custom
 */
public class MyNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("custom", new UserBeanDefinitionParser());
    }
}
/**
 * @author monco
 * @data 2020/9/27 15:32
 * @description : 将xml中读取的标签 放入到 BeanDefinition 中
 */
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    protected Class getBeanClass(Element element) {
        return CustomUser.class;
    }

    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        String username = element.getAttribute("username");
        String email = element.getAttribute("email");
        if (StringUtils.hasText(username)) {
            builder.addPropertyValue("username", username);
        }
        if (StringUtils.hasText(email)) {
            builder.addPropertyValue("email", email);
        }
    }
}

我们在代码层面需要做的事就是三步,第一步,定义一个需要注册的bean,第二步,将xml中解析的元素转化成BeanDefinition,第三步,将BeanDefinition注册到spring容器中,交给spring容器管理。

下面我们需要配置一些其他的东西。请睁大眼睛 仔细观察。

spring自定义标签原理 spring自定义标签解析_xml

 

 

 我们需要在resources文件夹下,创建META-INF文件夹,在其中创建3个文件,一个xsd文件,一个handlers文件,一个schemas文件。

xsd 文件

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.monco.com/schema/mytags"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.monco.com/schema/mytags"
            elementFormDefault="qualified">
    <!--自定义标签的名字 叫 custom-->
    <xsd:element name="custom">
        <xsd:complexType>
            <xsd:attribute name="id" type="xsd:string"></xsd:attribute>
            <xsd:attribute name="username" type="xsd:string"></xsd:attribute>
            <xsd:attribute name="email" type="xsd:string"></xsd:attribute>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

handlers 文件 指定读取的 id namespace

http\://www.monco.com/schema/mytags=com.monco.bean.custom.MyNamespaceHandler

schemas 文件 指定读取的xsd 位置

http\://www.monco.com/schema/mytags.xsd=META-INF/mytags.xsd

spring.xml 文件 

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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
    http://www.monco.com/schema/mytags
    http://www.monco.com/schema/mytags.xsd
"
       xmlns:monco="http://www.monco.com/schema/mytags"
       default-lazy-init="false">

    <!--自定义标签-->
    <context:component-scan base-package="com.monco"/>

    <!--传统标签-->
    <bean class="com.monco.entity.User" id="user">
        <constructor-arg name="username" value="monco"/>
        <constructor-arg name="password" value="123456"/>
    </bean>

    <!--replace-method-->
    <bean id="replaceClass" class="com.monco.bean.ReplaceClass" lazy-init="false"/>
    <bean id="originClass" class="com.monco.bean.OriginClass">
        <replaced-method name="method" replacer="replaceClass">
            <!--方法可能出现重载的情况,要根据类型和方法名找方法-->
            <arg-type match="java.lang.String"/>
        </replaced-method>
    </bean>

    <monco:custom id="customUser" username="monco" email="18552193820@163.com"/>

</beans>

测试类 

@Test
    public void testCustomTag() {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        CustomUser customUser = (CustomUser) applicationContext.getBean("customUser");
        System.out.println(customUser.getUsername());
    }

运行结果

spring自定义标签原理 spring自定义标签解析_spring_02

 

 

通过上述一个简单的例子,我们可以发现,我们自己定义的Bean竟然奇迹般的被解析到了。现在我们就来分析下,这是why?这些配置文件的作用到底是啥?我们学了这些东西到底有啥用?

上节我们讲到  DefaultBeanDefinitionDocumentReader 中的 parseBeanDefinitions 的方法

spring自定义标签原理 spring自定义标签解析_自定义标签_03

 

我们发现在实际去解析自定义标签的第一步,就是将元素中的namespaceURI读取出来,读取到 namespaceURI 之后,我们就可以 handlers 文件找到对应的 处理类

spring自定义标签原理 spring自定义标签解析_spring自定义标签原理_04

 

通过 文件 来找到处理类的这个过程 我们暂且称之为 SPI 思想。

spring自定义标签原理 spring自定义标签解析_spring自定义标签原理_05

 

我们通过上述步骤可以解决根据namespace 找到对应的处理类,接下来,我们需要执行 parse() 方法,将对应的标签写入BeanDefinition就可以了。主要是SPI的思想。