自定义Spring命名空间使JAXB更容易

首先,让我大声说出来: Spring不再是XML繁重的了 。 事实上,如今,您可以使用大量注释, Java配置和Spring Boot来编写几乎没有XML或根本没有XML的Spring应用程序。 认真地停止谈论Spring和XML,这已经成为过去。

话虽这么说,您仍然可能出于以下几个原因而使用XML:您受困于旧代码库,出于其他原因选择了XML,或者将Spring用作某些框架/平台的基础。 最后一种情况实际上很常见,例如Mule ESB和ActiveMQ在下面使用Spring来连接它们的依赖项。 而且,Spring XML是他们配置框架的方式。 但是,使用纯Spring <bean/>配置消息代理或企业服务总线会很麻烦且冗长。 幸运的是,Spring支持编写可嵌入标准Spring配置文件中的自定义名称空间 。 这些自定义的XML代码段在运行时进行了预处理,并且可以以简洁,令人愉悦的格式(在XML允许的范围内)一次注册许多bean定义。 从某种意义上说,自定义名称空间就像在运行时扩展为多个bean定义的宏。

为了让您了解我们的目标,请想象一个具有多个业务实体的标准“企业”应用程序。 对于每个实体,我们定义三个几乎相同的bean:存储库,服务和控制器。 它们始终以相似的方式进行布线,只是细节有所不同。 首先,我们的Spring XML如下所示(我正在粘贴带有缩略图的屏幕截图,以免引起您的注意,它巨大且huge肿): XML文件

这是一个“分层的”架构,因此我们将称为onion的自定义命名空间-因为洋葱具有层次性 -并且因为以这种方式设计的系统使我哭泣。 到本文结尾,您将学习如何将这堆XML折叠为:

<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns:b="http://www.springframework.org/schema/beans"xmlns="http://nurkiewicz.blogspot.com/spring/onion/spring-onion.xsd"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://nurkiewicz.blogspot.com/spring/onion/spring-onion.xsd http://nurkiewicz.blogspot.com/spring/onion/spring-onion.xsd"><b:bean id="convertersFactory" class="com.blogspot.nurkiewicz.onion.ConvertersFactory"/><converter format="html"/><converter format="json"/><converter format="error" lenient="false"/><entity class="Foo" converters="json, error"><page response="404" dest="not-found"/><page response="503" dest="error"/></entity><entity class="Bar" converters="json, html, error"><page response="400" dest="bad-request"/><page response="500" dest="internal"/></entity><entity class="Buzz" converters="json, html"><page response="502" dest="bad-gateway"/></entity></b:beans>

仔细观察,仍然是该框架可以完全理解的Spring XML文件-您将学习如何实现这一点。 您可以为每个顶级定制XML标记运行任意代码,例如,一次出现的<entity/>寄存器存储库,服务和控制器Bean定义全部一次。 要实现的第一件事是为我们的名称空间编写自定义XML模式。 这并不难,并且将允许IntelliJ IDEA以XML显示代码完成:

<?xml version="1.0" encoding="UTF-8"?>
<schemaxmlns:tns="http://nurkiewicz.blogspot.com/spring/onion/spring-onion.xsd"xmlns="http://www.w3.org/2001/XMLSchema"targetNamespace="http://nurkiewicz.blogspot.com/spring/onion/spring-onion.xsd"elementFormDefault="qualified"attributeFormDefault="unqualified"><element name="entity"><complexType><sequence><element name="page" type="tns:Page" minOccurs="0" maxOccurs="unbounded"/></sequence><attribute name="class" type="string" use="required"/><attribute name="converters" type="string"/></complexType></element><complexType name="Page"><attribute name="response" type="int" use="required"/><attribute name="dest" type="string" use="required"/></complexType><element name="converter"><complexType><attribute name="format" type="string" use="required"/><attribute name="lenient" type="boolean" default="true"/></complexType></element></schema>

模式完成后,我们必须使用两个文件在Spring中进行注册:

/META-INF/spring.schemas

http\://nurkiewicz.blogspot.com/spring/onion/spring-onion.xsd=/com/blogspot/nurkiewicz/onion/ns/spring-onion.xsd

/META-INF/spring.handlers

http\://nurkiewicz.blogspot.com/spring/onion/spring-onion.xsd=com.blogspot.nurkiewicz.onion.ns.OnionNamespaceHandler

一个将模式URL映射到本地的模式位置,另一个则指向所谓的名称空间处理程序。 此类非常简单–它告诉如何处理来自Spring配置文件中遇到的该名称空间的每个顶级自定义XML标签:

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;public class OnionNamespaceHandler extends NamespaceHandlerSupport {public void init() {registerBeanDefinitionParser("entity", new EntityBeanDefinitionParser());registerBeanDefinitionParser("converter", new ConverterBeanDefinitionParser());}
}

