如何使用Spring自定义Xml标签

作者:沉迷Spring 时间:2022-11-14 19:01:08 

目录
  • 前言

  • 正文

  • 自定义NameSpaceHandler

  • 自定义schema

  • Parser

  • Decorator

  • 总结

前言

在早期基于Xml配置的Spring Mvc项目中,我们往往会使用<context:component-scan basePackage="">这种自定义标签来扫描我们在basePackae配置里的包名下的类,并且会判断这个类是否要注入到Spring容器中(比如这个类上标记了@Component注解就代表需要被Spring注入),如果需要那么它会帮助我们把这些类一一注入。

正文

在分析这个自定义标签的解析机制前,我先提前剧透这个自定义标签是通过哪个强大的类来解析的吧,就是隶属于spring-context包下的ComponentScanBeanDefinitionParser,这个类在Springboot扫描Bean的过程中也扮演了重要角色。

既然知道了是这个类解析的,那么我们可以通过idea强大的搜索功能来搜它的引用之处了,这边就截图如下:

如何使用Spring自定义Xml标签

可以看到这里面初始化了8个带Parser后缀的各种Parser,从方法registerBeanDefinitionParser看出Spring是通过这个ContextNamespaceHandler来完成对以<context:自定义命名空间开头的标签解析器的注册。我们可以看到Spring内部已经集成了几个常用的NamespaceHandler,截图如下:

如何使用Spring自定义Xml标签

那么我们自己是否可以自定义一个NamespaceHandler来注册我们自定义的标签解析器呢?答案是肯定的。

自定义NameSpaceHandler


final class TestNamespaceHandler extends NamespaceHandlerSupport {

@Override
  public void init() {

//注册parser
     registerBeanDefinitionParser("testBean", new TestBeanDefinitionParser());
     registerBeanDefinitionParser("person", new PersonDefinitionParser());

//注册element的 decorater
     registerBeanDefinitionDecorator("set", new PropertyModifyingBeanDefinitionDecorator());
     registerBeanDefinitionDecorator("debug", new DebugBeanDefinitionDecorator());

//注册 attr的 decorator
     registerBeanDefinitionDecoratorForAttribute("object-name", new ObjectNameBeanDefinitionDecorator());
  }

到这里大家可能会有个疑问,这个NameSpaceHandler是怎么使用的呢?大家如果看了我之前写的文章,那就会知道有一种方式可以配置我们自定义的NamespaceHandler.


public class CustomXmlApplicationContext  extends AbstractXmlApplicationContext {

private static final String CLASSNAME = CustomXmlApplicationContext.class.getSimpleName();
  private static final String FQ_PATH = "org/wonder/frame/customBean";
  private static final String NS_PROPS = format("%s/%s.properties", FQ_PATH, CLASSNAME);

public CustomXmlApplicationContext(String... configLocations) {
     setConfigLocations(configLocations);
        refresh();
  }

@Override
  protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
     super.initBeanDefinitionReader(reader);

//1.指定resolver的 handlerMappingsLocation 就是 NamespaceHandler的 配置文件路径
     NamespaceHandlerResolver resolver = new DefaultNamespaceHandlerResolver(this.getClassLoader(), NS_PROPS);

//2.设置resolver
     reader.setNamespaceHandlerResolver(resolver);
     //3.设置验证模式
     reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_XSD);
     //4.设置entityResolver
     reader.setEntityResolver(new CustomSchemaResolver());
  }

可以看到我们在初始化BeanDefinitionReader的时候我们可以设置NamespaceHandlerResolver并且配置它的NamespaceHandler文件路径。那这个NamespaceHandler配置文件应该怎么写呢?


http\://www.john.com/resource=org.wonder.frame.customBean.TestNamespaceHandler

就一行配置,但是这里有两点要注意:

  • http\://www.john.com/resource要和xsd的targetNamspace一致。

  • http\://www.john.com/resource要和xml配置文件中的自定义namespace一致。

从代码里看出来我们解析自定义标签的时候其实是还需要自定义schema才能完成的。

自定义schema


