Send a SOAP Object with Feign Client – 用伪客户端发送一个SOAP对象

最后修改: 2022年 4月 1日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Feign abstracts the HTTP calls and makes them declarative. By doing so, Feign hides the lower-level details like HTTP connection management,  hardcoded-URLs, and other boilerplate code. The significant advantage of using Feign clients is that HTTP calls are made easy and eliminate a lot of code. Typically, we use the Feign for REST APIs application/json media type. However, the Feign clients work well with other media types like text/xml, multipart requests, etc.,

Feign对HTTP调用进行了抽象,使其成为声明性的。通过这样做,Feign隐藏了低级别的细节,如HTTP连接管理、硬编码URL和其他模板代码。使用Feign客户端的显著优势是,HTTP调用变得简单,并消除了大量的代码。通常情况下,我们使用Feign用于REST APIs application/json 媒体类型。然而,Feign客户端也能很好地处理其他媒体类型,如text/xml、多部分请求等。

In this tutorial, let’s learn how to invoke a SOAP-based web service (text/xml) using Feign.

在本教程中,让我们学习如何使用Feign调用一个基于SOAP的网络服务(text/xml)。

2. SOAP Web Service

2.SOAP网络服务

Let’s assume that there is a SOAP web service with two operations – getUser and createUser.

我们假设有一个SOAP Web服务,有两个操作–getUsercreateUser

Let’s use cURL to invoke the operation createUser:

让我们使用cURL来调用操作createUser

curl -d @request.xml -i -o -X POST --header 'Content-Type: text/xml'
  http://localhost:18080/ws/users

Here, the request.xml contains the SOAP payload:

这里,request.xml包含SOAP有效载荷。

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:feig="http://www.baeldung.com/springbootsoap/feignclient">
    <soapenv:Header/>
    <soapenv:Body>
         <feig:createUserRequest>
             <feig:user>
                 <feig:id>1</feig:id>
                 <feig:name>john doe</feig:name>
                 <feig:email>john.doe@gmail.com</feig:email>
            </feig:user>
         </feig:createUserRequest>
    </soapenv:Body>
</soapenv:Envelope>

If all the configurations are correct, we get a successful response:

如果所有的配置都正确,我们就会得到一个成功的响应。

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Header/>
    <SOAP-ENV:Body>
        <ns2:createUserResponse xmlns:ns2="http://www.baeldung.com/springbootsoap/feignclient">
            <ns2:message>Success! Created the user with id - 1</ns2:message>
        </ns2:createUserResponse>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Similarly, the other operation, getUser can also be invoked using cURL.

同样,另一个操作,getUser也可以用cURL来调用。

3. Dependencies

3.依赖性

Next, let’s see how to use Feign to invoke this SOAP web service. Let’s develop two different clients to invoke a SOAP service. Feign supports multiple existing HTTP Clients like Apache HttpComponents, OkHttp, java.net.URL, etc. Let’s use Apache HttpComponents as our underlying HTTP client. First, let’s add dependencies for OpenFeign Apache HttpComponents:

接下来,让我们看看如何使用Feign来调用这个SOAP网络服务。让我们开发两个不同的客户端来调用一个SOAP服务。Feign支持多种现有的HTTP客户端,如Apache HttpComponentsOkHttpjava.net.URL等。让我们使用Apache HttpComponents 作为我们的底层HTTP客户端。首先,让我们为OpenFeign Apache HttpComponents添加依赖项。

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-hc5</artifactId>
    <version>11.8</version>
</dependency>

In the next sections, let’s learn a couple of ways to invoke the SOAP web services using Feign.

在接下来的章节中,让我们学习几种使用Feign调用SOAP网络服务的方法。

4. SOAP Object as Plain-Text

4.作为纯文本的SOAP对象

We can send the SOAP request as plain text with content-type and accept headers set to text/xml. Let’s now develop a client that demonstrates this approach:

我们可以将SOAP请求作为纯文本发送,并将content-typeaccept头文件设置为text/xml。现在让我们开发一个客户端,演示一下这种方法。

public interface SoapClient {
    @RequestLine("POST")
    @Headers({"SOAPAction: createUser", "Content-Type: text/xml;charset=UTF-8",
      "Accept: text/xml"})
    String createUserWithPlainText(String soapBody);
}

Here, createUserWithPlainText takes a String SOAP payload. Note that we defined the accept and content-type headers explicitly. This is because when sending a SOAP body as text, it is mandatory to mention the Content-Type and Accept headers as text/xml.

