SpringBoot擴展——應用Web Service!

應用Web Service

Web Service是一個SOA(面向服務的編程)架構,這種架構不依賴於語言,不依賴於平臺,可以在不同的語言之間相互調用,通過Internet實現基於HTTP的網絡應用間的交互調用。Web Service是一個可以遠程調用的類,即它可以讓項目使用其他資源,如在網頁上顯示天氣、地圖、微博上的最新動態等,這些都是調用的其他資源。

Web Service簡介

在Web Service體系架構中有3個角色:

服務提供者(Service Provider),也稱為服務生產者;

服務請求者(Service Requester),也稱為服務消費者;

服務註冊中心(Service Register),服務提供者在這裡發佈服務,

服務請求者在這裡查找服務,獲取服務的綁定信息。

上述3個角色的請求過程如圖6.20所示。

Web Service的3個角色間主要有3個操作:

發佈(Publish):服務提供者把服務按照規范格式發佈到服務註冊中心。

查找(Find):服務請求者根據服務註冊中心提供的規范接口發出查找請求,獲取綁定服務所需的相關信息。

綁定(Bind):服務請求者根據服務綁定信息配置自己的系統,從而可以調用服務提供者提供的服務。

說明:Web Service是通過SOAP方式在Web上提供軟件服務,使用WSDL文件說明提供的軟件服務的具體信息,並通過UDDI進行註冊。

Web Service的主要適用場景是軟件的集成和復用,如氣象局(服務端系統)、天氣查詢網站等,具體如下:

當發佈一個服務(對內/對外),不考慮客戶端類型和性能時,建議使用Web Service。

如果服務端已經確定使用Web Service,則客戶端不能再選擇其他框架,必須使用Web Service。

在Java項目開發中,Web Service框架主要包括Axis2和CXF,如果需要多語言的支持,建議選擇Axis2。如果想和Spring集成或者其他程序集成,建議使用CXF,它們之間的區別如表6.4所示。

表6.4 Axis2和CXF區別

Spring Web Service簡介

Spring Web Service(Spring-WS)是Spring團隊開發的一個Java框架,其專註於創建文檔驅動的Web服務。Spring Web Service的目的是促進契約優先的SOAP服務開發,通過配置XML文件的方式,創建靈活的Web服務,簡化WebService的開發。

Spring Web Service有以下幾個功能:

XML映射到對象:可以使用Message Payload和SOAP Action Header中存儲的信息或使用XPath Expression將基於XML的請求映射給任何對象。

用於解析XML的多API支持:除瞭用於解析傳入XML請求的標準JAXPAPI(DOM、SAX、StAX)之外,還支持其他庫,如JDOM、dom4j、XOM。

用於劃分多分組XML的多API支持:Spring Web Service使用其Object/XML Mapping模塊支持JAXB 1和2、Castor、XMLBeans、JiBX和XStream庫。Object/XML Mapping模塊也可以用在非Web服務代碼中。

基於Spring的配置:在Spring Web Service應用中可以方便、快速地使用Spring配置進行項目的自定義配置。

使用WS-Security模塊:可以簽名、加密、解密SOAP消息或對其進行身份驗證。

支持Acegi安全性:使用Spring Web Service的WS-Security實現,Acegi配置可用於SOAP服務。

Spring Web Service是由5個模塊組成的,各模塊的功能如下:

Spring-WS Core:是主要模塊,提供WebServiceMessage和SoapMessage等中央接口、服務器端框架、強大的消息調度功能及實現Web服務端點的支持類。它還提供Web Service使用者客戶端作為WebServiceTemplate。

Spring-WS支持:為JMS和電子郵件等提供支持。

Spring-WS Security:負責提供與核心Web服務模塊集成的WSSecurity實現。此模塊允許使用現有的Spring SecurityImplementation進行身份驗證和授權。Spring XML:為Spring Web Service提供XML支持類。該模塊由Spring-WS框架內部使用。

Spring OXM:提供XML與對象映射的支持類。

這5個組件的關系如圖6.21所示。

實戰:Spring Web Service服務端發佈項目

下面新建一個項目,並通過Spring Web Service服務端(功能提供者)發佈。

(1)新建一個Web Service的提供者(provider),在pom.xml中添加Spring Web Service依賴如下:

org.springframework.boot

spring-boot-starter-parent

2.2.10.RELEASE

com.exampleweb-services-provider

0.0.1-SNAPSHOT

web-services-provider

Demo project for Spring Boot

1.8

org.springframework.boot

spring-boot-starter-web-services

org.apache.cxf

cxf-rt-frontend-jaxws

3.3.5

org.apache.cxf

cxf-rt-transports-http

3.3.5

org.springframework.boot

spring-boot-starter-test

test

org.junit.vintage

junit-vintage-engine

org.springframework.boot

spring-boot-maven-plugin

(2)新建Web Service的配置類,在其中配置請求地址信息如下:

package com.example.webservicesprovider.config;

import com.example.webservicesprovider.service.DemoService;

import com.example.webservicesprovider.service.impl.DemoServiceImpl;

import org.apache.cxf.Bus;

import org.apache.cxf.bus.spring.SpringBus;

import org.apache.cxf.jaxws.EndpointImpl;

import org.apache.cxf.transport.servlet.CXFServlet;

import org.springframework.boot.web.servlet.ServletRegistrationBean;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import javax.xml.ws.Endpoint;

@Configuration

public class CxfConfig {

@Bean

public ServletRegistrationBean cxfServlet() {

/**

* ServletRegistrationBean是Servlet註冊類,

* 參數1為Servlet對象,參數2為請求到Servlet的地址

*/

return new ServletRegistrationBean<>(new CXFServlet(),

"/demo/*");

}

@Bean(name = Bus.DEFAULT_BUS_ID)

public SpringBus springBus() {

return new SpringBus();

} /**

* 類的註冊

* @return

*/

@Bean

public DemoService demoService() {

return new DemoServiceImpl();

}

/**

* 發佈多個服務時,創建多個接觸點,並使用@Qualifier指定不同的名稱

* @return

*/

@Bean

public Endpoint endpoint() {

EndpointImpl endpoint = new EndpointImpl(springBus(),

demoService());

endpoint.publish("/api");

return endpoint;

}

}

(3)新建Web Service提供服務的接口:

package com.example.webservicesprovider.service;

import javax.jws.WebService;

/**

* name: Web Service的名稱;

* targetNamespace: 指定名稱空間,一般使用接口實現類的包名的反綴

*/

