如何解决使用带有 JAXB 生成类的 Mapstruct 没有隐式 JAXBElement 转换对于可空和可选的中间元素 pom.xmltest.xsdTest.java由 XSD 的 JAXB maven 插件生成TestMapper.java测试DTOTestMapper.javaTestMapperImpl.javaTestMapper.javaTestMapper.javaTestMapperImpl.java
我在将数据从 JAXB 生成的类(使用 jaxb2-maven-plugin
,基于 XSD)映射到简单的 DTO,使用 Mapstruct 时遇到问题。
总的来说,一切正常,但我在特定情况下遇到问题:
- 我的 XML 元素在 XSD 定义中既是可空的 (
nillable="true"
) 也是可选的 (minOccurs="0"
) - 在为 Mapstruct 的映射定义的源路径中,此 xml 元素不是“终端”
我当然不能对 XSD 采取行动,因为它是我们无法修改的第三方输入。
我做了一个简单的用例来演示这种情况。
要复制的文件
pom.xml
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>2.5.0</version>
<executions>
<execution>
<id>xjc</id>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<sources>
<source>src/main/resources/xsd/test.xsd</source>
</sources>
<outputDirectory>${project.build.directory}/generated-sources</outputDirectory>
<packageName>test.generated</packageName>
<clearOutputDir>false</clearOutputDir>
</configuration>
</plugin>
<plugin>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<defaultOutputDirectory>
${project.build.directory}/generated-sources
</defaultOutputDirectory>
<processors>
<processor>org.mapstruct.ap.MappingProcessor</processor>
</processors>
</configuration>
<executions>
<execution>
<id>process</id>
<phase>generate-sources</phase>
<goals>
<goal>process</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
test.xsd
<?xml version='1.0' encoding='UTF-8'?>
<?xml-stylesheet type="text/xsl" href="dico_v3.xsl"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning" elementFormDefault="qualified" attributeFormDefault="unqualified" vc:minVersion="1.1">
<xs:element name="test">
<xs:annotation>
<xs:documentation>Version V3 - 2021-04-23</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:all>
<xs:element name="string_element" type="xs:string">
<xs:annotation>
<xs:appinfo source="test/string_element"/>
<xs:documentation>simple string element</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="string_element_nillopt" type="xs:string" minOccurs="0" nillable="true">
<xs:annotation>
<xs:appinfo source="test/string_element_nillopt"/>
<xs:documentation>nillable AND optionnal string element </xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="obj_element">
<xs:complexType>
<xs:all>
<xs:element name="obj_element_string_element" type="xs:string">
<xs:annotation>
<xs:appinfo source="test/obj_element/obj_element_string_element"/>
<xs:documentation>simple string element INSIDE object element</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="obj_element_string_element_nillopt" type="xs:string" minOccurs="0" nillable="true">
<xs:annotation>
<xs:appinfo source="test/obj_element/obj_element_string_element_nillopt"/>
<xs:documentation>nillable AND optionnal string element INSIDE object element</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element name="obj_element_nillopt" minOccurs="0" nillable="true">
<xs:complexType>
<xs:all>
<xs:element name="obj_element_nillopt_string_element" type="xs:string">
<xs:annotation>
<xs:appinfo source="test/obj_element_nillopt/obj_element_nillopt_string_element"/>
<xs:documentation>simple string element INSIDE nillable AND optionnal object element</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="obj_element_nillopt_string_element_nillopt" type="xs:string" minOccurs="0" nillable="true">
<xs:annotation>
<xs:appinfo source="test/obj_element_nillopt/obj_element_nillopt_string_element_nillopt"/>
<xs:documentation>nillable AND optionnal string element INSIDE nillable AND optionnal object element</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
</xs:element>
</xs:schema>
Test.java(由 XSD 的 JAXB maven 插件生成)
@XmlAccessorType(XmlAccesstype.FIELD)
@XmlType(name = "",propOrder = {
})
@XmlRootElement(name = "test")
public class Test {
@XmlElement(name = "string_element",required = true)
protected String stringElement;
@XmlElementRef(name = "string_element_nillopt",type = JAXBElement.class,required = false)
protected JAXBElement<String> stringElementNillopt;
@XmlElement(name = "obj_element",required = true)
protected Test.ObjElement objElement;
@XmlElementRef(name = "obj_element_nillopt",required = false)
protected JAXBElement<Test.ObjElementNillopt> objElementNillopt;
@XmlAccessorType(XmlAccesstype.FIELD)
@XmlType(name = "",propOrder = {
})
public static class ObjElement {
@XmlElement(name = "obj_element_string_element",required = true)
protected String objElementStringElement;
@XmlElementRef(name = "obj_element_string_element_nillopt",required = false)
protected JAXBElement<String> objElementStringElementNillopt;
}
@XmlAccessorType(XmlAccesstype.FIELD)
@XmlType(name = "",propOrder = {
})
public static class ObjElementNillopt {
@XmlElement(name = "obj_element_nillopt_string_element",required = true)
protected String objElementNilloptStringElement;
@XmlElementRef(name = "obj_element_nillopt_string_element_nillopt",required = false)
protected JAXBElement<String> objElementNilloptStringElementNillopt;
}
}
TestMapper.java
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGnorE)
public interface TestMapper {
// Mapping fromXml
@Mapping(source = "stringElement",target = "stringAttribute1")
@Mapping(source = "stringElementNillopt",target = "stringAttribute2")
@Mapping(source = "objElement.objElementStringElement",target = "stringAttribute3")
@Mapping(source = "objElement.objElementStringElementNillopt",target = "stringAttribute4")
@Mapping(source = "objElementNillopt.objElementNilloptStringElement",target = "stringAttribute5")
@Mapping(source = "objElementNillopt.objElementNilloptStringElementNillopt",target = "stringAttribute6")
TestDTO fromXml(Test xmlInput);
}
测试DTO
public class TestDTO {
String stringAttribute1;
String stringAttribute2;
String stringAttribute3;
String stringAttribute4;
String stringAttribute5;
String stringAttribute6;
// ... getters & setters
}
第一次观察
我们可以看到,每次在 XSD 中同时使用 minOccurs="0"
和 nillable="true"
定义元素时,JAXB 插件会生成返回 JAXBElement<T>
而不是 T
的代码(在我们的示例 JAXBElement<String>
而不是 String
).
我希望 Mapstruct 能够透明地进行映射,正如它在文档 §5.1 中所说: https://mapstruct.org/documentation/stable/reference/html/#implicit-type-conversions
MapStruct 在很多情况下会自动处理类型转换。
...
目前自动应用以下转换:
...
在 JAXBElement 和 T 之间,List
确实有效,但仅当 JAXB 元素在映射源路径中为“终端”时...
TestMapper.java
使用这样的映射器会起作用(我删除了对 stringAttribute5
和 stringAttribute6
的 2 个桅杆映射):
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGnorE)
public interface TestMapper {
// Mapping fromXml
@Mapping(source = "stringElement",target = "stringAttribute4")
TestDTO fromXml(Test xmlInput);
}
TestMapperImpl.java
Mapstruct 生成一个 TestMapperImpl.java
,它通过引入 JAXBElement<T>
方法来处理目标 DTO 中源 T
到 jaxbElemTovalue
的转换:
@Generated(
value = "org.mapstruct.ap.MappingProcessor",date = "2021-05-10T18:34:10+0200",comments = "version: 1.4.2.Final,compiler: javac,environment: Java 11.0.2 (Oracle Corporation)"
)
public class TestMapperImpl implements TestMapper {
@Override
public TestDTO fromXml(Test xmlInput) {
if ( xmlInput== null ) {
return null;
}
TestDTO testDTO = new TestDTO();
testDTO.setStringAttribute1( xmlInput.getStringElement() );
testDTO.setStringAttribute2( jaxbElemTovalue( xmlInput.getStringElementNillopt() ) );
testDTO.setStringAttribute3( dpeObjElementObjElementStringElement( xmlInput) );
testDTO.setStringAttribute4( jaxbElemTovalue( dpeObjElementObjElementStringElementNillopt( xmlInput) ) );
return testDTO;
}
private <T> T jaxbElemTovalue( JAXBElement<T> element ) {
if ( element == null ) {
return null;
}
return element.isNil() ? null : element.getValue();
}
private String dpeObjElementObjElementStringElement(Test test) {
if ( test == null ) {
return null;
}
ObjElement objElement = test.getobjElement();
if ( objElement == null ) {
return null;
}
String objElementStringElement = objElement.getobjElementStringElement();
if ( objElementStringElement == null ) {
return null;
}
return objElementStringElement;
}
private JAXBElement<String> dpeObjElementObjElementStringElementNillopt(Test test) {
if ( test == null ) {
return null;
}
ObjElement objElement = test.getobjElement();
if ( objElement == null ) {
return null;
}
JAXBElement<String> objElementStringElementNillopt = objElement.getobjElementStringElementNillopt();
if ( objElementStringElementNillopt == null ) {
return null;
}
return objElementStringElementNillopt;
}
}
不错。
问题
TestMapper.java
现在我放回源映射路径中间带有 nillable/optionnal 元素的最后 2 个映射:
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGnorE)
public interface TestMapper {
// Mapping fromXml
@Mapping(source = "stringElement",target = "stringAttribute6")
TestDTO fromXml(Test xmlInput);
}
Mapstruct maven 目标会像这样失败:
No property named "objElementNillopt.objElementNilloptStringElement" exists in source parameter(s).
No property named "objElementNillopt.objElementNilloptStringElementNillopt" exists in source parameter(s).
完整的错误日志:
[INFO] --- maven-processor-plugin:3.3.1:process (process) @ DPE-API ---
[ERROR] diagnostic: C:\xxx\mapper\TestMapper.java:21: error: No property named "objElementNillopt.objElementNilloptStringElement" exists in source parameter(s). Did you mean "objElementNillopt.typeSubstituted"?
TestDTO fromXml(Test xmlInput);
^
[ERROR] diagnostic: C:\xxx\mapper\TestMapper.java:21: error: No property named "objElementNillopt.objElementNilloptStringElementNillopt" exists in source parameter(s). Did you mean "objElementNillopt.declaredType"?
TestDTO fromXml(Test xmlInput);
^
[ERROR] error on execute: use -X to have details
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
(解决方法?)解决方案
解决方案是明确告诉 Mapstruct 取中间 .value
JAXBElement 的 objElementNillopt
属性...
TestMapper.java
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGnorE)
public interface TestMapper {
// Mapping fromXml
@Mapping(source = "stringElement",target = "stringAttribute4")
@Mapping(source = "objElementNillopt.value.objElementNilloptStringElement",target = "stringAttribute5")
@Mapping(source = "objElementNillopt.value.objElementNilloptStringElementNillopt",target = "stringAttribute6")
TestDTO fromXml(Test xmlInput);
}
TestMapperImpl.java
@Generated(
value = "org.mapstruct.ap.MappingProcessor",date = "2021-05-10T18:50:43+0200",environment: Java 11.0.2 (Oracle Corporation)"
)
public class TestMapperImpl implements TestMapper {
@Override
public TestDTO fromXml(Test xmlInput) {
if ( xmlInput == null ) {
return null;
}
TestDTO testDTO = new TestDTO();
testDTO.setStringAttribute1( xmlInput.getStringElement() );
testDTO.setStringAttribute2( jaxbElemTovalue( xmlInput.getStringElementNillopt() ) );
testDTO.setStringAttribute3( xmlInputObjElementObjElementStringElement( xmlInput ) );
testDTO.setStringAttribute4( jaxbElemTovalue( xmlInputObjElementObjElementStringElementNillopt( xmlInput ) ) );
testDTO.setStringAttribute5( xmlInputObjElementNilloptValueObjElementNilloptStringElement( xmlInput ) );
testDTO.setStringAttribute6( jaxbElemTovalue( xmlInputObjElementNilloptValueObjElementNilloptStringElementNillopt( xmlInput ) ) );
return testDTO;
}
private <T> T jaxbElemTovalue( JAXBElement<T> element ) {
if ( element == null ) {
return null;
}
return element.isNil() ? null : element.getValue();
}
private String xmlInputObjElementObjElementStringElement(Test test) {
if ( test == null ) {
return null;
}
ObjElement objElement = test.getobjElement();
if ( objElement == null ) {
return null;
}
String objElementStringElement = objElement.getobjElementStringElement();
if ( objElementStringElement == null ) {
return null;
}
return objElementStringElement;
}
private JAXBElement<String> xmlInputObjElementObjElementStringElementNillopt(Test test) {
if ( test == null ) {
return null;
}
ObjElement objElement = test.getobjElement();
if ( objElement == null ) {
return null;
}
JAXBElement<String> objElementStringElementNillopt = objElement.getobjElementStringElementNillopt();
if ( objElementStringElementNillopt == null ) {
return null;
}
return objElementStringElementNillopt;
}
private String xmlInputObjElementNilloptValueObjElementNilloptStringElement(Test test) {
if ( test == null ) {
return null;
}
JAXBElement<ObjElementNillopt> objElementNillopt = test.getobjElementNillopt();
if ( objElementNillopt == null ) {
return null;
}
ObjElementNillopt value = objElementNillopt.getValue();
if ( value == null ) {
return null;
}
String objElementNilloptStringElement = value.getobjElementNilloptStringElement();
if ( objElementNilloptStringElement == null ) {
return null;
}
return objElementNilloptStringElement;
}
private JAXBElement<String> xmlInputObjElementNilloptValueObjElementNilloptStringElementNillopt(Test test) {
if ( test == null ) {
return null;
}
JAXBElement<ObjElementNillopt> objElementNillopt = test.getobjElementNillopt();
if ( objElementNillopt == null ) {
return null;
}
ObjElementNillopt value = objElementNillopt.getValue();
if ( value == null ) {
return null;
}
JAXBElement<String> objElementNilloptStringElementNillopt = value.getobjElementNilloptStringElementNillopt();
if ( objElementNilloptStringElementNillopt == null ) {
return null;
}
return objElementNilloptStringElementNillopt;
}
}
问题!
那么,如果我设法弄清楚了,为什么还要寻求帮助?
我的输入 XSD 很大,并且经常随着新元素的变化而变化,这些元素不时变为可空/可选项(或不可以)...
跟踪每个更改并找到在 Mapstruct 的 .value
源路径中添加 @Mapping
项的正确位置是很疯狂的。
我希望 Mapstruct 能够处理源路径中项目的 JAXBElements,无论它们是中间的还是终端的。
所以也许我在我的代码或 JAXB / Mapstruct Maven 插件配置中遗漏了一些东西?
如果您有任何可以提供帮助的反馈,我们将不胜感激!
在此先感谢您的帮助! :-)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。