这里,createUserWithPlainText接收了一个StringSOAP有效载荷。请注意,我们明确定义了 accept content-type 头信息。这是因为当以文本形式发送SOAP主体时,必须提到Content-TypeAccept头信息为text/xml。文本/xml

One downside of this approach is we should know the SOAP payload beforehand. Fortunately, if the WSDL is available, the payload can be generated using open-source tools like SoapUI. Once the payload is ready, let’s invoke the SOAP web service using Feign:

这种方法的一个缺点是我们应该事先知道SOAP的有效载荷。幸运的是,如果WSDL是可用的,那么可以使用开源工具生成有效载荷,例如SoapUI。一旦有效载荷准备好了,我们就用Feign来调用SOAP网络服务。

@Test
void givenSOAPPayload_whenRequest_thenReturnSOAPResponse() throws Exception {
    String successMessage="Success! Created the user with id";
    SoapClient client = Feign.builder()
       .client(new ApacheHttp5Client())
       .target(SoapClient.class, "http://localhost:18080/ws/users/");
    
    assertDoesNotThrow(() -> client.createUserWithPlainText(soapPayload()));
    
    String soapResponse= client.createUserWithPlainText(soapPayload());
 
    assertNotNull(soapResponse);
    assertTrue(soapResponse.contains(successMessage));
}

Feign supports logging of the SOAP messages and other HTTP-related information. This information is critical for debugging. So let’s enable the Feign logging. The logging of these messages requires an additional feign-slf4j dependency:

Feign支持记录SOAP消息和其他HTTP相关信息。这些信息对于调试是非常关键的。所以让我们启用Feign的日志记录。这些信息的记录需要一个额外的feign-slf4j依赖。

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-slf4j</artifactId>
    <version>11.8</version>
</dependency>

Let’s enhance our test case to include the logging information:

让我们加强我们的测试案例,以包括日志信息。

SoapClient client = Feign.builder()
  .client(new ApacheHttp5Client())
  .logger(new Slf4jLogger(SoapClient.class))
  .logLevel(Logger.Level.FULL)
  .target(SoapClient.class, "http://localhost:18080/ws/users/");

Now, when we run the test, we have the logs similar to:

现在,当我们运行测试时,我们有类似的日志。