因此,当Spring找到XML的<converter format="html"/> ,它知道需要使用我们的ConverterBeanDefinitionParser 。 请记住,如果我们的自定义标签有子级(例如<entity/> ),则仅对顶级标签调用bean定义解析器。 如何解析和处理儿童取决于我们自己。 好的,因此假设单个<converter/>标记可以创建以下两个bean:

<bean id="htmlConverter" class="com.blogspot.nurkiewicz.onion.Converter" factory-bean="convertersFactory" factory-method="build"><constructor-arg value="html.xml"/><constructor-arg value="true"/><property name="reader" ref="htmlReader"/>
</bean>
<bean id="htmlReader" class="com.blogspot.nurkiewicz.onion.ReaderFactoryBean"><property name="format" value="html"/>
</bean>

bean定义解析器的职责是以编程方式注册以XML定义的bean定义。 我不会详细介绍API,但是将其与上面的XML代码段进行比较,它们相互之间非常接近:

import org.w3c.dom.Element;public class ConverterBeanDefinitionParser extends AbstractBeanDefinitionParser {@Overrideprotected AbstractBeanDefinition parseInternal(Element converterElement, ParserContext parserContext) {final String format = converterElement.getAttribute("format");final String lenientStr = converterElement.getAttribute("lenient");final boolean lenient = lenientStr != null? Boolean.valueOf(lenientStr) : true;final BeanDefinitionRegistry registry = parserContext.getRegistry();final AbstractBeanDefinition converterBeanDef = converterBeanDef(format, lenient);registry.registerBeanDefinition(format + "Converter", converterBeanDef);final AbstractBeanDefinition readerBeanDef = readerBeanDef(format);registry.registerBeanDefinition(format + "Reader", readerBeanDef);return null;}private AbstractBeanDefinition readerBeanDef(String format) {return BeanDefinitionBuilder.rootBeanDefinition(ReaderFactoryBean.class).addPropertyValue("format", format).getBeanDefinition();}private AbstractBeanDefinition converterBeanDef(String format, boolean lenient) {AbstractBeanDefinition converterBeanDef = BeanDefinitionBuilder.rootBeanDefinition(Converter.class.getName()).addConstructorArgValue(format + ".xml").addConstructorArgValue(lenient).addPropertyReference("reader", format + "Reader").getBeanDefinition();converterBeanDef.setFactoryBeanName("convertersFactory");converterBeanDef.setFactoryMethodName("build");return converterBeanDef;}
}

您是否看到parseInternal()如何接收表示<converter/>标签的XML Element ,提取属性并注册bean定义? 由您决定在AbstractBeanDefinitionParser实现中定义多少个bean。 请记住,我们在这里几乎没有构建配置,还没有实例化。 一旦XML文件被完全解析并且所有bean定义解析器被触发,Spring将开始引导我们的应用程序。 要记住的一件事是最后返回null 。 API期望您返回单个bean定义。 但是,无需限制自己, null就可以了。

我们支持的第二个自定义标签是<entity/> ,它可以一次注册三个bean。 这很相似,因此没那么有趣,请参阅EntityBeanDefinitionParser完整源代码 。 可以找到的一个重要的实现细节是ManagedList的用法。 文档模糊地提到了它,但是它非常有价值。 如果您想知道一个ID来定义要注入的bean列表,那么简单的List<String>是不够的,您必须明确告诉Spring您的意思是一个bean引用列表:

List<BeanMetadataElement> converterRefs = new ManagedList<>();
for (String converterName : converters) {converterRefs.add(new RuntimeBeanReference(converterName));
}
return BeanDefinitionBuilder.rootBeanDefinition("com.blogspot.nurkiewicz.FooService").addPropertyValue("converters", converterRefs).getBeanDefinition();

使用JAXB简化bean定义解析器

好的,因此,现在您应该熟悉自定义Spring命名空间以及它们如何为您提供帮助。 但是,它们要求您使用原始XML DOM API解析自定义标签,因此级别很低。 但是我的队友发现,既然我们已经有了XSD模式文件,为什么不使用JAXB来处理XML解析呢? 首先,我们要求Maven在构建过程中生成表示XML类型和元素的Java bean:

<build><plugins><plugin><groupId>org.jvnet.jaxb2.maven2</groupId><artifactId>maven-jaxb22-plugin</artifactId><version>0.8.3</version><executions><execution><id>xjc</id><goals><goal>generate</goal></goals></execution></executions><configuration><schemaDirectory>src/main/resources/com/blogspot/nurkiewicz/onion/ns</schemaDirectory><generatePackage>com.blogspot.nurkiewicz.onion.ns.xml</generatePackage></configuration></plugin></plugins>
</build>

/target/generated-sources/xjc您会发现几个Java文件。 我喜欢生成的JAXB模型具有一些通用前缀,例如Xml ,可以通过在spring-onion.xsd旁边放置自定义bindings.xjb文件轻松实现:

<bindings version="1.0"xmlns="http://java.sun.com/xml/ns/jaxb"xmlns:xs="http://www.w3.org/2001/XMLSchema"extensionBindingPrefixes="xjc"><bindings schemaLocation="spring-onion.xsd" node="/xs:schema"><schemaBindings><nameXmlTransform><typeName prefix="Xml"/><anonymousTypeName prefix="Xml"/><elementName prefix="Xml"/></nameXmlTransform></schemaBindings></bindings></bindings>

它如何改变我们的自定义bean定义解析器? 以前我们有这个:

final String clazz = entityElement.getAttribute("class");
//...
final NodeList pageNodes = entityElement.getElementsByTagNameNS(NS, "page");
for (int i = 0; i < pageNodes.getLength(); ++i) {  //...

现在我们简单地遍历Java bean:

final XmlEntity entity = JaxbHelper.unmarshal(entityElement);
final String clazz = entity.getClazz();
//...
for (XmlPage page : entity.getPage()) {  //...

JaxbHelper只是一个简单的工具,可从外部隐藏已检查的异常和JAXB机制:

public class JaxbHelper {private static final Unmarshaller unmarshaller = create();private static Unmarshaller create() {try {return JAXBContext.newInstance("com.blogspot.nurkiewicz.onion.ns.xml").createUnmarshaller();} catch (JAXBException e) {throw Throwables.propagate(e);}}public static <T> T unmarshal(Element elem) {try {return (T) unmarshaller.unmarshal(elem);} catch (JAXBException e) {throw Throwables.propagate(e);}}}

几句话作为总结。 首先,我不鼓励您为每个实体自动生成存储库/服务/控制器Bean定义。 实际上,这是一个不好的做法,但是我们所有人都熟悉该领域,因此我认为这将是一个很好的例子。 其次,更重要的是,自定义XML名称空间是一个功能强大的工具,当其他所有东西(即抽象bean , 工厂bean和Java配置)失败时,应将其用作最后的手段。 通常,您会希望在Spring顶部构建的框架或工具中使用这种功能。 在这种情况下,请在GitHub上查看完整的源代码 。

参考: Java和社区博客上的JCG合作伙伴 Tomasz Nurkiewicz的JAXB使自定义Spring命名空间变得更加容易 。

翻译自: https://www.javacodegeeks.com/2014/03/custom-spring-namespaces-made-easier-with-jaxb.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/368477.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用锚点再点事吧丨

上图 接着HTML代码&#xff1a; <div id"color"> <div id"red">红</div> <div id"yellow">黄</div> <div id"black">黑</div> <div id"green">绿</div> </div>…

javaweb笔记1

转载于:https://www.cnblogs.com/webdev8888/p/9164291.html

以太网数据帧的报尾封装字段是什么_16、90秒快速“读懂”数据包的封装、解封装...

数据转发过程概述数据包在相同网段内或不同网段之间转发所依据的原理基本一致。一、数据帧封装TCP封装(四层)当主机建立了到达目的地的TCP连接后&#xff0c;便开始对应用层数据进行封装。主机A会对待发送的应用数据首先执行加密和压缩等相关操作&#xff0c;之后进行传输层封装…

CSS基本知识(慕课网)

1、注释 注解&#xff1a;CSS中注释/*这里是注释的文字*/ HTML中注释<!--这里是注释的文字--> 2、外部式css样式&#xff0c;写在单独的一个文件中 注解&#xff1a; 外部式css样式(也可称为外联式)就是把css代码写一个单独的外部文件中&#xff0c;这个css样式文件以“…

JMS和Spring:有时很重要的小事情

JmsTemplate和DefaultMessageListenerContainer是用于访问JMS兼容MOM的Spring帮助器。 他们的主要目标是在JMS API之上形成一层&#xff0c;并处理诸如事务管理/消息确认之类的基础结构&#xff0c;并隐藏JMS API的某些重复和笨拙的部分&#xff08;保留在那里&#xff1a; JMS…

css实现3D立方体旋转特效

先来看运行后出来的效果 它是在不停运行的一个立方体 先来看html部分的代码 <div class"rect-wrap"> <!--舞台元素&#xff0c;设置perspective&#xff0c;让其子元素获得透视效果。--><div class"container"> <!-- 容器&#…

Eclipse中的Tomcat:6个流行的“如何”问题

学习新技术总是一个艰难的过程。 当您尝试学习将要相互交互的两种技术时&#xff0c;此过程将变得更加困难。 Tomcat和Eclipse是Java EE开发中最流行的先决条件。 因此&#xff0c;要成为一名专业的开发人员&#xff0c;您需要知道如何使用此对执行最需要的操作以及如何进行一些…

adc分辨率和精度的区别_科普 | 传感器的灵敏度、分辨率和精度三者之间有何区别?...

传感器作为一种检测装置&#xff0c;具有微型化、数字化、智能化、多功能化、系统化、网络化的特点。在现代工业生产尤其是自动化生产过程中&#xff0c;需要各种传感器来监视和控制生产过程中的各个参数&#xff0c;使设备工作在正常状态或最佳状态&#xff0c;并使产品达到最…

Linux上的HotSpot GC线程CPU占用空间

以下问题将测试您对Linux操作系统上运行的Java应用程序的垃圾回收和高CPU故障排除的知识。 当调查过多的GC和/或CPU利用率时&#xff0c;此故障排除技术尤其重要。 它将假定您没有访问高级监视工具的能力&#xff0c;例如Compuware dynaTrace甚至JVisualVM。 将来会介绍使用此类…

tornado-简介和原理

tornado-设计初衷 1. 追求小而精 2. epoll IO多路复用和协程 3. 支持WebSocket 4. 单线程程序(GIL限制&#xff0c;本身某种意义上不启动多进程就是单线程程序) # Python GIL介绍详情 tronado应用场景 1. 大量的http请求连接(大量的用户请求&#xff0c;要求并发性和高性能) tr…

前端学习笔记--HTTP缓存

原文地址&#xff1a;https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hlzh-cn 缓存并重用之前获取的资源的能力是性能优化的一个关键方面。 每个浏览器都自带了 HTTP 缓存实现功能&#xff0c;只需要确保每个服务…

如何用Veripacks替换构建模块

比较下面的两棵树。 在这两种情况下&#xff0c;目标都是拥有一个具有两个独立模块&#xff08; frontend和reporting &#xff09;和一个共享/公用模块&#xff08; domain &#xff09;的应用程序。 frontend的代码不应访问reporting代码&#xff0c;反之亦然。 两个模块都可…

JS的DOM和BOM

* JavaScript分三个部分: ECMAScript标准:JS的基本的语法DOM:Document Object Model --->文档对象模型----操作页面的元素BOM:Browser Object Model----->浏览器对象模型---操作的是浏览器一、DOM对象 文档:把一个html文件看成是一个文档,由于万物皆对象,所以把这个文档看…

数据库(11)-- Hash索引和BTree索引 的区别

索引是帮助mysql获取数据的数据结构。最常见的索引是Btree索引和Hash索引。 不同的引擎对于索引有不同的支持&#xff1a;Innodb和MyISAM默认的索引是Btree索引&#xff1b;而Mermory默认的索引是Hash索引。 Hash索引 哈希索引包含以数组形式组织的 Bucket 集合。 哈希函数将索…

MySQL的复制:MySQL系列之十三

一、MySQL复制相关概念 主从复制&#xff1a;主节点将数据同步到多个从节点级联复制&#xff1a;主节点将数据同步到一个从节点&#xff0c;其他的从节点在向从节点复制数据同步复制&#xff1a;将数据从主节点全部同步到从节点时才返回给用户的复制策略叫同步复制异步复制&…

将NetBeans代码模板弯曲到我的意愿

任何阅读过我关于NetBeans的文章的人都知道&#xff0c;我真的很喜欢NetBeans的众多功能。 但是&#xff0c;最近&#xff0c;我发现自己对NetBeans特定功能的特定问题越来越恼火。 最终&#xff0c;它使我烦恼不已&#xff0c;促使我开始研究如何根据自己的喜好禁用或更改该功…

MySQL安装过程

最近试着重装了下MySQL&#xff0c;安装过程很简单&#xff0c;希望能帮助大家 mysql安装过程 1. 下载&#xff1a; 我下载的是64位系统的zip包&#xff1a; 下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 下载zip的包。 下载后解压到对应目录 如&#xff1a;D:…

极简的MyBatis在Spring Boot下的配置

以我的一个项目为例。 0、项目结构&#xff1a; 1、POM中添加MyBatis的依赖&#xff1a; <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.1</version> …

JPA –我应该成为懒惰的极端主义者吗?

当您与开发人员讨论将对象映射到关系数据库时&#xff0c;他们经常抱怨JPA性能差&#xff0c;JPA提供程序的行为不可预测等。通常&#xff0c;在对话的某些时候&#xff0c;您会听到&#xff1a; “让我们完全放弃这项技术&#xff0c;我们在上个月的会议上看到了更好的东西。 …

mysql恢复 报错_Mysql 数据恢复报错

1.测试mysql binlog 数据恢复功能&#xff0c;我的mysql版本是5.7.172.删了数据之后&#xff0c;执行show binlog events 命令得到如下记录3.找到开始删除和结束删除的位置&#xff0c;然后执行恢复命令:mysqlbinlog --no-defaults --start-position8991 --stop-position290468…