Spring Web Service Web Service SOAP WSDL Spring-WS Client side

Spring Web Service ─ Client 端實作

吳沛芸 Peiiun Wu 2020/07/24 11:44:01
69

前言

建立Web Service Client

 

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
	<groupId>org.apache.httpcomponents</groupId>
	<artifactId>httpclient</artifactId>
</dependency>

 

Spring-WS modules

 

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:sch="http://www.example.com/book" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.example.com/book" targetNamespace="http://www.example.com/book">
    <wsdl:types>
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://www.example.com/book">
            <xs:element name="getBookRequest">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="isbn" type="xs:string"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:element name="getBookResponse">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="book" type="tns:book"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:complexType name="book">
                <xs:sequence>
                    <xs:element name="isbn" type="xs:string"/>
                    <xs:element name="name" type="xs:string"/>
                    <xs:element name="author" type="xs:string"/>
                    <xs:element name="publishing" type="xs:string"/>
                    <xs:element name="edition" type="xs:int"/>
                </xs:sequence>
            </xs:complexType>
        </xs:schema>
    </wsdl:types>
    <wsdl:message name="getBookRequest">
        <wsdl:part element="tns:getBookRequest" name="getBookRequest">
    </wsdl:part>
    </wsdl:message>
    <wsdl:message name="getBookResponse">
        <wsdl:part element="tns:getBookResponse" name="getBookResponse">
    </wsdl:part>
    </wsdl:message>
    <wsdl:portType name="BookPort">
        <wsdl:operation name="getBook">
            <wsdl:input message="tns:getBookRequest" name="getBookRequest">
    </wsdl:input>
            <wsdl:output message="tns:getBookResponse" name="getBookResponse">
    </wsdl:output>
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="BookPortSoap11" type="tns:BookPort">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="getBook">
            <soap:operation soapAction=""/>
            <wsdl:input name="getBookRequest">
                <soap:body use="literal"/>
            </wsdl:input>
            <wsdl:output name="getBookResponse">
                <soap:body use="literal"/>
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="BookPortService">
        <wsdl:port binding="tns:BookPortSoap11" name="BookPortSoap11">
            <soap:address location="http://localhost:8080/bookService"/>
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>
<plugin>
    <groupId>org.jvnet.jaxb2.maven2</groupId>
    <artifactId>maven-jaxb2-plugin</artifactId>
    <version>0.14.0</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <schemaLanguage>WSDL</schemaLanguage>
        <generateDirectory>${project.basedir}/src/main/java</generateDirectory>
        <generatePackage>com.example.demo.model</generatePackage>
        <schemaDirectory>${project.basedir}/src/main/resources</schemaDirectory>
        <schemaIncludes>
            <include>book.wsdl</include>
        </schemaIncludes>
    </configuration>
</plugin>
wsimport -keep -d [目標路徑] -p [目標 package 路徑] [ wsdl 路徑或網址]

用 command line 生成的資料夾多了 Interface 和 Service,但在 Spring-WS 實作上不會用到、刪除亦可,兩者生成的 Java 檔差異如下:

生成方式不同的物件差異

package com.example.demo;

import java.net.ConnectException;
import java.net.SocketTimeoutException;
import org.apache.http.conn.ConnectTimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
import com.example.demo.model.GetBookRequest;
import com.example.demo.model.GetBookResponse;

public class BookClient extends WebServiceGatewaySupport {

    private static final Logger LOGGER = LoggerFactory.getLogger(BookClient.class);

    public GetBookResponse getBook(String isbn) {

        GetBookRequest request = new GetBookRequest();
        request.setIsbn(isbn);

        GetBookResponse response = null;
        try {
            response = (GetBookResponse) getWebServiceTemplate().marshalSendAndReceive(request);

        } catch (Exception ex) {
            if (ex.getCause() instanceof ConnectException) {
                LOGGER.error("Connect error ...", ex);
            } else if (ex.getCause() instanceof ConnectTimeoutException) {
                LOGGER.error("Connect time out error ...", ex);
            } else if (ex.getCause() instanceof SocketTimeoutException) {
                LOGGER.error("Read time out error ...", ex);
            } else {
                LOGGER.error("Other error ...", ex);
            }
        }

        return response;
    }
}

 

package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.client.support.interceptor.ClientInterceptor;
import org.springframework.ws.transport.WebServiceMessageSender;
import org.springframework.ws.transport.http.HttpComponentsMessageSender;

@Configuration
public class WebClientConfig {

    @Bean
    public Jaxb2Marshaller marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setContextPath("com.example.demo.model");
        return marshaller;
    }

    @Bean
    public WebServiceMessageSender webServiceMessageSender() {
        HttpComponentsMessageSender sender = new HttpComponentsMessageSender();
        sender.setConnectionTimeout(5 * 1000);
        sender.setReadTimeout(5 * 1000);
        return sender;
    }

    @Bean
    public BookClient wsClient(Jaxb2Marshaller marshaller, WebServiceMessageSender sender) {
        BookClient client = new BookClient();
        client.setDefaultUri("http://localhost:8080/bookService");
        client.setMarshaller(marshaller);
        client.setUnmarshaller(marshaller);
        client.setMessageSender(sender);
        ClientInterceptor[] ci = {new LoggingInterceptor()};
        client.setInterceptors(ci);
        return client;
    }
		
}

 

package com.example.demo;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.example.demo.model.Book;
import com.example.demo.model.GetBookResponse;

@SpringBootTest
class WebServiceClientApplicationTests {

    private static final Logger LOGGER =
            LoggerFactory.getLogger(WebServiceClientApplicationTests.class);