18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> "SOAPAction: createUser[\r][\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> "<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:feig="http://www.baeldung.com/springbootsoap/feignclient">[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <soapenv:Header/>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <soapenv:Body>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:createUserRequest>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:user>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:id>1</feig:id>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:name>john doe</feig:name>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:email>john.doe@gmail.com</feig:email>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " </feig:user>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " </feig:createUserRequest>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " </soapenv:Body>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> "</soapenv:Envelope>"
18:01:58.300 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 << "<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><ns2:createUserResponse xmlns:ns2="http://www.baeldung.com/springbootsoap/feignclient"><ns2:message>Success! Created the user with id - 1</ns2:message></ns2:createUserResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>"

5. Feign SOAP Codec

5 佯装SOAP编解码器

A cleaner and better approach to invoke a SOAP Webservice is using Feign’s SOAP Codec. The codec helps marshal the SOAP messages (Java to SOAP)/unmarshalling (SOAP to Java). However, the codec requires an additional feign-soap dependency. Therefore, let’s declare this dependency:

使用Feign的SOAP编解码器来调用SOAP Webservice是一种更简洁、更好的方法。该编解码器有助于整理 SOAP 消息(Java 至 SOAP)/解除整理(SOAP 至 Java)。然而,该编解码器需要一个额外的feign-soap依赖项。因此,让我们声明这个依赖关系。

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-soap</artifactId>
    <version>11.8</version>
</dependency>

Feign SOAP codec encodes and decodes the SOAP objects using JAXB and SoapMessage and the JAXBContextFactory provides the required marshalers and unmarshalers.

Feign SOAP编解码器使用JAXBSoapMessage对SOAP对象进行编码和解码,JAXBContextFactory 提供了所需的marshalers和unmarshalers。

Next, based on the XSD that we created, let’s generate the domain classes. JAXB requires these domain classes to marshal and unmarshal the SOAP messages. First, let’s add a plugin to our pom.xml:

接下来,根据我们创建的XSD,我们来生成域类。JAXB需要这些域类来调集和解除对SOAP消息的调集。首先,让我们在pom.xml中添加一个插件。

<plugin>
    <groupId>org.jvnet.jaxb2.maven2</groupId>
    <artifactId>maven-jaxb2-plugin</artifactId>
    <version>0.14.0</version>
    <executions>
        <execution>
            <id>feign-soap-stub-generation</id>
            <phase>compile</phase>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <schemaDirectory>target/generated-sources/jaxb</schemaDirectory>
                <schemaIncludes>
                    <include>*.xsd</include>
                </schemaIncludes>
                <generatePackage>com.baeldung.feign.soap.client</generatePackage>
                <generateDirectory>target/generated-sources/jaxb</generateDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>

Here, we configured the plugin to run during the compile phase. Now, let’s generate the stubs:

在这里,我们将该插件配置为在编译阶段运行。现在,让我们来生成存根。

mvn clean compile

After a successful build, the target folder contains the sources:

构建成功后,target文件夹包含源代码。

Folder Structure

Next, let’s use these stubs and Feign to invoke the SOAP web service. But, first, let’s add a new method to our SoapClient:

接下来,让我们使用这些存根和Feign来调用SOAP网络服务。但是,首先,让我们给我们的SoapClient添加一个新方法。

@RequestLine("POST")
@Headers({"Content-Type: text/xml;charset=UTF-8"})
CreateUserResponse createUserWithSoap(CreateUserRequest soapBody);

Next, let’s test the SOAP web service:

接下来,让我们测试一下SOAP网络服务。

@Test
void whenSoapRequest_thenReturnSoapResponse() {
    JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
      .withMarshallerJAXBEncoding("UTF-8").build();
    SoapClient client = Feign.builder()
      .encoder(new SOAPEncoder(jaxbFactory))
      .decoder(new SOAPDecoder(jaxbFactory))
      .target(SoapClient.class, "http://localhost:18080/ws/users/");
    CreateUserRequest request = new CreateUserRequest();
    User user = new User();
    user.setId("1");
    user.setName("John Doe");
    user.setEmail("john.doe@gmail");
    request.setUser(user);
    CreateUserResponse response = client.createUserWithSoap(request);

    assertNotNull(response);
    assertNotNull(response.getMessage());
    assertTrue(response.getMessage().contains("Success")); 
}

Let’s enhance our test case to log the HTTP and SOAP messages:

让我们加强我们的测试案例来记录HTTP和SOAP消息。

SoapClient client = Feign.builder()
  .encoder(new SOAPEncoder(jaxbFactory))
  .errorDecoder(new SOAPErrorDecoder())
  .logger(new Slf4jLogger())
  .logLevel(Logger.Level.FULL)
  .decoder(new SOAPDecoder(jaxbFactory))
  .target(SoapClient.class, "http://localhost:18080/ws/users/");

This code generates similar logs that we saw earlier.

这段代码生成了我们之前看到的类似日志。

Finally, let’s handle SOAP Faults. Feign provides a SOAPErrorDecoder that returns a SOAP Fault as SOAPFaultException. So, let’s set this SOAPErrorDecoder  as a Feign error decoder and handle the SOAP Faults:

最后,让我们来处理SOAP Faults。Feign提供了一个SOAPErrorDecoder,它将SOAP Fault作为SOAPFaultException返回。所以,让我们把这个SOAPErrorDecoder设为Feign错误解码器,并处理SOAP Faults。

SoapClient client = Feign.builder()
  .encoder(new SOAPEncoder(jaxbFactory))
  .errorDecoder(new SOAPErrorDecoder())  
  .decoder(new SOAPDecoder(jaxbFactory))
  .target(SoapClient.class, "http://localhost:18080/ws/users/");
try {
    client.createUserWithSoap(request);
} catch (SOAPFaultException soapFaultException) {
    assertNotNull(soapFaultException.getMessage());
    assertTrue(soapFaultException.getMessage().contains("This is a reserved user id"));
}

Here, if the SOAP web service throws a SOAP Fault, it will be handled by the SOAPFaultException.

在这里,如果SOAP网络服务抛出一个SOAP故障,它将由SOAPFaultException来处理。

6. Conclusion

6.结论

In this article, we learned to invoke a SOAP web service using Feign. Feign is a declarative HTTP client that makes it easy to invoke SOAP/REST web services. The advantage of using Feign is it reduces the lines of code. Lesser lines of code lead to lesser bugs and lesser unit tests.

在这篇文章中,我们学习了如何使用Feign来调用SOAP网络服务。Feign是一个声明性的HTTP客户端,它使调用SOAP/REST网络服务变得容易。使用Feign的好处是可以减少代码行数。更少的代码行会导致更少的错误和更少的单元测试。

As always, the complete source code is available over on GitHub.

一如既往,完整的源代码可在GitHub上获得