应用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依赖如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId><artifactId>web-services-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>web-services-provider</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.3.5</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.3.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> </plugin>
</plugins>
</build>
(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> 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依赖如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>web-services-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>web-services-client</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.3.5</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.3.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
(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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<soap:Envelope
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/\">" +
"<soap:Body> <getWeatherbyCityName
xmlns=\"http://WebXml.com.cn/\">" +
"<theCityName>" + city +
"</theCityName> </getWeatherbyCityName>" +
"</soap:Body></soap:Envelope>";
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对外提供接口,能够更好地对外提供数据,实现特定的功能。