JAX-WS 快速入门
2) JAX-WS 提供了一套标准(基于标签模式)来简化Web服务和客户端的开发。
3) JAX-WS 模式取代了JAX-RPC(Remote Procedure Call)的远程方法调用模式。
4) JAX-WS 模式是策略编程模式,依赖于Java(SE&EE)平台。
5) JAX-WS 标准的实现者在开发Web服务和客户端方面上提供了很多增强。
使用JAX-WS标准开发的Web服务和客户端能更好的依赖于以Java为平台的应用。比如,JAX-WS充分的利用了Java的动态Proxy模式,增强了JAX-RPC。
JAX-WS使用标注指明Java类为Web服务。该功能依赖于Java EE5以上平台。
例如:
@WebService
public class QuoteBean implements StockQuote {
public float getQuote(String sym) { ... }
}
@ WebService标注告诉服务器将该类的所有public方法暴露并将其作为一个WebService。使用标注有利于团队开发,你不必定义部署描述符来指定Web服务(比如JAX-RPC),并且可以并行开发service和服务所需的元数据。
异步调用指客户端与服务器的异步通信。JAX-WS支持同步和异步调用。JAX-WS采用轮询机制(polling)和异步回调机制(Callback)。使用polling模式,客户端先发送一个请求,获得一个response对象,然后客户端使用reponse对象去判断服务器是否已经响应了请求;如果使用Callback模式,客户端提供一个回调Handler去接受和处理发送回来的response对象。采用异步调用后客户端不必等待返回服务器的response而可以继续处理本地的业务,提高了系统效率。
下面有个异步调用的示例:(一个Service接口中应该包括处理同步和异步请求的方法)
@WebService
public interface CreditRatingService {
// 处理同步请求的方法
Score getCreditScore(Customer customer);
// 采用polling模式来处理异步请求的方法
Response<Score> getCreditScoreAsync(Customer customer);
// 采用callback模式来处理异步请求的方法
Future<?> getCreditScoreAsync(Customer customer, AsyncHandler<Score> handler);
}
下面是回调模式的处理Handler
// 客户端创建Service实例
CreditRatingService svc = ...;
// 客户端实现Handler
Future<?> invocation = svc.getCreditScoreAsync(customerFred,
new AsyncHandler<Score>() {
public void handleResponse (Response<Score> response) {
Score score = response.get();
// do work here...
}
}
);
下面是Polling模式的处理Handler
CreditRatingService svc = ...;
Response<Score> response = svc.getCreditScoreAsync(customerFred);
while (!response.isDone()) {
// 等待服务器响应时做
System.out.println("is not down");
}
Score score = response.get();
资源注入是指Java EE 5支持在运行时创建和初始化通用资源。JAX-WS使用这种技术将资源的创建任务从Service服务中分离出来并分配给相应的容器(Servlet容器)。
@WebService
public class MyService {
@Resource
private WebServiceContext ctx;
public String echo (String input) {
}
}
将@WebService标注放到服务端点的实现类上,我们就可以请求资源注入,然后通过javax.xml.ws.WebServiceContext接口获得指定的资源。
使用JAXB 2.0 API和工具完成Java对象和XML文档的相互转换即数据绑定过程。
Dispatch(javax.xml.ws.Dispatch)客户端使用JAX-WS的动态调用API,Dispatch客户端以Payload和Message模式发送XML消息。使用Payload模式时Dispatch客户端仅负责提供<soap:Body>的内容,JAX-WS会添加<soap:Envelope>和 <soap:Header>元素;如果使用Message模式,则Dispatch客户端负责提供整个SOAP外壳(包括<soap:Envelope>,<soap:Header>, 和 <soap:Body>),JAX-WS不会在SOAP消息中添加任何内容。Dispatch客户端支持callback和polling异步调用模式。
静态客户端也叫Proxy代理客户端,代理客户端如果要调用远程Service则必须为其提供一个Service Endpoint interface (SEI),客户端使用SEI进行远程调用,可用JAX-WS命令工具生成实现类。
使用JAX-WS,我们可以将附件以二进制base64的形式和Web服务请求一起发送给服务器,JAX-WS使用MTOM优化了对二进制数据的传输。
wsgen用于服务器端,使用一个class生成相应的Service和WSDL文件,然后部署去服务;wsimport主要用于客户端,根据WSDL文件生成SEI Java文件以及用于匹配XML的JAXB2.0的类文件。Wsimport生成的是客户端动态Proxy。
1) Java Architecture for XMLBinding (JAXB) 2.0数据绑定技术,是一套标准API,用于映射Java类和XML Document,支持运行时绑定。
2) JAXB是JAX-WS 2.0默认使用的数据绑定技术。
3) Axis2提供支持JAXB 2.0标准API。
4) JAXB的结构模型
使用已有的JavaBean去开发JAX-WS服务,添加@WebService标注将Bean定义为一个Web服务,JavaBean可以有一个SEI(serviceendpoint interface)即一个服务端接口,但是这不是必须的,如果没有则可看做是内部SEI。然后准备好Web服务所需的所有class和文件,将应用发布到Axis2。在这里,我们不必去开发一个WSDL文件,因为标注的使用会提供所有的WSDL信息来配置服务端或者是客户端,但做项目时最好还是有WSDL文件。下面是我采用的开发步骤。
1. 开发一个SEI和实现类
有两种方式来开发:标准的JavaBean SEI和Provider接口(XML消息级别处理)。
标准的JavaBean SEI使用@WebService,Provider接口使用@WebServiceProvider。
如果JavaBean Service实现类使用SEI接口,则实现类必须通过endpointInterface引用SEI接口,如果实现类没有使用SEI接口,则必须将在内容来定义和描述SEI。示例如下:
import javax.jws.WebMethod; import javax.jws.WebService; import javax.jws.soap.SOAPBinding; import javax.jws.soap.SOAPBinding.Style; @WebService @SOAPBinding(style = Style.RPC) public interface AuthHello { @WebMethod public String say(String name); } |
import javax.jws.WebService; @WebService(endpointInterface = "my.ws.fromjava.AuthHello") public class AuthHelloImpl implements AuthHello { @Override public String say(String name) { return "hello: " + name; } } |
2. 当使用从下向上,从SEI实现类开始开发时,我们使用wsgen命令行工具来生成部分服务组件。这些组件有:
注意:当使用从下向上从现有Bean开发Web服务时,我们不必要去生成一个WSDL文件,JAX-WS的标注已经提供了所有WSDL信息来配置我们的Service。
如果想发布实现类从父类集成的方法,则需要将父类用@WebService标注上。
3. 准备打包和发布服务。
1. 使用已有的WSDL文件去开发JAX-WS服务,使用wsimport工具。我们使用wsimport命令行工具来生成部分服务组件。这些组件有:
wsimport -keep -verbose wsdl_URL |
-keep:不允许删除生成的文件
-verbose:列出生成的文件
2. 为生成的SEI提供自行开发的实现类。
通过导入JAX-WS RIJar包的方式,我们可以在Servlet容器中使用监听器com.sun.xml.ws.transport.http.servlet.WSServletContextListener和sun-jaxws.xml来部署Web服务。
这里我们使用现有的ApacheAxis2框架来发布JAX-WS服务。比如打包为AAR文件然后上传到tomcat。具体方法可参照Axis2文章。
1. JAX-WS支持两种方式开发客户端:Dispatchclient API、the Dynamic Proxy client API。
2. Dispatch client
Web服务器与客户端是通过XML消息进行通信的,很多时候,JAX-WS APIs隐藏了XML消息处理的底层细节。但是有时我们想在XML消息级别上开发,这时我们可以使用Dispatch client APIs。
客户端创建dispatch对象时可以传入以下几种实例类型:
例如使用SOAPMessage:
QName serviceName = new QName(targetNamespace, _serviceName); QName portName = new QName(targetNamespace, _portName); javax.xml.ws.Service service = Service.create(serviceName); service.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING, "http://localhost:8080/Test/TestWebService?wsdl"); Dispatch<SOAPMessage> dispatch = service.createDispatch(portName,SOAPMessage.class, Service.Mode.MESSAGE); BindingProvider bp = (BindingProvider) dispatch; Map<String, Object> rc = bp.getRequestContext(); rc.put(BindingProvider.SOAPACTION_USE_PROPERTY, Boolean.TRUE); rc.put(BindingProvider.SOAPACTION_URI_PROPERTY, OPER_NAME); MessageFactory factory = ((SOAPBinding) bp.getBinding()).getMessageFactory(); SOAPMessage request = factory.createMessage(); ...... SOAPMessage reply = dispatch.invoke(request); ...... |
例如使用Sourse:
Dispatch<Source> dispatch = … create a Dispatch<Source> Source request = … create a Source object Source response = dispatch.invoke(request); |
dispatch.invoke的调用方式:
l 使用invoke同步调用;
l 使用带callback或者polling的invokeAsync异步调用。
3. Dynamic Proxy client
动态代理客户端必须先有一个SEI(从WSDL生成),然后Proxy基于SEI远程调用Web服务。动态Proxy类似RPC编程中的Stub(存根类),都是从WSDL生成的,但是Stub是静态的方式调用服务,而动态Proxy是基于JDK的动态代理模式、动态调用的方式。
dispatch.invoke的调用方式:
对于JAX-WS静态客户端编程模型是所谓的动态代理客户端。
创建动态代理客户端的wsimport命令行是:
wsimport -keep –b binding.xml -d bin -s src wsdl/TestService.wsdl |
以上使用binding.xml生成异步调用动态Proxy。
我们可以使用JAX-WS提供的动态调用接口Dispatch client API来开发客户端调用。
开发步骤是:
1. 选择动态客户端发送消息的模式:payload或message。
2. 创建Service实例,添加port。
service.addPort(portName,SOAPBinding.SOAP11HTTP_BINDING,
"http://localhost:8080/Test/TestWebService?wsdl");
3. 创建Dispatch对象
Dispatch<SOAPMessage> dispatch =
service.createDispatch(portName,SOAPMessage.class, Service.Mode.MESSAGE);
4. 配置request上下文属性
BindingProvider bp = (BindingProvider)dispatch;
Map<String, Object> rc =bp.getRequestContext();
rc.put(BindingProvider.SOAPACTION_USE_PROPERTY,Boolean.TRUE);
rc.put(BindingProvider.SOAPACTION_URI_PROPERTY,OPER_NAME);
5. 组装请求消息
6. 同步或异步发送消息
7. 处理相应消息
String endpointUrl = ...;
QName serviceName = new QName("http://org/apache/ws/axis2/sample/echo/", "EchoService"); QName portName = new QName("http://org/apache/ws/axis2/sample/echo/", "EchoServicePort"); /**创建Service实例,添加port **/ Service service = Service.create(serviceName); service.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING, endpointUrl);
/** Create a Dispatch instance from a service.**/ Dispatch<SOAPMessage> dispatch = service.createDispatch(portName, SOAPMessage.class, Service.Mode.MESSAGE); /** Create SOAPMessage request. **/ // compose a request message MessageFactory mf = // Create a message. This example works with the SOAPPART. SOAPMessage request = mf.createMessage(); SOAPPart part = request.getSOAPPart(); // Obtain the SOAPEnvelope and header and body elements. SOAPEnvelope env = part.getEnvelope(); SOAPHeader header = env.getHeader(); SOAPBody body = env.getBody(); // Construct the message payload. SOAPElement operation = body.addChildElement("invoke", "ns1", "http://org/apache/ws/axis2/sample/echo/"); SOAPElement value = operation.addChildElement("arg0"); value.addTextNode("ping"); request.saveChanges(); /** Invoke the service endpoint. **/ SOAPMessage response = dispatch.invoke(request);
/** Process the response. **/ |
默认情况下,异步客户端调用并没有通道上消息交换模式的异步行为,只是编程模式是异步的,客户端和服务器通信的request和response的消息交换不是异步的。
为了使用异步消息交换,必须在request上下文中设置属性org.apache.axis2.jaxws.use.async.mep为true。
Map<String, Object> rc = ((BindingProvider) port).getRequestContext(); rc.put("org.apache.axis2.jaxws.use.async.mep", Boolean.TRUE); |
有了异步交换,request和response消息会添加WS-Addressing头 headers,它会提供额外的路由信息。
有了异步交换,response被传到一个异步Listener,然后再将response传递给客户端。此时不存在给客户端传递停止监听response的超时设置信息,我们只能通过cancel方法来强制客户端停止等待response。
使用异步交换后,对于polling和callback异步调用方式,最后需要通过在客户端调用cancel方法来取消response的等待,当然这样不会影响到服务器端对request的处理。
与JAX-RPC编程模式一样,JAX-WS也为应用程序提供了Handler机制来处理Message消息输入输出流(SOAP报文)。我们可以在JAX-WS运行时环境中添加Handlers而对请求和响应消息做额外的处理,比如安全和日志。JAX-WS中有两种使用Handler的方式,logical handler和protocol handler。
1. logical handler
独立于协议的逻辑Handler,能够获得流中的XML消息。需要实现接口javax.xml.ws.handler.LogicalHandler。一个逻辑Handler会接收LogicalMessageContext对象,logicalhandler基于SOAP 和 XML/HTTP-based。
2. protocol handler
即协议Handler仅限于基于SOAP协议的配置,使用该Handler时必须实现接口javax.xml.ws.handler.soap.SOAPHandler,协议Handler会接收SOAPMessage 对象去读取消息数据。
JAX-WS运行时服务器端和客户端的Handler程序类没有区别。在服务器和客户端必须配置Handler,并且实现Handler内的特定业务逻辑。
为了使用Handler,需要在SEI或者实现类中添加@HandlerChain标注,然后添加一个对Handler描述的XML配置文件。对于客户端,我们也可以结合使用Bingding API以编程的方式来配置Handler。
@HandlerChain(file="../../common/handlers/myhandlers.xml") 或者 @HandlerChain(file="http://foo.com/myhandlers.xml") |
<?xml version="1.0" encoding="UTF-8"?> <jws:handler-chains xmlns:jws="http://java.sun.com/xml/ns/javaee"> <!—注意: * 表示一个通配符 --> <jws:handler-chain name="MyHandlerChain"> <jws:protocol-bindings>##SOAP11_HTTP ##ANOTHER_BINDING</jws:protocol-bindings> <jws:port-name-pattern xmlns:ns1="http://handlersample.samples.apache.org/">ns1:MySampl*</jws:port-name-pattern> <jws:service-name-pattern xmlns:ns1="http://handlersample.samples.apache.org/">ns1:*</jws:service-name-pattern> <jws:handler> <jws:handler-class> samples.handlersample.SampleLogicalHandler</jws:handler-class> </jws:handler> <jws:handler> <jws:handler-class> samples.handlersample.SampleProtocolHandler2</jws:handler-class> </jws:handler> <jws:handler> <jws:handler-class> samples.handlersample.SampleLogicalHandler</jws:handler-class> </jws:handler> <jws:handler> <jws:handler-class> samples.handlersample.SampleProtocolHandler2</jws:handler-class> </jws:handler> </jws:handler-chain> </jws:handler-chains> |
samples.handlersample; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPMessageContext; public class SampleProtocolHandler implements javax.xml.ws.handler.soap.SOAPHandler<SOAPMessageContext> { public void close(MessageContext messagecontext) { } public Set<QName> getHeaders() { return null; } public boolean handleFault(SOAPMessageContext messagecontext) { return true; } public boolean handleMessage(SOAPMessageContext messagecontext) { Boolean outbound = (Boolean) messagecontext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); if (outbound.booleanValue()) { // 处理输出流 } else(!outbound.booleanValue()) { // 处理输入流 } return true; } } |
在Server端可以使用HTTPSession管理来维护用户状态信息,通过最小的信息返回给用户从而跟踪回话。我们可以使用session cookies 或 URL 重写来实现HTTP Session管理。
浏览器之间,应用服务器和应用程序之间的交互,对用户和应用程序都是透明的。应用程序和用户通常不知道服务器提供的会话标识符。
1. session cookies
cookie是个客户端小文件负责维护JSESSIONID,发送请求时cookie用来匹配服务器端的session信息。从JAX-WS发送的请求,sessionID作为请求头的一部分传输,服务器根据特定的请求session ID匹配session信息,JAX-WS应用程序从HTTP响应头中获取Session ID,然后等下一请求时再发送给服务器。
2. URL 重写
URL重写是指在URL重定向时将Seesion ID作为编码后的参数存储在URL中一起发送给服务器。这种编码的URL用于同一服务器的并发请求。使用步骤如下:
1) 配置服务器使之能够跟踪回话。
2) 在客户端通过设置JAX-WS属性javax.xml.ws.session.maintain为true来提供Session管理支持:
Map<String, Object> rc = ((BindingProvider) port).getRequestContext(); rc.put(BindingProvider.SESSION_MAINTAIN_PROPERTY, Boolean.TRUE); |
JAX-WS应用可以将二进制(base64or hexBinary编码)数据放在XML文档中发送。MTOM仅支持xs:base64Binary数据类型,MTOM默认下是不启动的。JAX-WS应用需要在服务器、客户端分别配置以提供MTOM支持。在服务器端仅可配置JavaBeans endpoint ,而不能配置provider-based endpoint。
1. 在服务端启动MTOM,在实现类上添加@BindingType标注以标示支持的数据绑定类型,javax.xml.ws.SOAPBinding类定义了两个常量SOAP11HTTP_MTOM_BINDING和 SOAP12HTTP_MTOM_BINDING。
// for SOAP version 1.1 @BindingType(value = SOAPBinding.SOAP11HTTP_MTOM_BINDING) // for SOAP version 1.2 @BindingType(value = SOAPBinding.SOAP12HTTP_MTOM_BINDING) |
2. 在客户端使用javax.xml.ws.soap.SOAPBinding client-side API来启动MTOM,客户端以两种方式启动MTOM向服务器发送二进制消息:(SOAP1.1)
l Dispatch client:
// 方法1 SOAPBinding binding = (SOAPBinding)dispatch.getBinding(); binding.setMTOMEnabled(true); // 方法2 Service svc = Service.create(serviceName); svc.addPort(portName,SOAPBinding.SOAP11HTTP_MTOM_BINDING,endpointUrl); |
l Dynamic Proxy client:
// Create a BindingProvider bp from a proxy port. Service svc = Service.create(serviceName); MtomSample proxy = svc.getPort(portName, MtomSample.class); BindingProvider bp = (BindingProvider) proxy; //Enable MTOM SOAPBinding binding = (SOAPBinding) bp.getBinding(); binding.setMTOMEnabled(true); |