引入:
我们已经用前2篇的文章用2种方法创建了web service的endpoint并且对外提供服务。现在我们假设要对来往的消息进行处理,而这些处理应该和我们业务代码正交(也就是AOP,比如我们要吧消息写入日志,或者区分是去web service站点的请求消息还是从web service站点返回的相应消息),为了满足这个需求,我们可以利用SOAPHandler来完成。
代码实践:
简单来说,SOAPHandler就是用来对于SOAP消息进行处理的类,为了实现AOP,我们有两种方式来实现对SOAP消息的处理。
一种是实现SOAPHandler<SOAPMessageContext>接口,它会对于整个SOAP消息进行处理。
一种是实现LogicalHandler<LogicalMessageContext>接口,它会对消息的payload 进行处理。
我们就分别写2个处理器,来演示这2种用法。
服务器端:
首先,我们开发一个LogHandler,它会拦截交互的SOAP消息并且打印出消息内容,我们让其采用第一种方式,实现SOAPHandler<SOAPMessageContext>接口:
/** * SOAP Handler可以用来对SOAP消息进行访问。 * 有2种Handler,一种是访问整个消息的, 一种是只访问SOAP消息payload的 * 这里演示的是第一种,它必须实现SOAPHandler接口 */package com.charles.cxfstudy.server.handlers;import java.util.Set;import javax.xml.namespace.QName;import javax.xml.soap.SOAPMessage;import javax.xml.ws.handler.MessageContext;import javax.xml.ws.handler.soap.SOAPHandler;import javax.xml.ws.handler.soap.SOAPMessageContext;/** * 演示访问整个SOAP消息的Handler的用法 * @author charles.wang * */public class LogHandler implements SOAPHandler { /** * 如何去处理SOAP消息的逻辑。 * 这里会先打印当前调用的方法,然后从消息上下文中取出消息,然后写到标准输出流 */ public boolean handleMessage(SOAPMessageContext context) { System.out.println("LogHandler->handleMessage(context) method invoked"); SOAPMessage message = context.getMessage(); try{ message.writeTo(System.out); System.out.println(); }catch(Exception ex){ System.err.print("Exception occured when handling message"); } return true; } /** * 如何去处理错误的SOAP消息 * 这里会先打印当前调用的方法,然后从消息上下文中取出消息,然后写到标准输出流 */ public boolean handleFault(SOAPMessageContext context) { System.out.println("LogHandler->handleFault(context) method invoked"); SOAPMessage message = context.getMessage(); try{ message.writeTo(System.out); System.out.println(); }catch(Exception ex){ System.err.print("Exception occured when handling fault message"); } return true; } /** * 这里没有资源清理的需求,所以我们只打印动作到控制台 */ public void close(MessageContext context) { System.out.println("LogHandler->close(context) method invoked"); } public Set getHeaders() { return null; } }
然后我们再开发AddCustomizedPartHandler,这个Handler会判断这个消息是入站(往web service Endpoint发送的请求消息)消息还是出站(从web service Endpoint返回的消息)消息然后打印出结果,我们采用第二种方式,让其实现 LogicalHandler<LogicalMessageContext>接口:
/** * SOAP Handler可以用来对SOAP消息进行访问。 * 有2种Handler,一种是访问整个消息的, 一种是只访问SOAP消息payload的 * 这里演示的是第二种,它必须实现LogicalHandler接口 */package com.charles.cxfstudy.server.handlers;import javax.xml.ws.handler.LogicalHandler;import javax.xml.ws.handler.LogicalMessageContext;import javax.xml.ws.handler.MessageContext;/** * 演示访问SOAP消息的payload的Handler的用法 * @author charles.wang * */public class AddCustomizedPartHandler implements LogicalHandler { /** * 如何去处理SOAP消息的逻辑,它会去判断这是入站还是出站消息 */ public boolean handleMessage(LogicalMessageContext context) { System.out.println("AddCustomizedPartHandler->handleMessage(context) invoked"); //先判断消息来源是入站还是出站的 //(入站表示是发送到web service站点的消息,出站表示是从web service站点返回的消息) boolean outbound = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); //如果是出站消息 if(outbound){ System.out.println("This is an outbound message"); }else{ System.out.println("This is an inbound message"); } return true; } public boolean handleFault(LogicalMessageContext context) { System.out.println("AddCustomizedPartHandler->handleFault(context) invoked"); return true; } public void close(MessageContext context) { System.out.println("AddCustomizedPartHandler->close(context) invoked"); } }
为了让这2个handler生效,我们必须写一个配置文件,来配置处理器链,这里我们自定义某个handler_chains.xml(可以是任意名字),并且在其中配置了2个处理器,定义他们的顺序和实现类:
AddCustomizedPartHandler com.charles.cxfstudy.server.handlers.AddCustomizedPartHandler LogHandler com.charles.cxfstudy.server.handlers.LogHandler
从这里看出,判断入站出站的处理器在前,打印日志的处理器在后。
为了让我们的Handler链生效到我们的web service的Endpoint,我们必须在服务接口或者服务实现类上用@HandlerChain注解来配置这个Handler链定义文件。这里,我们为前面开发的加法运算的web服务激活处理器,所以在CalcServiceImpl上我们采用了这个注解:
@WebService(endpointInterface="com.charles.cxfstudy.server.services.ICalcService")//这里展示如何用@HandlerChain来声明一组Handler,他们会对指定的web service使用的SOAP消息进行处理,类似AOP@HandlerChain(file="/handler_chains.xml")public class CalcServiceImpl implements ICalcService {...
这时候,打包应用并且部署在服务器上,我们服务器端的代码就完成了。
客户端:
为了演示客户端和服务器端的交互是否会自动触发Handler的执行,我们写一个客户端,首先我们用JDK的wsimport工具来从给定的wsdl文件生成一组客户端的java文件:
这样它会在我们给定目录用给定的包名生成一组文件:
我们把这些文件复制到我们客户端的项目应用中,然后编写测试方法如下:
/** * 客户端测试代码 */package com.charles.cxfclient;import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;/** * @author charles.wang * */public class MainTest { public static void main(String [] args){ JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); factory.setServiceClass(ICalcService.class); factory.setAddress("http://localhost:8080/cxf_jaxws_server/services/calc"); //调用业务方法 ICalcService service = (ICalcService) factory.create(); int a = 3,b=5; System.out.println("调用CalcService进行加法计算,2个运算子分别是:"+a+","+b); System.out.println("加法运算结果为:" + service.calcSum(a, b)); }}
执行,它显然会打印出执行结果,不过这个执行的结果运算是通过 web service的调用完成的。
我们来看服务器的日志,如下图:
显然,这两个Handler都被正确的调用了(可以比较我们的Handler的代码),比如AddCustomizedPartHandler会正确的识别这是inbound 还是outbound消息,比如LogHandler能吧inbound消息(就是图片中内含<a>3</a><b>5</b>的)和outbound消息(就是图片中内涵<return>8</return>的)都打印出来,所以证明我们开发的Handler是正确的。
当然了,Handler还可以做很多更高级别的功能,比如检验cookie,持久化消息,添加自定义头等,有待我们发觉,但是代码的结构大体和我们例子相似。