private static final String CLASSNAME = CustomXmlApplicationContext.class.getSimpleName();
private static final String FQ_PATH = "org/wonder/frame/customBean";
private static final String TEST_XSD = format("%s/%s.xsd", FQ_PATH, CLASSNAME);
private final class CustomSchemaResolver extends PluggableSchemaResolver {

public CustomSchemaResolver() {
     super(CustomXmlApplicationContext.this.getClassLoader());
  }

@Override
  public InputSource resolveEntity(String publicId, String systemId) throws IOException {
     InputSource source = super.resolveEntity(publicId, systemId);
     if (source == null) {
        try{
           //todo 指定了xsd路径
           Resource resource = new ClassPathResource(TEST_XSD);
           source = new InputSource(resource.getInputStream());
           source.setPublicId(publicId);
           source.setSystemId(systemId);
           return source;
        }
        catch (FileNotFoundException ex){

}

}
     return  null;
  }
}

这里我们也通过ClassPathResource设置了自定义的xsd文件路径。我们来看看xsd文件长啥样:


<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://www.john.com/resource"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.john.com/resource"
        elementFormDefault="qualified">

<xsd:element name="person">
     <xsd:complexType>
        <xsd:attribute name="id" type="xsd:string" use="optional" form="unqualified"/>
        <xsd:attribute name="name" type="xsd:string" use="required" form="unqualified"/>
        <xsd:attribute name="age" type="xsd:integer" use="required" form="unqualified"/>
     </xsd:complexType>
  </xsd:element>

<xsd:element name="testBean">
     <xsd:complexType>
        <xsd:attribute name="id" type="xsd:string" use="required" form="unqualified"/>
        <xsd:attribute name="name" type="xsd:string" use="required" form="unqualified"/>
        <xsd:attribute name="age" type="xsd:integer" use="required" form="unqualified"/>
     </xsd:complexType>
  </xsd:element>

<xsd:element name="set">
     <xsd:complexType>
        <xsd:attribute name="name" type="xsd:string" use="required" form="unqualified"/>
        <xsd:attribute name="age" type="xsd:integer" use="required" form="unqualified"/>
     </xsd:complexType>
  </xsd:element>

<xsd:element name="debug"/>
  <xsd:attribute name="object-name" type="xsd:string"/>
</xsd:schema>

Parser

我们先来分析下TestBeanDefinitionParser和PersonDefinitionParser这两者有啥区别:

TestBeanDefinitionParser直接实现了BeanDefinitionParser接口,内部直接定义一个RootBeanDefinition并且注册。


private static class TestBeanDefinitionParser implements BeanDefinitionParser {

@Override
  public BeanDefinition parse(Element element, ParserContext parserContext) {

RootBeanDefinition definition = new RootBeanDefinition();
     definition.setBeanClass(CustomBean.class);

MutablePropertyValues mpvs = new MutablePropertyValues();
     mpvs.add("name", element.getAttribute("name"));
     mpvs.add("age", element.getAttribute("age"));

//1.设置beanDefinition的 属性 propertyValues
     definition.setPropertyValues(mpvs);

//2.获取到beanDefinition的 registry
     parserContext.getRegistry().registerBeanDefinition(element.getAttribute("id"), definition);
     return null;
  }
}

PersonDefinitionParser继承自AbstractSingleBeanDefinitionParser抽象类,内部使用BeanDefinitionBuilder构造器来完成BeanDefinition的创建。


private static final class PersonDefinitionParser extends AbstractSingleBeanDefinitionParser {

@Override
  protected Class<?> getBeanClass(Element element) {
     return CustomBean.class;
  }

@Override
  protected void doParse(Element element, BeanDefinitionBuilder builder) {
     builder.addPropertyValue("name", element.getAttribute("name"));
     builder.addPropertyValue("age", element.getAttribute("age"));
  }
}

Decorator

我们看到在NameSpaceHandler中我们除了parser外还可以定义自定义元素的decorator和自定义attribute的decorator,那这两个decorator是用来干嘛的呢?我们先来看下上述代码中的PropertyModifyingBeanDefinitionDecorator。


private static class PropertyModifyingBeanDefinitionDecorator implements BeanDefinitionDecorator {

@Override
  public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
     Element element = (Element) node;
     //1.获取BeanDefinition
     BeanDefinition def = definition.getBeanDefinition();

MutablePropertyValues mpvs = (def.getPropertyValues() == null) ? new MutablePropertyValues() : def.getPropertyValues();
     mpvs.add("name", element.getAttribute("name"));
     mpvs.add("age", element.getAttribute("age"));

((AbstractBeanDefinition) def).setPropertyValues(mpvs);
     return definition;
  }
}

