问题
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