概述
Feign 抽象了HTTP调用,并使其声明式。通过这种方式,Feign隐藏了底层细节,如HTTP连接管理、硬编码URL和其他样板代码。使用Feign客户端的主要优势是简化了HTTP调用并减少了大量代码。通常,我们使用Feign来调用REST API,以application/json
媒体类型。然而,Feign客户端也适用于其他媒体类型,如text/xml
、多部分请求等。
在这个教程中,我们将学习如何使用Feign调用基于SOAP的Web服务(text/xml
)。
2. SOAP Web服务
假设有一个带有两个操作(getUser
和createUser
)的SOAP Web服务。
让我们使用cURL来调用createUser
操作:
curl -d @request.xml -i -o -X POST --header 'Content-Type: text/xml'
http://localhost:18080/ws/users
这里的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>[email protected]</feig:email>
</feig:user>
</feig:createUserRequest>
</soapenv:Body>
</soapenv:Envelope>
如果配置正确,我们将收到成功响应:
<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>
同样,也可以使用cURL调用另一个操作getUser
。
3. 依赖项
接下来,我们将了解如何使用Feign调用这个SOAP Web服务。我们将开发两个不同的客户端来调用SOAP服务。Feign支持多种现有的HTTP客户端,如Apache HttpComponents、OkHttp、java.net.URL
等。我们将使用Apache HttpComponents作为底层HTTP客户端。首先,我们需要添加对OpenFeign Apache HttpComponents的依赖:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hc5</artifactId>
<version>11.8</version>
</dependency>
在接下来的章节中,我们将学习几种使用Feign调用SOAP Web服务的方法。
4. 将SOAP对象作为纯文本
我们可以将SOAP请求作为纯文本发送,设置content-type
和accept
头为text/xml
。现在,让我们开发一个演示这种方法的客户端:
public interface SoapClient {
@RequestLine("POST")
@Headers({"SOAPAction: createUser", "Content-Type: text/xml;charset=UTF-8",
"Accept: text/xml"})
String createUserWithPlainText(String soapBody);
}
这里,createUserWithPlainText
接受一个String
类型的SOAP负载。请注意,我们明确指定了accept
和content-type
头。这是因为当我们发送SOAP主体作为文本时,必须指定Content-Type
和Accept
头为text/xml
。
这种方法的一个缺点是我们必须事先知道SOAP负载。幸运的是,如果WSDL可用,可以使用开源工具如SoapUI生成负载。准备好负载后,我们可以使用Feign调用SOAP Web服务:
@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支持SOAP消息和其他HTTP相关信息的记录。这些信息对于调试至关重要。因此,让我们启用Feign日志。记录这些信息需要额外的[feign-slf4j](https://mvnrepository.com/artifact/io.github.openfeign/feign-slf4j)
依赖:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<version>11.8</version>
</dependency>
让我们增强测试用例,包括日志信息:
SoapClient client = Feign.builder()
.client(new ApacheHttp5Client())
.logger(new Slf4jLogger(SoapClient.class))
.logLevel(Logger.Level.FULL)
.target(SoapClient.class, "http://localhost:18080/ws/users/");
运行测试时,我们将看到类似以下的日志:
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>[email protected]</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编码器
使用Feign的SOAP编码器(Feign's SOAP Codec)调用SOAP Web服务是一种更干净且更好的方法。编码器有助于将SOAP消息(Java到SOAP)/反序列化(SOAP到Java)。但是,编码器需要额外的[feign-soap](https://mvnrepository.com/artifact/io.github.openfeign/feign-soap)
依赖。因此,让我们声明这个依赖:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-soap</artifactId>
<version>11.8</version>
</dependency>
Feign SOAP编码器使用JAXB和javax.xml.soap.SOAPMessage
以及JAXBContextFactory
来编码和解码SOAP对象。接下来,根据我们创建的XSD,让我们使用Maven WSDL stubs生成领域类。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>
这里,我们配置插件在编译阶段运行。现在,让我们生成stub:
mvn clean compile
构建成功后,target
文件夹将包含源代码:
接下来,我们将使用这些stub和Feign调用SOAP Web服务。但首先,让我们在SoapClient
中添加一个新的方法:
@RequestLine("POST")
@Headers({"Content-Type: text/xml;charset=UTF-8"})
CreateUserResponse createUserWithSoap(CreateUserRequest soapBody);
然后,让我们测试SOAP Web服务:
@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"));
}
让我们增强测试用例,记录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/");
这段代码将生成我们之前看到的类似日志。
最后,处理SOAP错误。Feign提供了一个SOAPErrorDecoder
,它将SOAP错误返回为SOAPFaultException
。因此,我们将设置这个SOAPErrorDecoder
作为Feign的错误解码器,并处理SOAP错误:
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"));
}
这样,如果SOAP Web服务抛出SOAP错误,它将由SOAPFaultException
处理。
6. 结论
在这篇文章中,我们学习了如何使用Feign调用SOAP Web服务。Feign是一个声明式的HTTP客户端,使得调用SOAP/REST Web服务变得简单。使用Feign的优点是减少了代码量。较少的代码行意味着更少的错误和更少的单元测试。
一如既往,完整的源代码可在GitHub上找到。