从decorate方法内部看出这个decorator是用来给我们的BeanDefinition来添加属性的。这样一来我们就可以在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:test="http://www.john.com/resource"
     xmlns:util="http://www.springframework.org/schema/util"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
      http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd
      http://www.john.com/resource http://www.john.com/resource/org/wonder/frame/customBean/CustomXmlApplicationContext.xsd"
  default-lazy-init="true">
  <test:testBean id="testBean" name="Rob Harrop" age="23"/>
  <bean id="customisedTestBean" class="org.wonder.frame.customBean.CustomBean">
     <!-- 自定义标签加 自定义属性 -->
     <test:set name="John wonder" age="36"/>
  </bean>

</beans>

我们看到testBean这个自定义标签定义了两个属性name和age。之后我们在使用这个testBean的时候就可以获取到它的name和age属性了。


CustomBean bean = (CustomBean) beanFactory.getBean("testBean");
System.out.println("name is:" +bean.getName() +" and age is:"+ bean.getAge());

那么ObjectNameBeanDefinitionDecorator这个attribute的Decorator是干嘛的呢?看如下示例


<!--为bean设置自定义Attr-->
<bean id="decorateWithAttribute" class="org.springframework.tests.sample.beans.TestBean" test:object-name="foo"/>

我们可以为这个Bean添加自定义Attribute,那么添加了这个Attribute我们怎么使用呢?看如下示例:


BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("decorateWithAttribute");
assertEquals("foo", beanDefinition.getAttribute("objectName"));

我们通过BeanDefinition的getAttribute就能获取到这个attribute值。

从Spring源码得知BeanDefinition扩展了AttributeAccessor接口,这个接口是用于附加和访问Bean元数据的通用的接口。直接实现这个接口的是AttributeAccessorSupport类。这个类里定义了名为attributes 的LinkedHashMap。

如何使用Spring自定义Xml标签

总结

Spring通过自定义标签和自定义属性实现了很多扩展功能,很多我们常用的Spring配置内部都是通过它来完成的。

来源:https://mp.weixin.qq.com/s?__biz=MzI4OTUwNDU0MA==&mid=2247484011&idx=1&sn=5f7d692c8d786e1d1910c3d7f8d77d6d

标签:Spring,xml,标签
0
投稿

猜你喜欢

  • java实现清理DNS Cache的方法

    2022-07-27 23:11:50
  • Java基础之关键字final详解

    2022-01-28 00:55:31
  • c# winform时钟的实现代码

    2023-04-05 07:40:53
  • IDEA中设置代码自动提示为Alt+/的具体做法

    2022-07-06 14:58:32
  • 如何使用Android注解处理器

    2023-08-16 16:49:05
  • android压力测试命令monkey详解

    2023-06-17 00:36:29
  • Hadoop源码分析六启动文件namenode原理详解

    2021-08-20 01:03:55
  • Java使用JDBC或MyBatis框架向Oracle中插入XMLType数据

    2023-10-21 04:12:22
  • VSCode配置C语言环境的方法

    2022-11-07 18:47:36
  • 老生常谈设计模式之动态代理

    2021-06-12 06:15:50
  • javaWeb使用servlet搭建服务器入门

    2023-11-21 04:47:45
  • SWT(JFace)体验之ViewForm的使用

    2023-10-20 13:02:01
  • 详解SpringCloud的负载均衡

    2022-03-14 03:42:28
  • java 2d画图示例分享(用java画图)

    2023-07-25 22:03:52
  • c#中datagridview处理非绑定列的方法

    2023-06-15 16:52:31
  • 浅谈C#指针问题

    2022-07-19 17:06:00
  • Java安全-ClassLoader

    2023-08-18 02:12:21
  • Android实现加载对话框

    2023-10-24 14:48:36
  • C#实现FTP客户端的案例

    2023-06-15 19:46:47
  • Android开发之开发者头条(二)实现左滑菜单

    2022-02-28 11:18:31
  • asp之家 软件编程 m.aspxhome.com