Spring boot way to marshall/demarshall XML

烂漫一生 提交于 2019-12-24 00:49:44

问题


What is the "Boot-iful" way to auto marshall/demarshall XML?

My Rest controller:

@RestController
@Slf4j
public class ProvisioningController {

    @RequestMapping(value = "/provisioning", method = RequestMethod.POST,
                    consumes = MediaType.APPLICATION_XML)
    public void handleRequest(@RequestBody ProvisioningRequest request) {
        log.info("Rx request: {}", request);
    }
}

The ProvisioningRequest class is generated by Jaxb from a XSD.

Posting a sample request to the rest endpoint gives the following error:

java.lang.IllegalStateException: Failed to resolve argument 0 of type 'jaxb.generated.ProvisioningRequest' on public void rest.controller.ProvisioningController.handleRequest(jaxb.generated.ProvisioningRequest)

I do have 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml' in classpath.

Configuring a @Bean Jackson2ObjectMapperBuilder doesn't seem to help either.

Am I missing something?

=== Full stack trace:

 java.lang.IllegalStateException: Failed to resolve argument 0 of type 'jaxb.generated.ProvisioningRequest' on public void rest.controller.ProvisioningController.handleRequest(jaxb.generated.ProvisioningRequest) throws java.io.IOException
    at org.springframework.web.reactive.result.method.InvocableHandlerMethod.getArgumentError(InvocableHandlerMethod.java:198) ~[spring-webflux-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
    at org.springframework.web.reactive.result.method.InvocableHandlerMethod.resolveArg(InvocableHandlerMethod.java:193) ~[spring-webflux-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
    at org.springframework.web.reactive.result.method.InvocableHandlerMethod.lambda$null$1(InvocableHandlerMethod.java:149) ~[spring-webflux-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
    at java.util.Optional.orElseGet(Optional.java:267) ~[na:1.8.0_73]
    at org.springframework.web.reactive.result.method.InvocableHandlerMethod.lambda$resolveArguments$2(InvocableHandlerMethod.java:147) ~[spring-webflux-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[na:1.8.0_73]
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) ~[na:1.8.0_73]
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) ~[na:1.8.0_73]
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_73]
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) ~[na:1.8.0_73]
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_73]
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) ~[na:1.8.0_73]
    at org.springframework.web.reactive.result.method.InvocableHandlerMethod.resolveArguments(InvocableHandlerMethod.java:153) ~[spring-webflux-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
    at org.springframework.web.reactive.result.method.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:117) ~[spring-webflux-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
    at org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter.lambda$handle$3(RequestMappingHandlerAdapter.java:312) ~[spring-webflux-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
    at reactor.core.publisher.MonoThenMap$MonoThenApplyMain.onNext(MonoThenMap.java:100) [reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:928) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoThenIgnore$MonoThenIgnoreMain.drain(MonoThenIgnore.java:145) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoThenIgnore.subscribe(MonoThenIgnore.java:54) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoThenMap.subscribe(MonoThenMap.java:57) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoThenMap$MonoThenApplyMain.onNext(MonoThenMap.java:130) [reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:68) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:78) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:263) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:743) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:119) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:928) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoThenIgnore$MonoThenAcceptSubscriber.onNext(MonoThenIgnore.java:261) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:1657) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoThenIgnore$MonoThenAcceptSubscriber.onSubscribe(MonoThenIgnore.java:250) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:59) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoThenIgnore$MonoThenIgnoreMain.drain(MonoThenIgnore.java:151) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoThenIgnore.subscribe(MonoThenIgnore.java:54) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:60) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:402) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:203) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:97) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:57) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.FluxConcatMap.subscribe(FluxConcatMap.java:126) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoNext.subscribe(MonoNext.java:41) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoOtherwiseIfEmpty.subscribe(MonoOtherwiseIfEmpty.java:44) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoThenMap.subscribe(MonoThenMap.java:57) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoThenMap.subscribe(MonoThenMap.java:57) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoOtherwise.subscribe(MonoOtherwise.java:44) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoOtherwise.subscribe(MonoOtherwise.java:44) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoOtherwise.subscribe(MonoOtherwise.java:44) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoThenIgnore$MonoThenIgnoreMain.drain(MonoThenIgnore.java:169) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at reactor.core.publisher.MonoThenIgnore.subscribe(MonoThenIgnore.java:54) ~[reactor-core-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at org.springframework.http.server.reactive.ServletHttpHandlerAdapter.service(ServletHttpHandlerAdapter.java:106) ~[spring-web-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:798) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1434) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) ~[na:1.8.0_73]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) ~[na:1.8.0_73]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
    at java.lang.Thread.run(Thread.java:745) ~[na:1.8.0_73]
Caused by: java.lang.UnsupportedOperationException: null
    at org.springframework.core.codec.AbstractDecoder.decodeToMono(AbstractDecoder.java:64) ~[spring-core-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
    at org.springframework.http.codec.DecoderHttpMessageReader.readMono(DecoderHttpMessageReader.java:84) ~[spring-web-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
    at org.springframework.web.reactive.result.method.annotation.AbstractMessageReaderArgumentResolver.readBody(AbstractMessageReaderArgumentResolver.java:160) ~[spring-webflux-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
    at org.springframework.web.reactive.result.method.annotation.RequestBodyArgumentResolver.resolveArgument(RequestBodyArgumentResolver.java:78) ~[spring-webflux-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
    at org.springframework.web.reactive.result.method.InvocableHandlerMethod.resolveArg(InvocableHandlerMethod.java:184) ~[spring-webflux-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
    ... 66 common frames omitted

回答1:


In WebFlux Jaxb2XmlDecoder did not implement decodeToMono, but it is now fixed via SPR-16759. So just upgrading to Spring Framework 5.0.6+ / Spring Boot 2.0.2+ should avoid the reported exception.




回答2:


To do proper serialization/deserialization you can set custom Encoder/Decoder. Please see examples below.

A short theory:

  • Default XML JAXB decoder has issue with deserializing single request - see Spring Reactive xml payload exception java.lang.IllegalStateException: Failed to resolve argument 0 of type 'reactor.core.publisher.Mono'
  • You can override one of the following:
    • Encoder or Decoder. You have no access to Http message, however you can reuse it outside http streams
    • HttpMessageReader or HttpMessageWriter. The same with about, however touched with http protocol (https and web sockets inclusive of course)

To use custom (and only custom) deserializers, please do the following overrides:

On Kotlin:

@Bean
open fun webFluxConfigurer(myReader: MyReader,                               
                           myWriter: MyWriter): WebFluxConfigurer {
    return object : WebFluxConfigurer {
        override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
            // disable default codecs, because of problematic XML serialization in Jaxb2XmlDecoder
            configurer.registerDefaults(false)

            configurer.customCodecs().decoder(myReader)

            configurer.customCodecs().encoder(myWriter)
        }
    }

}

Or on Java:

@Bean
WebFluxConfigurer webFluxConfigurer(MyReader myReader, MyWriter myWriter){ 
    return new WebFluxConfigurer() {
        @Override
        public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
            // disable default codecs, because of problematic XML serialization in Jaxb2XmlDecoder
            configurer.registerDefaults(false);

            configurer.customCodecs().decoder(myReader);

            configurer.customCodecs().encoder(myWriter);
        }
    };

}


来源:https://stackoverflow.com/questions/42496821/spring-boot-way-to-marshall-demarshall-xml

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!