42、Java Web服务全面解析与实战应用

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服务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值