@WebService(name = "DemoService", targetNamespace =

"http://impl.service.

server.example.com")

public interface DemoService { String sayHello(String user);

}

(4)新建接口的實現類,對外提供的功能的實現代碼如下:

package com.example.webservicesprovider.service.impl;

import com.example.webservicesprovider.service.DemoService;

import javax.jws.WebService;

import java.time.LocalDateTime;

/**

* serviceName: 對外發佈的服務名;

* targetNamespace: 指定名稱空間,一般使用接口實現類的包名的反綴;

* endpointInterface: 服務接口的全類名;

*/

@WebService(serviceName = "DemoService"

, targetNamespace = "http://impl.service.server.example.com"

, endpointInterface =

"com.example.webservicesprovider.service.DemoService")

public class DemoServiceImpl implements DemoService {

@Override

public String sayHello(String user) {

return user + ",接收到瞭請求, 現在的時間是: " +

LocalDateTime.now();

}

}

(5)新建Spring Boot的啟動類:

package com.example.webservicesprovider;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication

public class WebServicesProviderApplication {

public static void main(String[] args) {

SpringApplication.run(WebServicesProviderApplication.class,

args);

}

}

(6)在application.properties中設置項目端口為8080:

server.port=8080

實戰:Spring Web Service客戶端調用項目

完成瞭服務提供者的創建後,新建一個Spring Web Service的消費者(client),在pomx.xml中添加Spring Web Service依賴如下:

org.springframework.boot

spring-boot-starter-parent

2.3.10.RELEASE

com.example

web-services-client

0.0.1-SNAPSHOT

web-services-client

Demo project for Spring Boot

1.8

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-web-services

org.apache.cxf

cxf-rt-frontend-jaxws

3.3.5

org.apache.cxf

cxf-rt-transports-http

3.3.5

org.springframework.boot

spring-boot-starter-test

test

org.junit.vintage

junit-vintage-engine

org.springframework.boot

spring-boot-maven-plugin

(1)在客戶端中新建一個測試服務調用的TestController入口,請求

Web Service的提供者對返回的信息進行解析並打印結果。

package com.example.webservicesclient;

import org.apache.cxf.endpoint.Client;

import

org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;

import org.apache.cxf.transport.http.HTTPConduit;

import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RestController;

import org.w3c.dom.Document;

import org.w3c.dom.Node;

import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;

import javax.xml.parsers.DocumentBuilderFactory;

import java.io.InputStream;

import java.io.OutputStream;

import java.io.OutputStreamWriter;

import java.net.URL;

import java.net.URLConnection;

import java.nio.charset.StandardCharsets;

@RestController

public class TestController {

@GetMapping("/test")

public void test() throws Exception {

//創建動態客戶端

JaxWsDynamicClientFactory factory =

JaxWsDynamicClientFactory.newInstance();

//訪問自己的服務端

Client client =

factory.createClient("http://localhost:8080/demo/api?wsdl");

// 需要密碼時要加上用戶名和密碼

// client.getOutInterceptors().add(new

ClientLoginInterceptor(USER_NAME,PASS_WORD));

HTTPConduit conduit = (HTTPConduit) client.getConduit(); HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();

httpClientPolicy.setConnectionTimeout(2000); //連接超時

httpClientPolicy.setAllowChunking(false); //取消塊編

httpClientPolicy.setReceiveTimeout(120000); //響應超時

conduit.setClient(httpClientPolicy);

//client.getOutInterceptors().addAll(interceptors); //設置攔

截器

try {

Object[] objects;

// 調用方式invoke("方法名",參數1,參數2,參數3....);

objects = client.invoke("sayHello", "cc, i miss you ");

System.out.println("返回數據:" + objects[0]);

} catch (Exception e) {

e.printStackTrace();

}

}

/**

* 測試第三方的Web Service接口,測試天氣

*/

@GetMapping("/testWeather")

public void testWeather() {

String weatherInfo = getWeather("北京");

System.out.println(weatherInfo);

}

/**

* 對服務器端返回的XML進行解析

*

* @param city用戶輸入的城市名稱

* @return字符串用#分割

*/

private static String getWeather(String city) {

Document doc;

DocumentBuilderFactory dbf =

DocumentBuilderFactory.newInstance();

dbf.setNamespaceAware(true);

try {

DocumentBuilder db = dbf.newDocumentBuilder();

InputStream is = getSoapInputStream(city); assert is != null;

doc = db.parse(is);

NodeList nl = doc.getElementsByTagName("string");

StringBuffer sb = new StringBuffer();

for (int count = 0; count < nl.getLength(); count++) {

Node n = nl.item(count);

if ("查詢結果為

空!".equals(n.getFirstChild().getNodeValue())) {

sb = new StringBuffer(" ");

break;

}

sb.append(n.getFirstChild().getNodeValue()).append("

\n");

}

is.close();

return sb.toString();

} catch (Exception e) {

e.printStackTrace();

return null;

}

}

/**

* 從接口文檔中獲取SOAP的請求頭,並替換其中的標志符號為用戶輸入的城市

* (方法的接口文檔:

* http://ws.webxml.com.cn/WebServices/WeatherWebService.asmx?

op=getWeatherbyCityName)

*

* @param city用戶輸入的城市名稱

* @return客戶將要發送給服務器的SOAP請求

*/

private static String getSoapRequest(String city) {

String sb = "" +

"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" " +

"xmlns:xsd="http://www.w3.org/2001/XMLSchema" " +

"xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">" +

"

xmlns="http://WebXml.com.cn/">" +

"" + city +

" " +

"";

return sb;

}

/**

* 通過接口文檔的請求頭構建SOAP請求,向服務器端發送SOAP請求,並返回流

*

* @param city用戶輸入的城市名稱

* @return服務器端返回的輸入流,供客戶端讀取

* @throws Exception異常

*/

private static InputStream getSoapInputStream(String city) throws

Exception {

try {

String soap = getSoapRequest(city);

// 通過請求的服務地址(即Endpoint)構建URL對象,並使用URL對象開啟連接

URL url = new

URL("http://ws.webxml.com.cn/WebServices/WeatherWebService.asmx");

URLConnection conn = url.openConnection();

conn.setUseCaches(false);

conn.setDoInput(true);

conn.setDoOutput(true);

// 為連接設置請求頭屬性

conn.setRequestProperty("Content-Length",

Integer.toString(soap.length()));

conn.setRequestProperty("Content-Type", "text/xml;

charset=utf-8");

conn.setRequestProperty("SOAPAction",

"http://WebXml.com.cn/getWeatherbyCityName");

// 將請求的XML信息寫入連接的輸出流

OutputStream os = conn.getOutputStream();

OutputStreamWriter osw = new OutputStreamWriter(os,

StandardCharsets.UTF_8);

osw.write(soap);

osw.flush();

osw.close();

// 獲取連接中請求得到的輸入流

return conn.getInputStream();

} catch (Exception e) { e.printStackTrace();

return null;

}

}

}

(2)新建Spring Boot啟動類:

package com.example.webservicesclient;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

public class WebServicesClientApplication {

public static void main(String[] args) {

SpringApplication.run(WebServicesClientApplication.class,

args);

}

}

(3)在application.properties中添加當前項目端口為8080:

server.port=8081

(4)啟動項目服務端provider和客戶端client服務,打開瀏覽器並且訪問網址localhost: 8080/demo/api?wsdl,可以看到服務端提供的WebService的說明,如圖6.22所示。

圖6.22 服務端提供的Web Service說明詳情

(5)訪問localhost:8081/test,可以測試Web Service的調用,client完成瞭provider的功能調用,可以在控制臺上看到打印信息,如圖6.23所示,表明Web Service調用成功。

圖6.23 測試Web Service調用

(6)訪問localhost:8081/testWeather,調用一個公開的Web Service方法可以查詢北京市的天氣,顯示結果如圖6.24所示。

圖6.24 調用Web Service查詢天氣

至此完成瞭Web Service調用的演示。在開發中使用Web Service對外提供接口,能夠更好地對外提供數據,實現特定的功能。