您的位置:首页技术文章
文章详情页

如何使用Spring自定义Xml标签

浏览:2日期:2023-07-08 14:25:59
目录前言正文自定义NameSpaceHandler自定义schemaParserDecorator总结前言

在早期基于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 name='Rob Harrop' age='23'/> <bean 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 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配置内部都是通过它来完成的。

以上就是如何使用Spring自定义Xml标签的详细内容,更多关于使用Spring自定义Xml标签的资料请关注好吧啦网其它相关文章!

标签: Spring
相关文章: