如何使用JAXB和Spring-Boot将XML转换为字符串?

人气:65 发布:2023-01-03 标签: spring jaxb spring-boot java-11

问题描述

当我在包含pom.xml文件的文件夹上运行mvn spring-boot:run时,应用程序启动并正确地将POJO序列化为XML,但是当我转到目标文件夹并使用JAR文件中的java -jar启动它时,我得到了javax.xml.bind.JAXBException: Implementation of JAXB-API has not been found on module path or classpath.java.lang.ClassNotFoundException: com.sun.xml.internal.bind.v2.ContextFactory引起。

在我的maven中,我有以下JAXB依赖项:

<!-- JAXB API -->
<dependency>
    <groupId>jakarta.xml.bind</groupId>
    <artifactId>jakarta.xml.bind-api</artifactId>
</dependency>

<!-- JAXB Runtime -->
<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-impl</artifactId>
    <version>2.3.5</version>
    <scope>runtime</scope>
</dependency>

以下是将POJO序列化为XML的代码:

private static final Pattern REMOVE_HEADER = Pattern.compile("\<\?xml(.+?)\?\>");

public static <T> String toXML(final T data) {
    try {
        final var jaxbMarshaller = JAXBContext.newInstance(data.getClass()).createMarshaller();
        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        final var sw = new StringWriter();
        jaxbMarshaller.marshal(data, sw);
        return XMLUtils.REMOVE_HEADER.matcher(sw.toString()).replaceAll("").strip();
    } catch (final JAXBException e) {
        XMLUtils.LOGGER.error("Error while converting POJO to XML. ERROR: {}.", e.getMessage(), e);
    }

    return "";
}

以下是我使用Java-JAR启动应用程序时的日志:

2021-12-26 21:19:14,526 [ForkJoinPool.commonPool-worker-11] ERROR com.enterprise.system.shared.util.XMLUtils - Error while converting POJO to XML. ERROR: Implementation of JAXB-API has not been found on module path or classpath..
javax.xml.bind.JAXBException: Implementation of JAXB-API has not been found on module path or classpath.
 - with linked exception:
[java.lang.ClassNotFoundException: com.sun.xml.bind.v2.ContextFactory]
        at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:232)
        at javax.xml.bind.ContextFinder.find(ContextFinder.java:375)
        at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:691)
        at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:632)
        at com.enterprise.system.shared.util.XMLUtils.toXML(XMLUtils.java:26)
        at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
        at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
        at java.base/java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:952)
        at java.base/java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:926)
        at java.base/java.util.stream.AbstractTask.compute(AbstractTask.java:327)
        at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:746)
        at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
        at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
        at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
        at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
        at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)
Caused by: java.lang.ClassNotFoundException: com.sun.xml.bind.v2.ContextFactory
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        at javax.xml.bind.ServiceLoaderUtil.nullSafeLoadClass(ServiceLoaderUtil.java:92)
        at javax.xml.bind.ServiceLoaderUtil.safeLoadClass(ServiceLoaderUtil.java:125)
        at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:230)
        ... 17 more
以下是生成的带有JAXB依赖项的Spring-Boot FAT JAR的图像:

这是我的依赖关系树:

--- maven-dependency-plugin:3.2.0:tree (default-cli) @ java-eleven-jaxb-hell ---
com.enterprise.system:java-eleven-jaxb-hell:jar:0.0.1-SNAPSHOT
+- org.slf4j:slf4j-log4j12:jar:1.7.32:compile (optional)
|  +- org.slf4j:slf4j-api:jar:1.7.32:compile (optional)
|  - log4j:log4j:jar:1.2.17:compile (optional)
+- org.springframework.boot:spring-boot-autoconfigure:jar:2.6.2:compile
|  - org.springframework.boot:spring-boot:jar:2.6.2:compile
|     +- org.springframework:spring-core:jar:5.3.14:compile
|     |  - org.springframework:spring-jcl:jar:5.3.14:compile
|     - org.springframework:spring-context:jar:5.3.14:compile
|        +- org.springframework:spring-aop:jar:5.3.14:compile
|        +- org.springframework:spring-beans:jar:5.3.14:compile
|        - org.springframework:spring-expression:jar:5.3.14:compile
+- org.projectlombok:lombok:jar:1.18.22:provided
+- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:compile
|  - jakarta.activation:jakarta.activation-api:jar:1.2.2:compile
- com.sun.xml.bind:jaxb-impl:jar:2.3.5:runtime
   - com.sun.activation:jakarta.activation:jar:1.2.2:runtime

最后,以下是我的模型类:

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@XmlRootElement(name = "finans")
@XmlAccessorType(XmlAccessType.FIELD)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FinanTokenDTO {

    @XmlAttribute
    private String pln;

    @XmlAttribute
    private String ope;

    @XmlAttribute
    private String mod;

    @XmlAttribute
    private String mis;

    @XmlAttribute
    private String val;

    @XmlAttribute
    private String car;

    @XmlAttribute
    private String dti;

    @XmlAttribute
    private String dtf;

    @XmlAttribute
    private String ota;

    @XmlElement
    private FinanDTO finan;

}

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@XmlRootElement(name = "finan")
@XmlAccessorType(XmlAccessType.FIELD)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FinanDTO {

    @XmlAttribute
    private String prd;

    @XmlAttribute
    private String pkg;

    @XmlAttribute
    private String val;

    @XmlAttribute
    private String fty;

}

我使用的是Java 11,我知道在Java 11中,JAXB已从SE JDK中删除,因为它被视为EE功能。

我无法在生产环境中使用mvn spring-boot:run执行它,因为停靠镜像的大小以及使用停靠容器的安全相关问题。

既然Spring Boot生成FAT JAR,那么应用程序运行时应用于Spring Boot FAT JAR生成的文件的方式不应该与它在包含pom.xml的文件夹中应用mvn spring-boot:run的方式相同吗?

编辑:

经过大量挖掘和测试后,我发现当我们尝试使用POJO列表上的parallel stream而不是stream来封送几个有效的POJO时,就会出现这个问题。为了更好地理解,我上传了java-eleven-jaxb-hell中的代码,不幸的是,由于性能问题,我无法将parallelStream更改为stream。请记住,对于发生的问题,您必须对目标文件夹中的Spring-Boot生成的FAT JAR运行java -jar

推荐答案

我建议您将SUN的JAXB-Impl替换为GlassFish的JAXB-Impl:

<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>2.3.5</version>
</dependency>
一般说来,Sun已经不复存在了,这个包可能只是为了兼容。目前推荐的JAXB实现来自GlassFish。

19