Java Web服务全面解析与实战应用
1. 引言
在当今的软件开发领域,Web服务已成为实现不同系统间通信和数据共享的关键技术。它不仅能让开发者轻松访问各种在线服务,还推动了下一代万维网——语义网的发展。本文将深入探讨Web服务的相关知识,包括其原理、标准,以及如何在Java环境中创建和使用Web服务,并通过一个天气网站的实例进行详细说明。
2. 启动本地COS命名服务
要注册 FileNotification 实例,需先启动本地的COS命名服务。JDK通过 orbd 工具提供了CORBA COS命名服务。在命令行运行 orbd 即可启动命名服务:
orbd
若未指定参数, orbd 将在端口1049运行,代码会在此端口查找该服务。 orbd 启动后,可启动客户端程序:
java book.FileNotificationImpl
3. Web服务概述
3.1 Web服务的发展与优势
近年来,Web服务从炒作中脱颖而出,成为一种广泛应用的互操作性技术。许多大型厂商,如亚马逊、eBay和谷歌,都为其服务提供了Web服务API。Web服务的主要驱动力包括实现对网站服务的编程访问,以及促进企业间的通信。它是下一代万维网(语义网)的关键推动者,能使内容不仅对人类可读,还能被机器读取。
3.2 Web服务的特点与局限性
Web服务支持远程过程调用和异步消息传递,通常以XML消息的形式通过HTTP协议实现。不过,它也存在一些局限性,如不支持开箱即用的会话(可模拟)、不支持像CORBA或RMI那样的分布式对象,且在安全消息传递和可靠传输方面的安全标准实现较晚。其首要目标是互操作性,Web服务互操作性组织(WS - I)的成立就体现了这一点。
3.3 Java 6对Web服务的支持
从Java 6开始,Web服务支持被集成到核心Java平台中,仅使用标准JRE即可使用和托管Web服务。尽管Web服务并非用于替代RMI和CORBA,但大多数分布式架构开始将其作为与其他分布式系统的接口。
4. 随机天气网站示例
4.1 网站介绍
以 random - weather.org 网站为例,该网站根据用户输入的邮政编码生成随机天气预报。其HTML界面简单,用户输入邮政编码后提交表单,网站会返回相应的天气信息。
4.2 网站HTML代码
以下是输入邮政编码页面的HTML代码:
<html>
<head>
<title>Weather Page</title>
</head>
<body>
<h1>Get the Current Weather!</h1>
<form method="get" action="weather.jsp">
<table border="0" cellspacing="2" cellpadding="2">
<tr>
<td><input name="zipcode" type="textbox"/></td>
<td><input type="submit" value="Get Weather!"/></td>
</tr>
</table>
</form>
</body>
</html>
以下是显示天气信息页面的HTML代码:
<html>
<head>
<title>Weather for Zip Code: 12345</title>
</head>
<body>
<h1>Weather for Zip Code: 12345</h1>
<table border="0" cellspacing="2" cellpadding="2">
<tr>
<td>High Temperature</td>
<td>93</td>
</tr>
<tr>
<td>Low Temperature</td>
<td>73</td>
</tr>
<tr>
<td>Description</td>
<td>Partly Cloudy</td>
</tr>
<tr>
<td>Barometer</td>
<td>26.11519 and Rising</td>
</tr>
</table>
</body>
</html>
3.3 编程访问的挑战与Web服务的优势
若要通过软件编程访问该网站的天气信息,需解析HTML并提取相关数据。但网站设计的改变可能会破坏客户端软件。若网站提供Web服务,开发人员可轻松编写代码访问,且返回的数据将是结构化的,与展示分离。
5. 平台无关的RPC
5.1 原理
Web服务是平台无关的远程过程调用的实现。可将每个Web服务视为一个远程方法,XML编码的请求被发送到服务器,服务器返回XML编码的响应。通常,Web服务通过HTTP POST操作发送。
5.2 优势
- 成熟协议 :HTTP是一种成熟的协议,被多种语言实现,借助现有协议可加速采用并避免一些RPC规范中常见的互操作性问题。
- 易于调试 :使用XML over HTTP的应用程序比使用二进制协议(如JRMP或IIOP)的应用程序更易于调试,可使用TCPMon等工具查看消息的明文内容。
5.3 局限性
XML Web服务不具备CORBA或RMI的某些特性,如不支持会话、过程回调和分布式对象。安全、事务和可扩展性等标准因实现而异。
6. WS - I基本配置文件
6.1 简介
WS - I基本配置文件是Web服务互操作性的一组实现指南。Java 6中的Java API for XML Web Services(JAX - WS)符合WS - I基本配置文件1.1版本。
6.2 规范说明
该配置文件虽不定义具体规范,但明确了如何使用相关标准。例如,它要求在SOAP中使用XML Schema进行数据编码(文档样式编码),而非SOAP RPC编码;在HTTP方面,它规定了在不同情况下应返回的HTTP代码以及如何处理错误。
6.3 重要性
使用符合1.1基本配置文件的工具包实现Web服务,有助于实现互操作性。目前,谷歌、eBay和亚马逊等公司提供了专门的客户端Web服务SDK,以确保与他们的服务器互操作。随着WS - I基本配置文件的广泛采用,未来仅发布服务的WSDL描述就可能实现互操作性。
7. Web服务描述语言(WSDL)
7.1 作用
WSDL类似于CORBA IDL,是一种接口文档,用于描述如何与特定的Web服务进行通信。它可以更详细地描述传递的数据,因为所有数据都以XML定义。
7.2 组成部分
- Types :服务中使用的XML数据类型(可使用XML Schema描述)。
- Messages :每个Web服务的内容定义。
- Port Types :指定特定操作或方法的输入和/或输出消息。
- Binding :指定Web服务运行的底层通信协议和传输协议。
7.3 示例
以下是一个简单的WSDL示例,用于 random - weather.org 网站的天气服务:
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://book/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" targetNamespace="http://book/"
name="WeatherGetterService">
<types>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://book/" version="1.0">
<xs:element xmlns:ns1="http://book/" name="getWeather"
type="ns1:getWeather"></xs:element>
<xs:complexType name="getWeather">
<xs:sequence>
<xs:element name="zipcode" type="xs:int"></xs:element>
</xs:sequence>
</xs:complexType>
<xs:element xmlns:ns2="http://book/" name="getWeatherResponse"
type="ns2:getWeatherResponse"></xs:element>
<xs:complexType name="getWeatherResponse">
<xs:sequence>
<xs:element xmlns:ns3="http://book/" name="return" type="ns3:weather"
minOccurs="0"></xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="weather">
<xs:sequence>
<xs:element name="barometer" type="xs:float"></xs:element>
<xs:element name="barometer-description" type="xs:string"
minOccurs="0"></xs:element>
<xs:element name="general-description" type="xs:string"
minOccurs="0"></xs:element>
<xs:element name="high-temperature" type="xs:int"></xs:element>
<xs:element name="low-temperature" type="xs:int"></xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>
</types>
<message name="getWeather">
<part element="tns:getWeather" name="parameters"></part>
</message>
<message name="getWeatherResponse">
<part element="tns:getWeatherResponse" name="parameters"></part>
</message>
<portType name="WeatherGetter">
<operation name="getWeather">
<input message="tns:getWeather"></input>
<output message="tns:getWeatherResponse"></output>
</operation>
</portType>
<binding name="WeatherGetterPortBinding" type="tns:WeatherGetter">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"></soap:binding>
<operation name="getWeather">
<soap:operation soapAction=""></soap:operation>
<input>
<soap:body use="literal"></soap:body>
</input>
<output>
<soap:body use="literal"></soap:body>
</output>
</operation>
</binding>
<service name="WeatherGetterService">
<port name="WeatherGetterPort" binding="tns:WeatherGetterPortBinding">
<soap:address
location="http://localhost:8080/services/weather"></soap:address>
</port>
</service>
</definitions>
在JDK 6的JAX - WS实现中,Java API for XML Binding(JAXB)用于将Java Web服务的参数和返回类型定义为XML Schema,以符合WS - I基本配置文件1.1推荐的文档/文字编码。
8. 简单对象访问协议(SOAP)
8.1 协议定义
SOAP是Web服务使用的通信协议,是一种基于XML的消息格式,具有平台无关性。其规范可在 http://www.w3.org/TR/soap/ 查看。
8.2 结构
SOAP消息具有以下结构属性:
- Envelope :整个XML消息的根元素,包含消息的所有内容。
- Headers :可将XML数据放在SOAP消息的头部,与实际内容分离。
- Body :包含SOAP消息传递的XML内容。
8.3 示例
以下是 random - weather.org Web服务的SOAP请求和响应消息示例:
请求消息 :
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<getWeather xmlns="http://book/">
<zipcode xmlns="">12345</zipcode>
</getWeather>
</soap:Body>
</soap:Envelope>
响应消息 :
<?xml version="1.0" ?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://book/">
<soapenv:Body>
<ns1:getWeatherResponse>
<return>
<barometer>30.623604</barometer>
<barometer-description>Holding Steady</barometer-description>
<general-description>Sunny</general-description>
<high-temperature>23</high-temperature>
<low-temperature>17</low-temperature>
</return>
</ns1:getWeatherResponse>
</soapenv:Body>
</soapenv:Envelope>
大多数Web服务工具包会处理SOAP的具体语法,因此学习WSDL的细节更为重要。
9. 底层传输协议
SOAP可以通过多种协议传输,常见的是HTTP/HTTPS(基于TCP/IP),也可通过以下协议发送:
- 直接TCP/IP(无HTTP/HTTPS)
- 简单邮件传输协议(SMTP)
- Java消息服务协议
通过HTTP传输时,Web服务是正常的HTTP请求,无需在网络防火墙上打开新端口。
10. 天气网站的实现
10.1 核心类
天气网站的核心类是 WeatherGetter ,它根据邮政编码随机生成天气预报。预报内容包括高温、低温、天气描述和气压及描述。这些信息由 Weather JavaBean类保存:
package book;
public class Weather {
private String description;
private int lowTemp;
private int highTemp;
private float barometer;
private String barometerDescription;
public float getBarometer() {
return barometer;
}
public void setBarometer(float barometer) {
this.barometer = barometer;
}
public String getBarometerDescription() {
return barometerDescription;
}
public void setBarometerDescription(String barometerDescription) {
this.barometerDescription = barometerDescription;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getHighTemp() {
return highTemp;
}
public void setHighTemp(int highTemp) {
this.highTemp = highTemp;
}
public int getLowTemp() {
return lowTemp;
}
public void setLowTemp(int lowTemp) {
this.lowTemp = lowTemp;
}
}
10.2 天气生成逻辑
WeatherGetter 类的代码如下:
package book;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Random;
public class WeatherGetter {
private Random random;
public WeatherGetter() {
this.random = new Random();
}
public Weather getWeather(int zipCode) {
Calendar cal = new GregorianCalendar();
// changes the weather value daily
random.setSeed(zipCode + cal.get(Calendar.DAY_OF_YEAR) +
cal.get(Calendar.YEAR));
Weather w = new Weather();
int x = random.nextInt(100);
int y = random.nextInt(100);
if (x >= y) {
w.setHighTemp(x);
w.setLowTemp(y);
} else {
w.setHighTemp(y);
w.setLowTemp(x);
}
w.setBarometer(25 + random.nextFloat() * 8);
if (random.nextBoolean()) {
if (random.nextBoolean()) {
w.setBarometerDescription("Rising");
} else w.setBarometerDescription("Falling");
} else w.setBarometerDescription("Holding Steady");
String adjective;
String noun;
if (random.nextBoolean()) {
adjective = "Partly";
} else adjective = "";
if (random.nextBoolean()) {
noun = "Sunny";
} else noun = "Cloudy";
if (("Partly".equals(adjective) || "Cloudy".equals(noun))
&& random.nextBoolean()) {
noun += ", Chance of ";
if (w.getLowTemp() < 32)
noun += "snow";
else noun += "rain";
}
w.setDescription((adjective + " " + noun).trim());
return w;
}
}
10.3 用户交互流程
用户与天气网站的交互流程如下:
1. 用户通过浏览器请求 http://random - weather.org/weather/ 。
2. index.jsp 返回用于输入邮政编码的HTML表单。
3. 用户输入邮政编码并提交表单。
4. index.jsp 处理请求,调用 WeatherGetter.getWeather(int zipcode) 方法获取填充的 Weather bean。
5. index.jsp 将 getWeather() 返回的 Weather bean中的信息以HTML表格的形式返回给用户。
10.4 暴露为Web服务的意义
getWeather() 方法是获取天气预报的核心,将其暴露为Web服务可实现对天气信息的编程访问。
graph LR
A[用户请求网站] –> B[index.jsp返回表单]
B –> C[用户输入并提交邮编]
C –> D[index.jsp处理请求]
D –> E[调用getWeather方法]
E –> F[返回Weather bean]
F –> G[index.jsp返回HTML表格]
11. 从Java方法创建Web服务
11.1 创建步骤
创建Web服务并将Java方法暴露出来,主要有以下三个必要步骤:
1. 使用 javax.jws 中的注解标注要暴露为Web服务的类以及这些类中要暴露为Web方法的方法。同时,使用Java API for XML Binding(JAXB)注解标注Web方法的参数和返回值,以指定它们在XML中的编码方式(JAXB注解位于 javax.xml.bind.annotation 中)。
2. 运行JDK 6中包含的 wsgen 工具,生成指定Web服务请求和响应容器元素所需的额外JAXB类。
3. 部署Web服务。
11.2 使用JAX - WS注解
创建Web服务的第一步是使用JAX - WS注解标注代表服务的Java类。每个Java类可以代表一个Web服务,并且可以将其一个或多个方法暴露为Web方法。以下是JAX - WS的关键注解及其功能:
| 注解(来自 javax.jws ) | 功能 |
| — | — |
| WebMethod | 将Java方法暴露为Web服务方法,可提供WSDL操作名称,必须与应用于其类的 WebService 注解结合使用。 |
| WebParam | 允许Web服务方法(标记有 WebMethod )的参数指定其XML元素名称和命名空间,还可提供其他WSDL信息,如参数类型(IN、OUT、INOUT)以及在SOAP消息中参数的解码位置。 |
| WebResult | 类似于 WebParam ,用于标记Web服务方法的返回值,可自定义返回值的XML元素名称和命名空间。 |
| WebService | 将Java类标记为Web服务,类上必须存在此注解,其方法才能被标记为 WebMethod ,可自定义服务名称并提供自定义WSDL文件的位置。 |
以下是将 WeatherGetter 类暴露为Web服务的注解示例:
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
@WebService(name="WeatherService")
public class WeatherGetter {
@WebMethod
public @WebResult(name="weather") Weather getWeather(@WebParam(name="zipcode") int zipCode) {
// 方法实现
}
}
JAX - WS注解不能指定 getWeather() 方法返回值 Weather bean的XML格式,但可指定元素名称。对于 Weather bean,可使用JAXB注解自定义其XML表示:
import javax.xml.bind.annotation.XmlElement;
public class Weather {
@XmlElement(name="barometer")
public float getBarometer() {
// 方法实现
}
@XmlElement(name="barometer-description")
public String getBarometerDescription() {
// 方法实现
}
@XmlElement(name="general-description")
public String getDescription() {
// 方法实现
}
@XmlElement(name="high-temperature")
public int getHighTemp() {
// 方法实现
}
@XmlElement(name="low-temperature")
public int getLowTemp() {
// 方法实现
}
}
所有Web方法的参数和返回值必须可由JAXB序列化,即类必须有默认的无参构造函数,且类中要序列化的所有字段或属性也必须是JAXB可序列化的类型。
11.3 运行wsgen
在部署带有JAX - WS注解的类作为Web服务之前,必须运行 wsgen 工具。该工具生成SOAP信封消息元素、表示服务中特定方法请求的元素以及表示响应的元素。只有当服务方法的参数和返回值都是Java基本类型时,才无需运行 wsgen 。运行命令如下:
wsgen -cp . book.WeatherGetter
运行 wsgen 工具后,会生成以下文件:
./book/jaxws/GetWeather.class
./book/jaxws/GetWeatherResponse.class
./book/jaxws/GetWeather.java
./book/jaxws/GetWeatherResponse.java
生成的 GetWeather 和 GetWeatherResponse 类是带有JAXB注解的Java Bean,分别代表 getWeather() 请求消息和相应的响应消息。如果不运行 wsgen 就尝试部署Web服务,会收到 ClassNotFoundException 。
11.4 使用JDK 6部署Web服务
JDK 6新增了一个内部Web服务器,可仅使用Java 6运行时来部署Web服务。使用JDK的内部Web服务器部署基于JAX - WS开发的Web服务快速且简单。以下是部署示例代码:
import javax.xml.ws.Endpoint;
public class Main {
public static void main(String[] args) throws Exception {
WeatherGetter wg = new WeatherGetter();
// 在端口8080创建并启动一个新的Web服务端点
Endpoint endpoint = Endpoint.publish("http://localhost:8080/services/weather", wg);
// 让Web服务器运行5分钟,然后关闭
Thread.sleep(1000 * 60 * 5);
// 取消发布Web服务(并停止Web服务器)
endpoint.stop();
}
}
发布Web服务后,可通过在端点URL后附加 ?wsdl 访问其WSDL,例如 http://localhost:8080/services/weather?wsdl 。
11.5 使用Tomcat部署Web服务
要将Web服务部署到Apache Tomcat,可按以下步骤操作:
1. 下载并安装最新版本的Apache Tomcat(编写本文时,最新的5.5.x版本可从 http://tomcat.apache.org/download - 55.cgi 下载)。
2. 正常部署Web应用程序(将Web应用程序目录复制到 TOMCAT_HOME/webapps )。
3. 创建 sun - jaxws.xml 配置文件,指定Web服务端点,并将Sun的Web服务Servlet和上下文监听器添加到 web.xml 中。确保 sun - jaxws.xml 文件部署到Web应用程序的 WEB - INF 目录。
11.5.1 配置web.xml
以下是配置了JAX - WS Web服务的 web.xml 示例:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<listener>
<listener-class>
com.sun.xml.ws.transport.http.servlet.WSServletContextListener
</listener-class>
</listener>
<servlet>
<servlet-name>jaxservlet</servlet-name>
<servlet-class>
com.sun.xml.ws.transport.http.servlet.WSServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jaxservlet</servlet-name>
<url-pattern>/weather</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>
index.html
</welcome-file>
</welcome-file-list>
</web-app>
11.5.2 配置sun - jaxws.xml
sun - jaxws.xml 文件用于定义Web服务端点,示例如下:
<?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0">
<endpoint
name="weather"
implementation="book.WeatherGetter"
url-pattern="/weather"/>
</endpoints>
部署时,不要忘记将 wsgen 生成的类包含在Web应用程序的类路径中。如果遇到 ClassNotFoundExceptions ,可下载最新的JAX - WS RI并将其JAR文件放入 TOMCAT_HOME/shared/lib 目录。
12. 编写Web服务客户端
12.1 运行wsimport
当有Web服务的WSDL时,编写Web服务客户端快速且简单。JDK 6中的 wsimport 工具可根据WSDL生成Web服务客户端存根类。运行命令如下:
wsimport http://localhost:8080/weather/weather?wsdl
运行 wsimport 工具后,会生成以下类:
./book/GetWeather.class
./book/GetWeatherResponse.class
./book/ObjectFactory.class
./book/package-info.class
./book/Weather.class
./book/WeatherGetter.class
./book/WeatherGetterService.class
wsimport 工具默认不生成支持异步Web服务调用的存根。若要支持异步调用,需创建一个JAX - WS绑定XML文件,示例如下:
<bindings
wsdlLocation="http://localhost:8080/weather/weather?wsdl"
xmlns="http://java.sun.com/xml/ns/jaxws">
<enableAsyncMapping>true</enableAsyncMapping>
</bindings>
运行带有绑定文件的 wsimport 命令:
wsimport -b bindings.xml http://localhost:8080/weather/weather?wsdl
12.2 调用Web服务
12.2.1 同步调用
同步调用会阻塞当前线程,直到Web服务调用完成。以下是同步调用的示例代码:
import book.Weather;
import book.WeatherGetter;
import book.WeatherGetterService;
public class WcMain {
public static void main(String[] args) throws Exception {
WeatherGetterService wgs = new WeatherGetterService();
WeatherGetter wg = wgs.getWeatherGetterPort();
Weather w = wg.getWeather(12345);
System.out.println(w.getGeneralDescription());
System.out.println(w.getBarometer() + " : " + w.getBarometerDescription());
System.out.println(w.getLowTemperature() + " / " + w.getHighTemperature());
}
}
12.2.2 异步调用
异步调用是非阻塞的,Web服务完成后会触发一个事件。以下是异步调用的示例代码:
import book.Weather;
import book.WeatherGetter;
import book.WeatherGetterService;
import book.GetWeatherResponse;
import java.util.concurrent.Future;
import javax.xml.ws.AsyncHandler;
import javax.xml.ws.Response;
public class WcMain {
public static void main(String[] args) throws Exception {
WeatherGetterService wgs = new WeatherGetterService();
WeatherGetter wg = wgs.getWeatherGetterPort();
Future f = wg.getWeatherAsync(12345, new AsyncHandler<GetWeatherResponse>() {
public void handleResponse(Response<GetWeatherResponse> response) {
try {
if (!response.isCancelled() && response.isDone()) {
System.out.println("Async call back");
GetWeatherResponse msg = response.get();
Weather w = msg.getWeather();
System.out.println(w.getGeneralDescription());
System.out.println(w.getBarometer() + " : " + w.getBarometerDescription());
System.out.println(w.getLowTemperature() + " / " + w.getHighTemperature());
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
// 取消异步调用
// f.cancel(true);
Thread.sleep(10000);
}
}
异步调用的返回类型是 java.util.concurrent.Future ,可使用其 cancel() 方法取消请求。
12.3 WSDL位置
JDK 6中包含的JAX - WS参考实现,在初始化 javax.xml.ws.Service 类(所有Web服务客户端类的基类)时会解析Web服务的WSDL。若要更改WSDL的初始解析位置,可采用以下方法:
- 使用指定WSDL URL的服务构造函数。
- 保存WSDL副本,从文件系统或类路径(作为资源)加载,并传递该文件的WSDL URL。
- 重新生成存根,将 wsimport 指向生产环境的WSDL文件。
以下是使用不同位置的WSDL文件初始化 WeatherGetterService 实例的示例代码:
import javax.xml.namespace.QName;
import java.net.URL;
public class Main {
public static void main(String[] args) throws Exception {
WeatherGetterService wgs = new WeatherGetterService(new URL("http://production:8080/weather/weather?wsdl"), new QName("http://book/", "WeatherGetterService"));
}
}
13. Web服务安全 - 基本HTTP认证
13.1 服务器端启用基本HTTP认证
基本HTTP认证通常足以确定调用方的身份。以下是在天气Web应用程序示例中启用基本HTTP认证的 web.xml 文件示例:
<security-constraint>
<web-resource-collection>
<web-resource-name>
Weather web service
</web-resource-name>
<url-pattern>/weather/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>tomcat</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>UserDatabase</realm-name>
</login-config>
用户、密码和角色在 TOMCAT_HOME/conf/tomcat - users.xml 文件中指定:
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
<role rolename="tomcat"/>
<role rolename="role1"/>
<user username="tomcat" password="tomcat" roles="tomcat"/>
<user username="both" password="tomcat" roles="tomcat,role1"/>
<user username="mytestusername" password="itspassword" roles="tomcat"/>
<user username="role1" password="tomcat" roles="role1"/>
</tomcat-users>
若要在Web服务类中获取用户信息,可使用 WebServiceContext ,示例代码如下:
import javax.annotation.Resource;
import java.security.Principal;
import javax.xml.ws.WebServiceContext;
@WebService(name="WeatherGetter")
public class WeatherGetter {
@Resource WebServiceContext wsContext;
@WebMethod
public @WebResult(name="weather") Weather getWeather(@WebParam(name="zipcode") int zipCode) {
Principal usrPrincipal = wsContext.getUserPrincipal();
if (usrPrincipal != null) {
// 记录用户访问信息
}
if (wsContext.isUserInRole("tomcat")) {
// 根据用户角色执行特殊操作
}
return null;
}
}
使用基本HTTP认证时,应始终使用安全的HTTPS连接发送密码。
13.2 客户端启用基本HTTP认证
客户端启用基本HTTP认证有两种方法:
13.2.1 使用JDK的原生支持
可通过扩展 java.net.Authenticator 类并实现 getPasswordAuthentication() 方法来实现,示例代码如下:
import java.net.Authenticator;
import java.net.PasswordAuthentication;
public class BasicHTTPAuthenticator extends Authenticator {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if ("https".equalsIgnoreCase(this.getRequestingProtocol()) && "localhost".equalsIgnoreCase(this.getRequestingHost())) {
return new PasswordAuthentication("mytestusername", new char[] { 'i', 't', 's', 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' });
}
return null;
}
}
设置默认认证器:
Authenticator.setDefault(new BasicHTTPAuthenticator());
13.2.2 使用JAX - WS请求上下文
可通过 javax.xml.ws.BindingProvider 接口设置用户名和密码,示例代码如下:
import javax.xml.ws.BindingProvider;
import book.Weather;
import book.WeatherGetter;
import book.WeatherGetterService;
public class Main {
public static void main(String[] args) {
WeatherGetterService wgs = new WeatherGetterService();
WeatherGetter wg = wgs.getWeatherGetterPort();
((BindingProvider) wg).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "mytestusername");
((BindingProvider) wg).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "itspassword");
Weather w = wg.getWeather(12345);
}
}
14. 天气系统托盘应用
14.1 应用概述
天气系统托盘应用 Weather Watcher 允许用户从系统托盘中检查指定邮政编码的最新天气预报。它使用JAX - WS异步调用模型,并提供取消已发送天气请求的功能。
14.2 关键代码分析
以下是 WeatherWatcher 类的关键代码:
import java.awt.AWTException;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.util.concurrent.Future;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.xml.ws.AsyncHandler;
import javax.xml.ws.Response;
public class WeatherWatcher implements ActionListener {
private static class BasicHTTPAuthenticator extends Authenticator {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if ("https".equalsIgnoreCase(this.getRequestingProtocol()) && "localhost".equalsIgnoreCase(this.getRequestingHost())) {
return new PasswordAuthentication("mytestusername", new char[] { 'i', 't', 's', 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' });
}
return null;
}
}
private TrayIcon icon;
private WeatherGetterService service;
private WeatherGetter weatherGetter;
private int zipcode = 12345;
private MenuItem getWeatherMI;
private MenuItem cancelMI;
private Future future;
public WeatherWatcher() throws AWTException {
Authenticator.setDefault(new BasicHTTPAuthenticator());
service = new WeatherGetterService();
weatherGetter = service.getWeatherGetterPort();
SystemTray sysTray = SystemTray.getSystemTray();
Image wImage = Toolkit.getDefaultToolkit().getImage(WeatherWatcher.class.getResource("book-weather-icon.png"));
PopupMenu menu = new PopupMenu();
getWeatherMI = menu.add(new MenuItem("Get Weather"));
getWeatherMI.addActionListener(this);
cancelMI = menu.add(new MenuItem("Cancel"));
cancelMI.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (future != null && !future.isDone()) {
future.cancel(true);
getWeatherMI.setEnabled(true);
cancelMI.setEnabled(false);
}
}
});
cancelMI.setEnabled(false);
menu.add(new MenuItem("-"));
menu.add(new MenuItem("Configure Zipcode")).addActionListener(new ActionListener() {
// 处理邮政编码配置
});
menu.add(new MenuItem("Exit")).addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
System.exit(0);
}
});
this.icon = new TrayIcon(wImage, "Weather Watcher", menu);
sysTray.add(this.icon);
}
public void actionPerformed(ActionEvent evt) {
this.getWeatherMI.setEnabled(false);
cancelMI.setEnabled(true);
future = weatherGetter.getWeatherAsync(zipcode, new AsyncHandler<GetWeatherResponse>() {
public void handleResponse(Response<GetWeatherResponse> resp) {
try {
final Weather w;
if (!resp.isCancelled() && resp.isDone()) {
GetWeatherResponse gwr = resp.get();
w = gwr.getWeather();
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
cancelMI.setEnabled(false);
getWeatherMI.setEnabled(true);
StringBuffer msg = new StringBuffer();
msg.append("Zipcode: " + zipcode + "\n\n");
msg.append(w.getGeneralDescription() + "\n");
msg.append(w.getBarometer() + " : " + w.getBarometerDescription() + "\n");
msg.append(w.getLowTemperature() + " / " + w.getHighTemperature());
icon.displayMessage("Weather Report", msg.toString(), TrayIcon.MessageType.INFO);
icon.setToolTip(msg.toString());
}
});
} else {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
cancelMI.setEnabled(false);
getWeatherMI.setEnabled(true);
}
});
return;
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
}
}
在 actionPerformed() 方法中,当用户点击“Get Weather”菜单项时,会启动异步Web服务调用。由于 handleResponse() 方法在另一个线程中执行,因此需要使用 SwingUtilities.invokeAndWait() 方法回到GUI线程更新GUI组件。
14.3 使用TCPMon模拟慢速连接
在开发分布式系统时,使用TCPMon模拟慢速连接可更全面地测试应用程序。配置TCPMon模拟慢速连接的步骤如下:
1. 启动TCPMon,设置在端口8080监听,并将所有请求转发到端口8081。
2. 修改 TOMCAT_HOME/conf/server.xml 文件,将Tomcat配置为在端口8081运行:
<Connector port="8081" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
connectionTimeout="20000" disableUploadTimeout="true" />
这样,客户端应用程序仍连接到 http://localhost:8080/weather/weather ,但请求会通过TCPMon转发,从而模拟慢速连接。
graph LR
A[用户点击Get Weather] –> B[启动异步调用]
B –> C{请求完成?}
C – 是 –> D[获取Weather bean]
D –> E[更新GUI显示结果]
C – 否 –> F[取消请求]
F –> G[更新菜单状态]
15. 其他客户端可能性与WSIT项目
15.1 其他客户端可能性
Web服务为客户端应用程序提供了语言和平台的自由。客户端可以是胖客户端或瘦客户端,并以多种方式使用服务中的信息。例如,信息中心应用程序可以集成各种Web服务,查询地图信息、交通信息和天气预报,并将它们叠加在一个视图中。定制应用程序可以显示各种在线账户的信息,如汽车保险信息、账单、银行账户和退休计划等,并自动整合这些信息。
15.2 WSIT项目
Web服务互操作性技术项目(WSIT)是Sun在java.net上发起的项目,旨在为JAX - WS实现许多扩展。该项目正在实现OASIS的许多标准,并测试与Microsoft .NET平台的互操作性。其目标是对最终用户透明, wsimport 将自动生成处理WS - Security、可靠消息传递等功能的类。WS - *系列标准最终将使Web服务成为关键任务应用程序的可靠平台。可通过 https://wsit.dev.java.net/ 跟踪WSIT项目,OASIS的标准可在 http://www.oasis - open.org/committees/tc_cat.php?cat = ws 找到。
Web服务作为一种强大的技术,为不同系统间的通信和数据共享提供了有效的解决方案。通过本文的介绍,希望开发者能够深入理解Web服务的原理和标准,并在Java环境中熟练创建和使用Web服务。
超级会员免费看

被折叠的 条评论
为什么被折叠?



