Apache CXF 拦截器示例

 

目录

CXF 拦截器

日志拦截器

服务端

客户端

拦截器测试

自定义拦截器

客户端

服务端

测试运行


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、如下所示为账户密码错误时的客户端与服务端:

猜你喜欢

转载自blog.csdn.net/wangmx1993328/article/details/87616841