首发于Java开发

CXF使用及常见报错

Apache CXF 是一个开源的 WebService 框架,CXF可以用来构建和开发 WebService,这些服务可以支持多种协议,比如:SOAP、POST/HTTP、HTTP ,CXF 大大简化了WebService并且可以天然地和 Spring 进行无缝集成。CXF是 Celtrix (ESB框架)和 XFire(webserivice) 合并而成,核心是org.apache.cxf.Bus(总线),类似于Spring的 ApplicationContext,CXF默认是依赖于Spring的,另 CXF 发行包中的jar,如果全部放到lib中,需要 JDK1.6 及以上,否则会报JAX-WS版本不一致的问题。CXF 内置了Jetty服务器 ,它是servlet容器。

这段话是摘自别人的,这里划重点需要jdk1.6以上,一定要是jdk,千万别搞成了jre。

CXF框架特点: 与spring相结合,支持注解的方式来发布webservice - 能够根据需求添加拦截器,例如后文会提到的输入拦截器、输出拦截器 :输入日志信息拦截器、输出日志拦截器、用户权限认证的拦截器

cxf主要用来调用webService接口,以笔者目前的工作经验来看cxf不支持rpc模式发布的webservice,有个同事是写dephi的,他用dephi写了个webservice,我这边使用cxf调不通,最后走soap协议调的,下一篇文章会写到webservice三要素及要点。 这里先简要介绍下webservice: - webservice是一种跨平台,跨语言的规范,用于不同平台,不同语言开发的应用之间的交互。利用webservice发布的接口无需关心对方什么平台上开发以及使用何种语言开发。程序员只关心调用对方发布webservice接口方法和参数。开发人员一般就是在基于某种语言开发webservice接口,以及调用webservice接口;通常情况下开发语言都有自己的webservice实现框架。而CXF是java开发webService的一种实现框架技术。目前,CXF也是主流的webService实现框架

Spring集成cxf

首先需要在命名空间中声明cxf的配置,如下图所示

然后定义一个接口:

@WebService
public interface IWebServicePublisher {
    public abstract String test(String xml);
}

再定义一个实现类实现这个接口

//endpointInterface是具体的接口包全路径
@WebService(endpointInterface="com.IWebServicePublisher ")
@Service
public class WebServicePublisherImpl implements IWebServicePublisher {
    public String test(String xml) {
        // TODO Auto-generated method stub
        //通常建议使用一个参数,然后按照xml或者json来解析
        //使用xml是因为有cdata可以避免特殊字符带来的麻烦
        return "hello world!";
    }
}

引入cxf日志

默认的话是不开启的,如果有需要是可以通过代码或者注解的形式开启,上图中的注释代码已经阐述了cxf-spring的日志开启方式,这里介绍代码的形式,先写一个CDATAOutInterceptor类,用于继承AbstractPhaseInterceptor, 这个了就是我们输出拦截类

import java.io.OutputStream;
import javax.xml.stream.XMLStreamWriter;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.staxutils.StaxUtils;

public class CDATAOutInterceptor extends AbstractPhaseInterceptor<Message> {
    public CDATAOutInterceptor() {
        super(Phase.WRITE);
    }

    public void handleMessage(Message message) {
        message.put("disable.outputstream.optimization", Boolean.TRUE);
        XMLStreamWriter writer = StaxUtils.createXMLStreamWriter(message.getContent(OutputStream.class));
        message.setContent(XMLStreamWriter.class, new CDATAXMLStreamWriter(writer));
    }
}

接下来给出调用的代码,LoggingInInterceptor()和LoggingOutInterceptor()是内置的日志类

