Reactive Spring WebClient - 进行 SOAP 调用
Posted
技术标签:
【中文标题】Reactive Spring WebClient - 进行 SOAP 调用【英文标题】:Reactive Spring WebClient - Making a SOAP call 【发布时间】:2018-09-16 00:36:33 【问题描述】:我希望从 spring 响应式 webclient 进行 SOAP 调用。 我找不到任何文档。想知道有什么办法。我现在在想
-
在单独的线程池上使用 JAXB 构造 SOAP 消息
通过 webclient 将其转换为字符串来进行调用
在返回单独 tp 的途中使用 jaxb 转换回 java。
有什么缺点和其他方法?
【问题讨论】:
【参考方案1】:您需要生成 SOAP 客户端作为带有异步方法的存根类。 JAX-WS API 支持异步调用。 使用 wsiimport 和 enableAsyncMapping 生成方法 operationAsync(Input request, AsyncHandler asyncHandler);
AsyncHandler 使用 Mono.create() 创建
Service service = new Service();
ServicePortType portType = service.getPortType();
public Mono<Output> operation(Input input)
return Mono.create(sink ->
portType.operation(input, outputFuture ->
try
sink.success(outputFuture.get());
catch (Exception e)
sink.error(e);
)
);
然后你会反应性地得到 Mono
我在帖子https://blog.godatadriven.com/jaxws-reactive-client找到了建议
【讨论】:
欢迎提供解决方案链接,但请确保您的答案在没有它的情况下有用:add context around the link 这样您的其他用户就会知道它是什么以及为什么会出现,然后引用最相关的您链接到的页面的一部分,以防目标页面不可用。 Answers that are little more than a link may be deleted. 此解决方案不使用 Spring Reactor 的 WebClient。 外部链接保存在web.archive.org/web/20200303110721/https://…【参考方案2】:这是一个使用 Spring Reactor 的工作示例:https://github.com/gungor/spring-webclient-soap
您需要将生成的 JAXB 类包含在带有自定义编码器的肥皂信封中,如下所示,然后将其添加到 WebClient 的交换策略中。
package webclient.soap.encoding;
import org.reactivestreams.Publisher;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.CodecException;
import org.springframework.core.codec.Encoder;
import org.springframework.core.codec.EncodingException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.PooledDataBuffer;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.support.DefaultStrategiesHelper;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.MarshalException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class Jaxb2SoapEncoder implements Encoder<Object>
private final JaxbContextContainer jaxbContexts = new JaxbContextContainer();
@Override
public boolean canEncode(ResolvableType elementType, MimeType mimeType)
Class<?> outputClass = elementType.toClass();
return (outputClass.isAnnotationPresent(XmlRootElement.class) ||
outputClass.isAnnotationPresent(XmlType.class));
@Override
public Flux<DataBuffer> encode(Publisher<?> inputStream, DataBufferFactory bufferFactory, ResolvableType elementType, MimeType mimeType, Map<String, Object> hints)
return Flux.from(inputStream)
.take(1)
.concatMap(value -> encode(value, bufferFactory, elementType, mimeType, hints))
.doOnDiscard(PooledDataBuffer.class, PooledDataBuffer::release);
@Override
public List<MimeType> getEncodableMimeTypes()
return Arrays.asList( MimeTypeUtils.TEXT_XML );
private Flux<DataBuffer> encode(Object value ,
DataBufferFactory bufferFactory,
ResolvableType type,
MimeType mimeType,
Map<String, Object> hints)
return Mono.fromCallable(() ->
boolean release = true;
DataBuffer buffer = bufferFactory.allocateBuffer(1024);
try
OutputStream outputStream = buffer.asOutputStream();
Class<?> clazz = ClassUtils.getUserClass(value);
Marshaller marshaller = initMarshaller(clazz);
// here should be optimized
DefaultStrategiesHelper helper = new DefaultStrategiesHelper(WebServiceTemplate.class);
WebServiceMessageFactory messageFactory = helper.getDefaultStrategy(WebServiceMessageFactory.class);
WebServiceMessage message = messageFactory.createWebServiceMessage();
marshaller.marshal(value, message.getPayloadResult());
message.writeTo(outputStream);
release = false;
return buffer;
catch (MarshalException ex)
throw new EncodingException(
"Could not marshal " + value.getClass() + " to XML", ex);
catch (JAXBException ex)
throw new CodecException("Invalid JAXB configuration", ex);
finally
if (release)
DataBufferUtils.release(buffer);
).flux();
private Marshaller initMarshaller(Class<?> clazz) throws JAXBException
Marshaller marshaller = this.jaxbContexts.createMarshaller(clazz);
marshaller.setProperty(Marshaller.JAXB_ENCODING, StandardCharsets.UTF_8.name());
return marshaller;
WebClient 配置
@Bean
public WebClient webClient()
TcpClient tcpClient = TcpClient.create();
tcpClient
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.doOnConnected(connection ->
connection.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS));
connection.addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS));
);
ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder().codecs( clientCodecConfigurer ->
clientCodecConfigurer.customCodecs().encoder(new Jaxb2SoapEncoder());
).build();
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient).wiretap(true)))
.exchangeStrategies( exchangeStrategies )
.build();
return webClient;
网络客户端
public void call(GetCountryRequest getCountryRequest) throws SOAPException, ParserConfigurationException, IOException
webClient.post()
.uri( soapServiceUrl )
.contentType(MediaType.TEXT_XML)
.body( Mono.just(getCountryRequest) , GetCountryRequest.class )
.retrieve()
.onStatus(
HttpStatus::isError,
clientResponse ->
clientResponse
.bodyToMono(String.class)
.flatMap(
errorResponseBody ->
Mono.error(
new ResponseStatusException(
clientResponse.statusCode(),
errorResponseBody))))
.bodyToMono(GetCountryResponse.class)
.doOnSuccess( (GetCountryResponse response) ->
//handle success
)
.doOnError(ResponseStatusException.class, error ->
//handle error
)
.subscribe();
【讨论】:
编码器是个很酷的东西,谢谢你的好主意。但我相信你也应该配置解码器,因为答案也是SOAP
消息,而webClient
不知道如何将它映射到你的对象中。s
其实 org.springframework.http.codec.xml.Jaxb2XmlDecoder 处理 Jaxb 解码。它存在于 WebClient 的默认交换策略中。 Jaxb2XmlDecoder 使用 com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl 将 SOAP xml 解码为 Jaxb 对象。
我试过了,但收到一个错误,提示出现意外元素Envelope
你看过例子了吗:github.com/gungor/spring-webclient-soap ?
现在添加了 Jaxb2SoapDecoder,Jaxb2XmlDecoder 似乎不适用于不同版本的 jaxb-runtime以上是关于Reactive Spring WebClient - 进行 SOAP 调用的主要内容,如果未能解决你的问题,请参考以下文章
使用 Spring Security OAuth 时 Spring Boot Reactive WebClient “serverWebExchange must be null”
Spring reactive WebClient GET json response with Content-Type "text/plain;charset=UTF-8"
Spring Webflux Webclient |内容类型标题设置问题