目录
CXF 拦截器
1、拦截器的目的是为了在 webService 请求过程中能动态操作请求和响应数据。Java JDK 原生的 JWS 是没有拦截器的
2、拦截器分类:
1)按所处的位置分:服务器端拦截器、客户端拦截器
2)按消息的方向分:入拦截器、出拦截器
3)按定义者分:系统拦截器、自定义拦截器(服务端与客户端拦截器是无关的,没有联系的,各自自由设置拦截器)
3、拦截器主要 API 分析:
org.apache.cxf.interceptor.Interceptor :整个拦截器体系的超类(接口),3.3.0 版本时一共有 126 的子孙类/接口
org.apache.cxf.phase.AbstractPhaseInterceptor :阶段拦截器,自定义拦截器可以实现/继承它org.apache.cxf.interceptor.LoggingInInterceptor :系统日志入拦截器类
org.apache.cxf.interceptor.LoggingOutInterceptor :系统日志出拦截器类
日志拦截器
1、使用日志拦截器,实现日志记录,这些日志打印在控制台上,可以清晰的看到请求与返回的 saop 消息。
2、主要使用 API 如下:
org.apache.cxf.interceptor.LoggingInInterceptor :系统日志入拦截器类
org.apache.cxf.interceptor.LoggingOutInterceptor :系统日志出拦截器类
3、本示例将在《Apache CXF 入门第一个示例》代码的基础上进行追加改写,只对需要修改的地方进行粘贴。
服务端
1、服务端只需要对 Web_Service 启动类进行修改,用于添加日志拦截器:
2、内容修改如下,其中核心代码只有几行,大部分的是注释:
import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.message.Message;
import javax.xml.ws.Endpoint;
import java.util.List;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/1/25 0025.
* webServer 启动类
*/
public class Web_Service {
//日志记录器
public static final Logger logger = Logger.getGlobal();
public static void main(String[] args) {
/**webService服务器提供给客户端访问的地址
* 192.168.1.20 为服务器 ip、3333为指定的端口号、web_server 为应用名称、student 为标识
* 这个地址符合 http 地址即可,为了看起来更像是 web访问,所以才写了应用名,即使是 http://192.168.1.20:3333/xxx 也是可以的
*/
String wsUrl = "http://192.168.1.20:3333/web_server/student";
/**
* javax.xml.ws.Endpoint 表示一个 web service 终端,这是一个抽象类,其中提供了静态方法可以直接调用
* Endpoint publish(String address, Object implementor)
* address:传输协议的 url 地址;
* implementor(实现者):web service 终端的实现类,因为一个 ws 接口可能会有多个实现类
*/
Endpoint endpoint = Endpoint.publish(wsUrl, new StudentServiceImpl());
/**
* javax.xml.ws.Endpoint 是一个抽象类,因为导入了 CXF 的 jar 包,所以 endpoint 的类实质是-
* org.apache.cxf.jaxws.EndpointImpl 类型,这里进行强转,因为拦截器 JWS 没有,CXF 才有。
*/
EndpointImpl endpointImpl = (EndpointImpl) endpoint;
/**
* 添加系统日志出拦截器类
* getOutInterceptors 获取 "出拦截器集合",使用 add 方法将需要添加的拦截器进行添加,这样就会起作用
* org.apache.cxf.interceptor.LoggingOutInterceptor :这是 cxf 提供好的日志出拦截器
*/
List<Interceptor<? extends Message>> outInterceptorList = endpointImpl.getOutInterceptors();
outInterceptorList.add(new LoggingOutInterceptor());
/**
* 添加系统日志入拦截器类
* getInInterceptors 获取 "入拦截器集合",使用 add 方法将需要添加的拦截器进行添加,这样就会起作用
* org.apache.cxf.interceptor.LoggingInInterceptor : cxf 提供好的日志入拦截器
*/
List<Interceptor<? extends Message>> inInterceptorList = endpointImpl.getInInterceptors();
inInterceptorList.add(new LoggingInInterceptor());
logger.info("webService 服务启动,等待客户端请求...");
}
}
3、服务端添加了上面日志拦截器后,此时从浏览器访问 服务端 wsdl,或者客户端访问(与客户端是否有没有设置拦截器无关)服务接口,服务端控制台都可以看到日志信息。
客户端
1、客户端与服务端拦截器无关,先为客户端也追加上日志拦截,同样是在《Apache CXF 入门第一个示例》客户端的基础上追加。
2、之前客户端是没有导入CXF 的 jar 包,下载因为要使用 CXF 的拦截器,所以必须要导入才行,同样导入的包和服务端时一样,截图如下:
3、同样只需要修改原来的 Web_service 调用测试类,在调用服务端接口前添加日志拦截器,必须在调用服务端方法前添加。
import com.lct.web_service.Student;
import com.lct.web_service.StudentService;
import com.lct.web_service.StudentServiceImplService;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/1/25 0025.
* CXF wsdl2java 工具常用指令:.\wsdl2java -d E:/wmx/webservice/cxf -encoding UTF-8 -verbose wsdlurl
*/
public class Web_service {
//日志记录器
public static final Logger logger = Logger.getGlobal();
public static void main(String[] args) {
/**1、创建实现类对象
* */
StudentServiceImplService studentServiceImplService = new StudentServiceImplService();
/** 2、获取服务接口实例
* 这些代码只能看着源码跟着感觉来写,因为代码完全是人家写的,对方通常也不会提供什么使用文档的
*/
StudentService studentService = studentServiceImplService.getStudentServiceImplPort();
/**
* org.apache.cxf.frontend.ClientProxy :客户端代理
* getClient(Object o) 返回 org.apache.cxf.endpoint.Client 发送请求的客户端对象,参数传入服务端接口对象
*/
Client client = ClientProxy.getClient(studentService);
/**
* 添加拦截器代码如同服务端一样,都是先获取拦截器列表,然后添加需要使用的拦截器
*/
client.getInInterceptors().add(new LoggingInInterceptor());//添加入拦截器
client.getOutInterceptors().add(new LoggingOutInterceptor());//添加出拦截器
/**
*3、有了服务接口实例,就可以调用其中的方法了,这里返回值也是对方使用的对象
*/
Student student = studentService.getStudentById(11);//调用服务
System.out.println(student);
}
}
拦截器测试
1、之前服务端添加拦截器之后,通过浏览器访问wsdl地址,以及客户端调用,都已经能成功记录日志,现在运行客户端调用服务端接口,查看客户端日志。
2、日志拦截器成功,可以看到完整的请求与响应的 saop 消息。
自定义拦截器
1、自定义拦截器可以继承 org.apache.cxf.phase.AbstractPhaseInterceptor 抽象类,然后实现其中的 handleMessage 方法。
2、本示例演示使用自定义拦截器,实现用户名与密码的检验,因为实际中一些 webService 对外提供服务,客户端调用时需要提供正确的账号与密码才能调用成功。
1)客户端需要设置 out(出) 拦截器,在 soap 消息中携带账号与密码过去
2)服务端需要设置 in(入) 拦截器,解析 soap 消息中的账号与密码
客户端
1、仍然在 《Apache CXF 入门第一个示例》中的客户端基础上进行改写,这里先写客户端,再写服务端,这样可以看日志更加清晰。
2、自定义拦截器 WsUserInterceptor 内容如下:
import com.sun.org.apache.xml.internal.utils.DOMHelper;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.namespace.QName;
import java.util.List;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/2/18 0018.
* 自定义 webService 拦截器
*/
public class WsUserInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
private static final Logger logger = Logger.getGlobal();//日志记录器
/**
* webService 需要传递的账号与密码
*/
private String name;
private String password;
/**
* 使用构造器传入参数,构造器中必须使用 super 设置拦截器发生的时刻/阶段
* org.apache.cxf.phase.Phase 中提供了大量的常量表示拦截器发生的时刻
*
* @param name
* @param password
*/
public WsUserInterceptor(String name, String password) {
super(Phase.PRE_PROTOCOL);//协议前进行拦截
this.name = name;
this.password = password;
}
/**
* 默认情况下,客户端给服务端请求的 soap 消息如下:
* <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:getStudentByIdResponse xmlns:ns2="http://web_service.lct.com/"><getStudentById><birthday>2019-02-18T16:19:12.102+08:00</birthday><punishStatus>5</punishStatus><sID>11</sID><sName>0a90959c-22ee-497b-b60d-0550b1f2adf7</sName></getStudentById></ns2:getStudentByIdResponse></soap:Body></soap:Envelope>
* 其中的 <body></body>部分为请求的主体,现在为 Envelope 设置头信息 <head></head>,将账户密码信息放在 <head></head>中,<head>与 body 同级
* <head></head>中的内容设置为账户与密码元素,如:
* <head><userInfo><name>admin</name><password>123456</password></userInfo></head>,其中的 userInfo、name、password 等元素名称必须和服务端约定的一致,
* 因为服务端还需要根据这些名称解析出元素的值
*
* @param message :在客户端请求服务端前,先进入此方法,然后将账户密码信息添加到 soap 消息头中
* @throws Fault
*/
@Override
public void handleMessage(SoapMessage message) throws Fault {
/**
* com.sun.org.apache.xml.internal.utils.DOMHelper
* org.w3c.dom.Document
* org.w3c.dom.Element
*/
Document document = DOMHelper.createDocument();//创建 w3c 的文档对象
Element userInfoEle = document.createElement("userInfo");//创建标签元素
Element nameEle = document.createElement("name");//创建标签元素
Element passwordEle = document.createElement("password");//创建标签元素
nameEle.setTextContent(this.name);//设置标签元素内容
passwordEle.setTextContent(this.password);//设置标签元素内容
userInfoEle.appendChild(nameEle);//添加子元素
userInfoEle.appendChild(passwordEle);//添加子元素
/**
* org.apache.cxf.headers.Header
* 最后将创建好的 soap 头信息添加到 SoapMessage 中
*/
List<Header> headerList = message.getHeaders();
/**QName构造器中的值与后面的 userInfoEle 元素的标签名保持一致*/
headerList.add(new Header(new QName("userInfo"), userInfoEle));
logger.info("客户端 webServic 自定义出拦截器执行完毕....");
}
}
3、Web_service类内容如下:
import com.lct.web_service.Student;
import com.lct.web_service.StudentService;
import com.lct.web_service.StudentServiceImplService;
import com.lct.web_service.interceptors.WsUserInterceptor;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/1/25 0025.
* CXF wsdl2java 工具常用指令:.\wsdl2java -d E:/wmx/webservice/cxf -encoding UTF-8 -verbose wsdlurl
*/
public class Web_service {
//日志记录器
public static final Logger logger = Logger.getGlobal();
public static void main(String[] args) {
/**1、创建实现类对象
* */
StudentServiceImplService studentServiceImplService = new StudentServiceImplService();
/** 2、获取服务接口实例
* 这些代码只能看着源码跟着感觉来写,因为代码完全是人家写的,对方通常也不会提供什么使用文档的
*/
StudentService studentService = studentServiceImplService.getStudentServiceImplPort();
/**
* org.apache.cxf.frontend.ClientProxy :客户端代理
* getClient(Object o) 返回 org.apache.cxf.endpoint.Client 发送请求的客户端对象,参数传入服务端接口对象
*/
Client client = ClientProxy.getClient(studentService);
/**
* 添加拦截器代码如同服务端一样,都是先获取拦截器列表,然后添加需要使用的拦截器
*/
//添加日志出拦截器,这在调试阶段可以清楚的看到请求的 soap 消息,部署阶段可以删除掉
client.getOutInterceptors().add(new LoggingOutInterceptor());
//添加自定义出拦截器,设置请求的账户与密码,为了演示简单,所以直接写死,实际中应该提高配置文件进行配置
client.getOutInterceptors().add(new WsUserInterceptor("admin", "123456"));
/**
*3、有了服务接口实例,就可以调用其中的方法了,这里返回值也是对方使用的对象
*/
Student student = studentService.getStudentById(11);//调用服务
System.out.println(student);
}
}
4、已经说过客户端与服务端的拦截器是相互分离的,现在即使服务端还没有设置自定义入拦截器,客户端也可以运行。
服务端
1、仍然在 《Apache CXF 入门第一个示例》中的服务端基础上进行改写。
2、自定义拦截器 WsUserInterceptor 内容如下:
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Element;
import javax.xml.namespace.QName;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/2/18 0018.
* 自定义拦截器
*/
public class WsUserInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
private static final Logger logger = Logger.getGlobal();//日志记录器
/**
* 构造器中必须使用 super 设置拦截器发生的时刻/阶段
* org.apache.cxf.phase.Phase 中提供了大量的常量表示拦截器发生的时刻
*/
public WsUserInterceptor() {
super(Phase.PRE_PROTOCOL);//协议前进行拦截
}
/**
* 客户端传来的 soap 消息先进入拦截器这里进行处理,客户端的账目与密码消息放在 soap 的消息头<head></head>中,类似如下:
* <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><userInfo><name>admin</name><password>123456</password></userInfo></soap:Header><soap:Body><ns2:getStudentById xmlns:ns2="http://web_service.lct.com/"><sId>11</sId></ns2:getStudentById></soap:Body></soap:Envelope>
* 现在只需要解析其中的 <head></head>标签,如果解析验证成功,则放行,否则这里直接抛出异常,服务端不会再往后运行,客户端也会跟着抛出异常,得不到正确结果
*
* @param message
* @throws Fault
*/
@Override
public void handleMessage(SoapMessage message) throws Fault {
/**org.apache.cxf.headers.Header
* QName :xml 限定名称,客户端设置头信息时,必须与服务器保持一致,否则这里返回的 header 为null,则永远通不过的
*/
Header header = message.getHeader(new QName("userInfo"));
if (header != null) {
Element rootEle = (Element) header.getObject();//获取根元素,即 <userInfo>标签
String name = rootEle.getElementsByTagName("name").item(0).getTextContent();//获取元素值
String password = rootEle.getElementsByTagName("password").item(0).getTextContent();//获取元素值
/**为了演示简单,直接写死了,实际中建议放在配置文件中提供配置*/
if ("admin".equals(name) && "123456".equals(password)) {
logger.info("webService 服务端自定义拦截器验证通过....");
return;//放行
}
}
/**
* 验证失败,客户端没有提供账号密码、或者提供账号密码错误
*/
logger.info("webService 服务端自定义拦截器验证失败....");
throw new Fault(new RuntimeException("请提供正确的用户名和密码!格式:<Header><userInfo><name>xxx</name><password>xxx</password></userInfo></Header>"));
}
}
3、发布服务类 Web_Service 内容如下:
import com.lct.interceptors.WsUserInterceptor;
import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.message.Message;
import javax.xml.ws.Endpoint;
import java.util.List;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/1/25 0025.
* webServer 启动类
*/
public class Web_Service {
//日志记录器
public static final Logger logger = Logger.getGlobal();
public static void main(String[] args) {
/**webService服务器提供给客户端访问的地址
* 192.168.1.20 为服务器 ip、3333为指定的端口号、web_server 为应用名称、student 为标识
* 这个地址符合 http 地址即可,为了看起来更像是 web访问,所以才写了应用名,即使是 http://192.168.1.20:3333/xxx 也是可以的
*/
String wsUrl = "http://192.168.1.20:3333/web_server/student";
/**
* javax.xml.ws.Endpoint 表示一个 web service 终端,这是一个抽象类,其中提供了静态方法可以直接调用
* Endpoint publish(String address, Object implementor)
* address:传输协议的 url 地址;
* implementor(实现者):web service 终端的实现类,因为一个 ws 接口可能会有多个实现类
*/
Endpoint endpoint = Endpoint.publish(wsUrl, new StudentServiceImpl());
/**
* javax.xml.ws.Endpoint 是一个抽象类,因为导入了 CXF 的 jar 包,所以 endpoint 的类实质是-
* org.apache.cxf.jaxws.EndpointImpl 类型,这里进行强转,因为拦截器 JWS 没有,CXF 才有。
*/
EndpointImpl endpointImpl = (EndpointImpl) endpoint;
/**
* 添加拦截器
* getInInterceptors 获取 "入拦截器集合",使用 add 方法将需要添加的拦截器进行添加,这样就会起作用
* org.apache.cxf.interceptor.LoggingInInterceptor : cxf 提供好的日志入拦截器
* 日志拦截器在调试阶段很有用,部署阶段可以自行删除
*/
List<Interceptor<? extends Message>> inInterceptorList = endpointImpl.getInInterceptors();
inInterceptorList.add(new LoggingInInterceptor());//添加系统日志入拦截器类
inInterceptorList.add(new WsUserInterceptor());//添加自定义拦截器
logger.info("webService 服务启动,等待客户端请求...");
}
}
测试运行
1、如下所示为账户密码正确时的客户端与服务端:
2、如下所示为账户密码错误时的客户端与服务端: