如何通过JAXB自定义绑定在XJC代码生成时将List<LangStringWrapper>转换为Map<String, String>
我之前刚好踩过这个坑,为了把重复的LangString元素转成Map折腾了好一阵,终于摸清楚了完整的实现流程,下面把步骤一步步讲清楚,亲测能用~
首先得确认你的XSD里LangString的结构,应该是类似这样的吧?就是带一个lang属性的字符串扩展类型:
<xs:complexType name="LangString"> <xs:simpleContent> <xs:extension base="xs:string"> <xs:attribute name="lang" type="xs:string" use="required"/> </xs:extension> </xs:simpleContent> </xs:complexType>
如果你的结构是包裹在LangStringWrapper里的(比如Wrapper类包含lang和value字段),核心思路也一样,只是后面转换器里的对象调用要对应调整。
第一步:编写XJC自定义绑定文件
创建一个绑定文件(比如叫bindings.xjb),这是告诉XJC怎么生成我们想要的Map类型的核心配置,有两种配置方式可以选:
方式一:全局配置(所有重复LangString元素都转Map)
如果项目里所有重复的LangString都要转成Map,用这种方式最省心:
<jaxb:bindings version="2.1" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <!-- 全局绑定规则:把LangString类型的重复实例映射为Map --> <jaxb:globalBindings> <jaxb:javaType name="java.util.Map<String, String>" xmlType="xs:LangString" parseMethod="com.yourpackage.LangStringConverter.parseLangStrings" printMethod="com.yourpackage.LangStringConverter.printLangStrings"/> </jaxb:globalBindings> </jaxb:bindings>
方式二:局部配置(仅针对特定元素转Map)
如果只有某个特定的重复LangString元素需要转Map,就用这种精准定位的方式,比如XSD里Product类型下的description元素是重复的LangString:
<jaxb:bindings version="2.1" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <jaxb:bindings schemaLocation="your-schema.xsd"> <!-- 定位到Product类型下的description元素 --> <jaxb:bindings node="//xs:complexType[@name='Product']/xs:sequence/xs:element[@name='description']"> <jaxb:property> <jaxb:baseType name="java.util.Map<String, String>"/> <jaxb:javadoc>键是lang属性值,值是文本内容</jaxb:javadoc> </jaxb:property> </jaxb:bindings> </jaxb:bindings> </jaxb:bindings>
第二步:编写类型转换器类
XJC本身没法直接把带属性的元素集合转成Map,所以我们得自己写一个转换器类,用来在List<LangString>(或List<LangStringWrapper>)和Map<String, String>之间做双向转换——既能支持代码生成时转成Map,也能保证后续XML序列化正常工作。
比如创建com.yourpackage.LangStringConverter,代码如下:
package com.yourpackage; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; // 这里导入XJC默认生成的类,如果是LangStringWrapper就替换成对应的 import com.yourpackage.generated.LangString; public class LangStringConverter { // 把LangString列表转成Map:lang属性当键,文本内容当值 public static Map<String, String> parseLangStrings(List<LangString> langStrings) { Map<String, String> langMap = new HashMap<>(); if (langStrings == null) return langMap; for (LangString ls : langStrings) { // 如果lang属性可能为空,这里可以加判断,比如给默认键"en" langMap.put(ls.getLang(), ls.getValue()); } return langMap; } // 把Map转回LangString列表,用于XML序列化 public static List<LangString> printLangStrings(Map<String, String> langMap) { List<LangString> langStrings = new ArrayList<>(); if (langMap == null) return langStrings; for (Map.Entry<String, String> entry : langMap.entrySet()) { LangString ls = new LangString(); ls.setLang(entry.getKey()); ls.setValue(entry.getValue()); langStrings.add(ls); } return langStrings; } }
如果是LangStringWrapper,只要把代码里的LangString替换成LangStringWrapper,然后调用对应的getter/setter方法(比如wrapper.getLang()、wrapper.setValue())就行。
第三步:用XJC生成代码
命令行方式
直接在命令行指定绑定文件和XSD文件:
xjc -d 生成代码的输出目录 -b bindings.xjb 你的XSD文件路径.xsd
Maven项目方式
如果用Maven管理项目,在pom.xml里配置jaxb2-maven-plugin,加上绑定文件路径:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>jaxb2-maven-plugin</artifactId> <version>2.5.0</version> <executions> <execution> <goals> <goal>xjc</goal> </goals> </execution> </executions> <configuration> <schemaDirectory>${project.basedir}/src/main/resources/xsd</schemaDirectory> <bindingDirectory>${project.basedir}/src/main/resources</bindingDirectory> <bindingFiles>bindings.xjb</bindingFiles> <outputDirectory>${project.build.directory}/generated-sources/jaxb</outputDirectory> </configuration> </plugin>
执行mvn clean install后,就能在指定目录看到生成的类——原本是List<LangString>的字段已经变成Map<String, String>了。
一些要注意的小细节
- 确保XSD里的LangString元素是
maxOccurs="unbounded"的,也就是允许重复出现,不然XJC只会生成单个实例,没法转成Map。 - 如果
lang属性不是必填项,转换器里要处理null键的情况,比如给默认值或者跳过,避免空指针异常。 - 转换器类的包路径要和绑定文件里写的完全一致,不然XJC会找不到转换器直接报错。
我当时就是这么配置的,生成的代码完全符合预期,你可以根据自己的实际XSD结构调整绑定文件和转换器的细节~
内容来源于stack exchange

