微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用带有 JAXB 生成类的 Mapstruct 没有隐式 JAXBElement 转换对于可空和可选的中间元素 pom.xmltest.xsdTest.java由 XSD 的 JAXB maven 插件生成TestMapper.java测试DTOTestMapper.javaTestMapperImpl.javaTestMapper.javaTestMapper.javaTestMapperImpl.java

如何解决使用带有 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 和 List

确实有效,但仅当 JAXB 元素在映射源路径中为“终端”时...

TestMapper.java

使用这样的映射器会起作用(我删除了对 stringAttribute5stringAttribute6 的 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 中源 TjaxbElemTovalue 的转换:

@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 举报,一经查实,本站将立刻删除。