@Test
    public void logTest() {

        try {

            JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
            Client client = dcf.createClient("http://localhost:9001/Test/service?wsdl");
            client.getInInterceptors().add(new LoggingInInterceptor());
            client.getOutInterceptors().add(new CDATAOutInterceptor());
            client.getOutInterceptors().add(new LoggingOutInterceptor());
            Object[] objs = new Object[2];
            objs[0] = "20";
            objs[1] = "张三";
            objs = client.invoke("method", objs);
            String s = objs[0].toString();
            System.out.println(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

如果还需要进一步对数据进行封装,例如xml格式的数据,则可以如下操作:

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.apache.cxf.staxutils.DelegatingXMLStreamWriter;

public class CDATAXMLStreamWriter extends DelegatingXMLStreamWriter {

    private String currentElementName;

    public CDATAXMLStreamWriter(XMLStreamWriter writer) {
        super(writer);
    }

    @Override
    public void writeCharacters(String text) throws XMLStreamException {
        boolean useCData = isNeedCData(text);
        if (useCData) {
            super.writeCData(text);
        } else {
            super.writeCharacters(text);
        }
    }

    private boolean isNeedCData(String text) {
        // 自己拓展哪些属性需要处理CDATA
        if(text != null && !"".equals(text)){

            if(text.startsWith("MSH")){

                return true;
            }
        }

        return false;
    }

    public void writeStartElement(String prefix, String local, String uri) throws XMLStreamException {
        currentElementName = local;
        super.writeStartElement(prefix, local, uri);
    }
}

常见报错-部署编译失败或提示找不到xxx类

引入cxf框架,在实际上线部署后如果采用java -jar或者部署在tomcat容器中出现编译失败,又或者提示找不到xxx类,大家如果上网搜索得到的答案一般是说当前环境是Jre,而cxf需要jdk环境下的tools.jar,也就是说cxf是需要Jdk环境的,所以解决方案很简单,如果是跑Jar包,那就进入jdk/bin路径下执行java -jar,如果是跑容器,通常情况下容器都是读取环境变量的jdk路径其实就是jre,那就修改容器的环境为jdk路径就可以了。例如tomcat是在bin\setclasspath.bat的文件中加入: set JAVA_HOME=JDK路径 set JRE_HOME=JRE路径(直接改为JDK路径) 然后重启就可以了,主要目的是让服务器使用jdk环境而不是jre环境即可

常见报错-调用超时

cxf正常调用webservice的格式如下:

JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
Client client = dcf.createClient("http://localhost:9001/Test/service?wsdl");
try {
Object[] objs = new Object[2];
objs[0] = "20";
objs[1] = "张三";
objs = client.invoke("method", objs);
String s = objs[0].toString();
System.out.println(s);
} catch (Exception e) {
e.printStackTrace();
}

cxf处理超时调用问题:

JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
Client client = dcf.createClient("http://localhost:9001/Test/service?wsdl");
//一般超时是一分钟,如果一个请求在一分钟内没有结束,又来个新的请求,如此周而复始,CXF就会缓存起来,当数缓存太大时 就会挂掉
//加入日志
client.getInInterceptors().add(new LoggingInInterceptor());
//这个输出类是我自己加的,可以删除,该类继承AbstractPhaseInterceptor
client.getOutInterceptors().add(new CDATAOutInterceptor());
client.getOutInterceptors().add(new LoggingOutInterceptor());
HTTPConduit http = (HTTPConduit) client.getConduit();
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setAllowChunking(false); //取消块编码
httpClientPolicy.setConnectionTimeout(180 * 1000); //连接超时3分钟
httpClientPolicy.setReceiveTimeout(300 * 1000); //响应超时
http.setClient(httpClientPolicy);

加入配置后,正常调用即可。 Apache CXF 是一个开源的 WebService 框架,CXF可以用来构建和开发 WebService,这些服务可以支持多种协议,比如:SOAP、POST/HTTP、HTTP ,CXF 大大简化了WebService并且可以天然地和 Spring 进行无缝集成。CXF是 Celtrix (ESB框架)和 XFire(webserivice) 合并而成,核心是org.apache.cxf.Bus(总线),类似于Spring的 ApplicationContext,CXF默认是依赖于Spring的,另 CXF 发行包中的jar,如果全部放到lib中,需要 JDK1.6 及以上,否则会报JAX-WS版本不一致的问题。CXF 内置了Jetty服务器 ,它是servlet容器。

这段话是摘自别人的,这里划重点需要jdk1.6以上,一定要是jdk,千万别搞成了jre。

CXF框架特点: - 与spring相结合,支持注解的方式来发布webservice - 能够根据需求添加拦截器,例如后文会提到的输入拦截器、输出拦截器 :输入日志信息拦截器、输出日志拦截器、用户权限认证的拦截器

cxf主要用来调用webService接口,以笔者目前的工作经验来看cxf不支持rpc模式发布的webservice,有个同事是写dephi的,他用dephi写了个webservice,我这边使用cxf调不通,最后走soap协议调的,下一篇文章会写到webservice三要素及要点。 这里先简要介绍下webservice: - webservice是一种跨平台,跨语言的规范,用于不同平台,不同语言开发的应用之间的交互。利用webservice发布的接口无需关心对方什么平台上开发以及使用何种语言开发。程序员只关心调用对方发布webservice接口方法和参数。开发人员一般就是在基于某种语言开发webservice接口,以及调用webservice接口;通常情况下开发语言都有自己的webservice实现框架。而CXF是java开发webService的一种实现框架技术。目前,CXF也是主流的webService实现框架

Spring集成cxf

首先需要在命名空间中声明cxf的配置,如下图所示

然后定义一个接口:

@WebService
public interface IWebServicePublisher {
    public abstract String test(String xml);
}

再定义一个实现类实现这个接口

//endpointInterface是具体的接口包全路径
@WebService(endpointInterface="com.IWebServicePublisher ")
@Service
public class WebServicePublisherImpl implements IWebServicePublisher {
    public String test(String xml) {
        // TODO Auto-generated method stub
        //通常建议使用一个参数,然后按照xml或者json来解析
        //使用xml是因为有cdata可以避免特殊字符带来的麻烦
        return "hello world!";
    }
}

引入cxf日志

默认的话是不开启的,如果有需要是可以通过代码或者注解的形式开启,上图中的注释代码已经阐述了cxf-spring的日志开启方式,这里介绍代码的形式,先写一个CDATAOutInterceptor类,用于继承AbstractPhaseInterceptor, 这个了就是我们输出拦截类

import java.io.OutputStream;
import javax.xml.stream.XMLStreamWriter;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.staxutils.StaxUtils;

public class CDATAOutInterceptor extends AbstractPhaseInterceptor<Message> {
    public CDATAOutInterceptor() {
        super(Phase.WRITE);
    }

    public void handleMessage(Message message) {
        message.put("disable.outputstream.optimization", Boolean.TRUE);
        XMLStreamWriter writer = StaxUtils.createXMLStreamWriter(message.getContent(OutputStream.class));
        message.setContent(XMLStreamWriter.class, new CDATAXMLStreamWriter(writer));
    }
}

接下来给出调用的代码,LoggingInInterceptor()和LoggingOutInterceptor()是内置的日志类

@Test
    public void logTest() {

        try {

            JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
            Client client = dcf.createClient("http://localhost:9001/Test/service?wsdl");
            client.getInInterceptors().add(new LoggingInInterceptor());
            client.getOutInterceptors().add(new CDATAOutInterceptor());
            client.getOutInterceptors().add(new LoggingOutInterceptor());
            Object[] objs = new Object[2];
            objs[0] = "20";
            objs[1] = "张三";
            objs = client.invoke("method", objs);
            String s = objs[0].toString();
            System.out.println(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

如果还需要进一步对数据进行封装,例如xml格式的数据,则可以如下操作:

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.apache.cxf.staxutils.DelegatingXMLStreamWriter;

public class CDATAXMLStreamWriter extends DelegatingXMLStreamWriter {

    private String currentElementName;

    public CDATAXMLStreamWriter(XMLStreamWriter writer) {
        super(writer);
    }

    @Override
    public void writeCharacters(String text) throws XMLStreamException {
        boolean useCData = isNeedCData(text);
        if (useCData) {
            super.writeCData(text);
        } else {
            super.writeCharacters(text);
        }
    }

    private boolean isNeedCData(String text) {
        // 自己拓展哪些属性需要处理CDATA
        if(text != null && !"".equals(text)){

            if(text.startsWith("MSH")){

                return true;
            }
        }

        return false;
    }

    public void writeStartElement(String prefix, String local, String uri) throws XMLStreamException {
        currentElementName = local;
        super.writeStartElement(prefix, local, uri);
    }
}

常见报错-部署编译失败或提示找不到xxx类

引入cxf框架,在实际上线部署后如果采用java -jar或者部署在tomcat容器中出现编译失败,又或者提示找不到xxx类,大家如果上网搜索得到的答案一般是说当前环境是Jre,而cxf需要jdk环境下的tools.jar,也就是说cxf是需要Jdk环境的,所以解决方案很简单,如果是跑Jar包,那就进入jdk/bin路径下执行java -jar,如果是跑容器,通常情况下容器都是读取环境变量的jdk路径其实就是jre,那就修改容器的环境为jdk路径就可以了。例如tomcat是在bin\setclasspath.bat的文件中加入: set JAVA_HOME=JDK路径 set JRE_HOME=JRE路径(直接改为JDK路径) 然后重启就可以了,主要目的是让服务器使用jdk环境而不是jre环境即可

常见报错-调用超时

cxf正常调用webservice的格式如下:

JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
Client client = dcf.createClient("http://localhost:9001/Test/service?wsdl");
try {
Object[] objs = new Object[2];
objs[0] = "20";
objs[1] = "张三";
objs = client.invoke("method", objs);
String s = objs[0].toString();
System.out.println(s);
} catch (Exception e) {
e.printStackTrace();
}

cxf处理超时调用问题:

JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
Client client = dcf.createClient("http://localhost:9001/Test/service?wsdl");
//一般超时是一分钟,如果一个请求在一分钟内没有结束,又来个新的请求,如此周而复始,CXF就会缓存起来,当数缓存太大时 就会挂掉
//加入日志
client.getInInterceptors().add(new LoggingInInterceptor());
//这个输出类是我自己加的,可以删除,该类继承AbstractPhaseInterceptor
client.getOutInterceptors().add(new CDATAOutInterceptor());
client.getOutInterceptors().add(new LoggingOutInterceptor());
HTTPConduit http = (HTTPConduit) client.getConduit();
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setAllowChunking(false); //取消块编码
httpClientPolicy.setConnectionTimeout(180 * 1000); //连接超时3分钟
httpClientPolicy.setReceiveTimeout(300 * 1000); //响应超时
http.setClient(httpClientPolicy);

加入配置后,正常调用即可。

发布于 2020-10-31 01:02