    @Test
    void contextLoads() {}

    @Autowired
    BookClient client;

    @Test
    public void getBook() {
        GetBookResponse response = client.getBook("9789570841008");
        if (response != null) {
            Book resBookData = response.getBook();
            LOGGER.debug("Book Name:{} author:{} Currency:{} Population:{}", resBookData.getName(),
                    resBookData.getAuthor(), resBookData.getPublishing(), resBookData.getEdition());
        } else {
            LOGGER.debug("無法取得資訊...");
        }
    }

}

 

logging.level.org.springframework.ws.client.MessageTracing.sent=DEBUG
logging.level.org.springframework.ws.client.MessageTracing.received=TRAC

 

設置攔截器:
對比 properties 攔截器就靈活許多,藉由實做 ClientInterceptor 的方法對 SOAP 的請求回應再次加工。一次完整的請求回應會經過 handleRequest、handleResponse(或handleFault)、afterCompletion;若是出現 TimeoutException 或是 ConnectException 則是只有 handleRequest 和 afterCompletion。由於攔截器可以設置多個,返回的布林值是 True 時表示要繼續下個攔截器。

package com.example.demo;

import java.io.ByteArrayOutputStream;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import org.apache.http.conn.ConnectTimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ws.client.WebServiceClientException;
import org.springframework.ws.client.support.interceptor.ClientInterceptor;
import org.springframework.ws.context.MessageContext;

public class LoggingInterceptor implements ClientInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoggingInterceptor.class);

    @Override
    public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
        try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
            messageContext.getRequest().writeTo(out);
            String outStr = new String(out.toString("UTF-8"));
            LOGGER.info("== req == messageContext:{}", outStr);
        } catch (Exception e) {
            LOGGER.error("error...", e);
        }

        return true;
    }

    @Override
    public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
        try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
            messageContext.getResponse().writeTo(out);
            String outStr = new String(out.toString("UTF-8"));
            LOGGER.info("== res == messageContext:{}", outStr);
        } catch (Exception e) {
            LOGGER.error("error...", e);
        }
        return true;
    }

    @Override
    public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
        try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
            messageContext.getResponse().writeTo(out);
            String outStr = new String(out.toString("UTF-8"));
            LOGGER.info("== fault == messageContext:{}", outStr);
        } catch (Exception e) {
            LOGGER.error("error...", e);
        }
        return true;
    }

    @Override
    public void afterCompletion(MessageContext messageContext, Exception ex)
            throws WebServiceClientException {
        if (ex != null) {
            if (ex instanceof ConnectException) {
                LOGGER.info("== aftercomplete == do with Connect error...");
            } else if (ex instanceof ConnectTimeoutException) {
                LOGGER.info("== aftercomplete == do with Connect timeout error...");
            } else if (ex instanceof SocketTimeoutException) {
                LOGGER.info("== aftercomplete == do with Read timeout ...");
            } else {
                LOGGER.error("== aftercomplete == do with other error ...", ex);
            }
        } else {
            LOGGER.info("== aftercomplete == do something ...");
        }
    }
}
com.example.demo.LoggingInterceptor      : == req == messageContext:<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><ns2:getBookRequest xmlns:ns2="http://www.example.com/book"><ns2:isbn>9789861856216</ns2:isbn></ns2:getBookRequest></SOAP-ENV:Body></SOAP-ENV:Envelope>
o.s.ws.client.MessageTracing.sent        : Sent request [SaajSoapMessage {http://www.example.com/book}getBookRequest]
o.s.ws.client.MessageTracing.received    : Received response [<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><ns2:getBookResponse xmlns:ns2="http://www.example.com/book"><ns2:book><ns2:isbn>9789861856216</ns2:isbn><ns2:name>冰與火之歌:權力遊戲</ns2:name><ns2:author>喬治馬汀</ns2:author><ns2:publishing>2011</ns2:publishing><ns2:edition>1</ns2:edition></ns2:book></ns2:getBookResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>] for request [<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><ns2:getBookRequest xmlns:ns2="http://www.example.com/book"><ns2:isbn>9789861856216</ns2:isbn></ns2:getBookRequest></SOAP-ENV:Body></SOAP-ENV:Envelope>]
com.example.demo.LoggingInterceptor      : == res == messageContext:<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><ns2:getBookResponse xmlns:ns2="http://www.example.com/book"><ns2:book><ns2:isbn>9789861856216</ns2:isbn><ns2:name>冰與火之歌:權力遊戲</ns2:name><ns2:author>喬治馬汀</ns2:author><ns2:publishing>2011</ns2:publishing><ns2:edition>1</ns2:edition></ns2:book></ns2:getBookResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
com.example.demo.LoggingInterceptor      : == aftercomplete == do something ...
com.example.demo.LoggingInterceptor      : == req == messageContext:<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><ns2:getBookRequest xmlns:ns2="http://www.example.com/book"><ns2:isbn>9789861856216</ns2:isbn></ns2:getBookRequest></SOAP-ENV:Body></SOAP-ENV:Envelope>
o.s.ws.client.MessageTracing.sent        : Sent request [SaajSoapMessage {http://www.example.com/book}getBookRequest]
com.example.demo.LoggingInterceptor      : == aftercomplete == do with Read timeout ...
com.example.demo.BookClient              : Read time out error ...
org.springframework.ws.client.WebServiceIOException: I/O error: Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out
......
Caused by: java.net.SocketTimeoutException: Read timed out
......

專案資料夾結構:

專案資料夾結構

 

結語

 

吳沛芸 Peiiun Wu