一. 简介
一、WebService是什么
WebService是一种跨编程语言和跨操作系统平台的远程调用技术
跨编程语言:就是说服务端程序采用java编写,客户端程序则可以采用其他编程语言编写,反之亦然!
跨操作系统平台:服务端程序和客户端程序可以在不同的操作系统上运行。
远程调用:就是一台计算机a上的一个程序可以调用到另外一台计算机b上的一个程序的接口方法,譬如,银联提供给商场的pos刷卡系统,商场的POS机转账调用的转账方法的代码其实是跑在银行服务器上。再比如,amazon,天气预报系统,淘宝网,校内网,百度等把自己的系统服务以webservice服务的形式暴露出来,让第三方网站和程序可以调用这些服务功能,这样扩展了自己系统的市场占有率,往大的概念上吹,就是所谓的SOA应用,当然,webservice也是rpc远程调用的一种实现方式。
二、为什么要用Web service?
web service能解决:
-
跨平台调用
-
跨语言调用
-
远程调用
三、什么时候使用web Service?
1. 同一家公司的新旧应用之间
2. 不同公司的应用之间
3. 一些提供数据的内容聚合应用:天气预报、股票行情
四、Web Service中的几个重要术语
4.1、WSDL(web service definition language)
WSDL是webservice定义语言, 对应.wsdl文档, 一个webservice会对应一个唯一的wsdl文档, 定义了客户端与服务端发送请求和响应的数据格式和过程
4.2、SOAP(simple object access protocal)
SOAP是"简单对象访问协议"
-
是一种简单的、基于HTTP和XML的协议, 用于在WEB上交换结构化的数据
-
soap消息:请求消息和响应消息
4.3、SEI(WebService EndPoint Interface)
SEI是web service的终端接口,就是WebService服务器端用来处理请求的接口
4.4、CXF(Celtix + XFire)
一个apache的用于开发webservice服务器端和客户端的框架。
二、一步步使用jdk实现webservice
webservice服务提供方
一、新建一个java project,命名为ceshi_webservice_server,用来当做webservice提供服务的一端
二、编写接口、接口的实现类、服务发布类,结构如图:
可以看到,使用jdk发布webservice不需要引入任何的jar包。
这三个类结构都很简单,如下。
IpManage.java是接口类,表明了你这个webservice要提供哪些服务给外部,代码如下:
package cn.zhao.svc;
import javax.jws.WebMethod;
import javax.jws.WebService;
@WebService
public interface IpManage {
@WebMethod
public String auth(byte[] ip);
}
很简单,只有一个方法,接受一个字节数组,返回一个字符串。
IpManageImpl是IpManage的实现类,代码如下:
package cn.zhao.svc.impl;
import javax.jws.WebService;
import cn.zhao.svc.IpManage;
@WebService
public class IpManageImpl implements IpManage {
@Override
public String auth(byte[] ip) {
try {
System.out.println("客户端传来的ip:"+new String(ip));
return "success"+new String(ip);
} catch (Exception e) {
e.printStackTrace();
}
return "error";
}
}
IpManageSvr的作用是发布这个webservice服务,只有你的服务发布了并且一直处于运行状态,别人才可以调用你的webservice服务,代码如下:
package cn.zhao.server;
import javax.xml.ws.Endpoint;
import cn.zhao.svc.impl.IpManageImpl;
public class IpManageSvr {
public static void main(String[] args) {
//本webservice的地址
String address="http://localhost:8888/ceshi_webservice_server/svc01";
Endpoint endpoint = Endpoint.publish(address, new IpManageImpl());
System.out.println(endpoint);
System.out.println("通过jdk发布webservice成功");
}
}
address是你指定的你这个webservice服务的访问地址,如果其他外部想调用你的这个webservice服务,你只需要提供这个地址给别人就行了。我们直接运行这个main函数,结果如下:
好了,经过上面两步,webservice的服务提供方的工作就做完了。
webservice调用方
一、在服务提供方提供了地址(比如上面的http://localhost:8888/ceshi_webservice_server/svc01)之后,客户端可以通过http://localhost:8888/ceshi_webservice_server/svc01?wsdl的方式来查看服务提供方提供的接口调用格式:
webservice本质上就是发送和接受的soap消息,soap消息就是html+xml片段,这样这里的soap消息跟现在吹牛逼经常使用soa并没有关系。解释下wsdl(webservice defination language)的结构:
types:定义消息
message:引用消息
porttype:定义sei(service endpoint interface)
binding:sei的实现类
service:sei的容器
看接口的结构要访问types里面定义的import进来的文件:
二、知道了webservice服务的wsdl我们之后需要做的就是利用jdk提供的工具自动生成代码,我们再在这个代码的基础上去编写代码调用就行了,怎么自动生成代码呢?wsimport -keep http://localhost:8888/ceshi_webservice_server/svc01?wsdl或者wsimport -keep e:\\xx.wsdl,其中xx.wsdl是你把http://localhost:8888/ceshi_webservice_server/svc01?wsdl的内容拷贝到了本地之后的文件,两种方式都行,其中wsimport是安装jdk时bin目录中自带的工具,我们新建一个工程名字为ceshi_webservice_client,我们使用第一种:
执行完之后,eclipse中ceshi_webservice_client项目的结构变成了下面的样子:
红框中的类都是生成的,那么我们就来编写个测试类测试一下吧。
三、调用webservice服务,编写IpManageClient.java:
package cn.zhao.client;
import cn.zhao.svc.impl.IpManageImpl;
import cn.zhao.svc.impl.IpManageImplService;
public class IpManageClient {
public static void main(String[] args) {
//factory可以得到sei的实现类对象
IpManageImplService factory=new IpManageImplService();
//接口
IpManageImpl ipManageImpl = factory.getIpManageImplPort();
System.out.println(ipManageImpl.getClass());//ipManageImpl是动态产生的代理对象
String ret= ipManageImpl.auth("192.168.1.1".getBytes());
System.out.println("服务端返回的值:"+ret);
}
}
运行结果:
四、完成
三、一步步使用cxf实现webservice
前边详细说了如何“一步步使用jdk实现webservice”,今天我们来看看如何使用比较流行的cxf来实现webservice。既然jdk可以实现webservice,为什么还要cxf呢?cxf支持list map set Student等所有类型,jdk的ws不支持map,另外针对webservice服务,jdk是没有拦截器的,cxf才有。
我们这里基于“一步步使用jdk实现webservice”项目改造,服务端和客户端都是用cxf来实现。
服务端
服务端不需要改变任何代码,要是什么jar包都不加就是使用的jdk发布webservice,假如加入了cxf的jar包那么就是使用的cxf了。因此我们要做的就是在服务端加入cxf的jar包,cxf需要的jar包都在cxf的压缩包里吗了,上面已经提供。
这里我们加入jar包之后,直接运行IpManageSvr类:
package cn.zhao.server;
import javax.xml.ws.Endpoint;
import cn.zhao.svc.impl.IpManageImpl;
public class IpManageSvr {
public static void main(String[] args) {
//本webservice的地址
String address="http://localhost:8888/ceshi_webservice_server/svc01";
Endpoint endpoint = Endpoint.publish(address, new IpManageImpl());
System.out.println(endpoint);
System.out.println("通过cxf发布webservice成功");
}
}
我只时把通过jdk改为了通过cxf。
运行结果如下:
就这样,不要改变任何代码,你就通过cxf把webservice服务发布成功了。
看下wsdl文档:
比jdk的简洁了很多。
webservice调用端
一、新建ceshi_webservice_client java项目,重新生成代码,类似jdk的wsimport,这次试用的命令是wsdl2java http://localhost:8888/ceshi_webservice_server/svc01?wsdl 或者wsdl2java e:\\xx.wsdl。
查看代码结构:
我们看到,跟jdk生成webservice时生成的包结构也有稍微的差异。
二、新建个客户端调用类
package cn.zhao.client;
import cn.zhao.svc.IpManage;
import cn.zhao.svc.impl.IpManageImplService;
public class IpManageClient {
public static void main(String[] args) {
//factory可以得到sei的实现类对象
IpManageImplService factory=new IpManageImplService();
//接口
IpManage ipManage = factory.getIpManageImplPort();
System.out.println(ipManage.getClass());//ipManageImpl是动态产生的代理对象
String ret= ipManage.auth("192.168.1.1".getBytes());
System.out.println("服务端返回的值:"+ret);
}
}
三、加入cxf的jar包,最终的项目结构如下:
四、运行:
客户端结果 | f服务端结果 |
![]() | ![]() |
四、cxf开发webservice加入权限验证拦截器与日志拦截器
cxf里面的日志拦截器可以打印soap消息信息,有出拦截器和入拦截器之分。当然这些soap消息也可以通过工具来看到。如何加入日志拦截器呢,我们还是接着“一步步使用cxf实现webservice”里面的代码。
服务端
修改IpManageSvr发布类:
package cn.zhao.server;
import java.util.List;
import javax.xml.ws.Endpoint;
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 cn.zhao.svc.impl.IpManageImpl;
public class IpManageSvr {
public static void main(String[] args) {
//本webservice的地址
String address="http://localhost:8888/ceshi_webservice_server/svc01";
Endpoint endpoint = Endpoint.publish(address, new IpManageImpl());
//加入日志拦截器
EndpointImpl eImpl=(EndpointImpl)endpoint;
List<Interceptor<? extends Message>> inInterceptors=eImpl.getInInterceptors();
//加入日志入拦截器
inInterceptors.add(new LoggingInInterceptor());
//加入日志出拦截器
List<Interceptor<? extends Message>> outInterceptors = eImpl.getOutInterceptors();
outInterceptors.add(new LoggingOutInterceptor());
System.out.println(endpoint);
System.out.println("通过cxf发布webservice成功");
}
}
客户端
修改IpManageClient调用类:
package cn.zhao.client;
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 cn.zhao.svc.IpManage;
import cn.zhao.svc.impl.IpManageImplService;
public class IpManageClient {
public static void main(String[] args) {
//factory可以得到sei的实现类对象
IpManageImplService factory=new IpManageImplService();
//接口
IpManage ipManage = factory.getIpManageImplPort();
System.out.println(ipManage.getClass());//ipManageImpl是动态产生的代理对象
//在方法调用之前加入日志出入拦截器
//调用端的出对应服务端的入;调用端的入对应服务端的出;
Client client = ClientProxy.getClient(ipManage);
client.getOutInterceptors().add(new LoggingOutInterceptor());
client.getInInterceptors().add(new LoggingInInterceptor());
String ret= ipManage.auth("192.168.1.1".getBytes());
System.out.println("服务端返回的值:"+ret);
}
}
分别运行服务和客户端,结果如下:
服务端打印信息结果:
信息: started o.e.j.s.h.ContextHandler{/ceshi_webservice_server,null}
org.apache.cxf.jaxws22.EndpointImpl@36905f7a
通过cxf发布webservice成功
八月 18, 2018 2:16:02 下午 org.apache.cxf.services.IpManageImplService.IpManageImplPort.IpManage
信息: Inbound Message
----------------------------
ID: 1
Address: http://localhost:8888/ceshi_webservice_server/svc01?wsdl
Encoding: UTF-8
Http-Method: GET
Content-Type: text/xml
Headers: {Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], content-type=[text/xml], Host=[localhost:8888], Pragma=[no-cache], User-Agent=[Apache CXF 2.5.9]}
--------------------------------------
八月 18, 2018 2:16:02 下午 org.apache.cxf.services.IpManageImplService.IpManageImplPort.IpManage
信息: Inbound Message
----------------------------
ID: 2
Address: http://localhost:8888/ceshi_webservice_server/svc01?wsdl=IpManage.wsdl
Encoding: UTF-8
Http-Method: GET
Content-Type: text/xml
Headers: {Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], content-type=[text/xml], Host=[localhost:8888], Pragma=[no-cache], User-Agent=[Apache CXF 2.5.9]}
--------------------------------------
八月 18, 2018 2:16:06 下午 org.apache.cxf.services.IpManageImplService.IpManageImplPort.IpManage
信息: Inbound Message
----------------------------
ID: 3
Address: http://localhost:8888/ceshi_webservice_server/svc01
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml; charset=UTF-8
Headers: {Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], Content-Length=[191], content-type=[text/xml; charset=UTF-8], Host=[localhost:8888], Pragma=[no-cache], SOAPAction=[""], User-Agent=[Apache CXF 2.5.9]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:auth xmlns:ns2="http://svc.zhao.cn/"><arg0>MTkyLjE2OC4xLjE=</arg0></ns2:auth></soap:Body></soap:Envelope>
--------------------------------------
客户端传来的ip:192.168.1.1
八月 18, 2018 2:16:06 下午 org.apache.cxf.services.IpManageImplService.IpManageImplPort.IpManage
信息: Outbound Message
---------------------------
ID: 3
Encoding: UTF-8
Content-Type: text/xml
Headers: {}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:authResponse xmlns:ns2="http://svc.zhao.cn/"><return>success192.168.1.1</return></ns2:authResponse></soap:Body></soap:Envelope>
--------------------------------------
客户端打印信息:
八月 18, 2018 2:16:03 下午 org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
信息: Creating Service {http://impl.svc.zhao.cn/}IpManageImplService from WSDL: http://localhost:8888/ceshi_webservice_server/svc01?wsdl
class com.sun.proxy.$Proxy25
八月 18, 2018 2:16:06 下午 org.apache.cxf.services.IpManageImplService.IpManageImplPort.IpManage
信息: Outbound Message
---------------------------
ID: 1
Address: http://localhost:8888/ceshi_webservice_server/svc01
Encoding: UTF-8
Content-Type: text/xml
Headers: {Accept=[*/*], SOAPAction=[""]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:auth xmlns:ns2="http://svc.zhao.cn/"><arg0>MTkyLjE2OC4xLjE=</arg0></ns2:auth></soap:Body></soap:Envelope>
--------------------------------------
八月 18, 2018 2:16:07 下午 org.apache.cxf.services.IpManageImplService.IpManageImplPort.IpManage
信息: Inbound Message
----------------------------
ID: 1
Response-Code: 200
Encoding: UTF-8
Content-Type: text/xml;charset=UTF-8
Headers: {Content-Length=[213], content-type=[text/xml;charset=UTF-8], Server=[Jetty(7.5.4.v20111024)]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:authResponse xmlns:ns2="http://svc.zhao.cn/"><return>success192.168.1.1</return></ns2:authResponse></soap:Body></soap:Envelope>
--------------------------------------
服务端返回的值:success192.168.1.1
cxf自带的日志拦截器主要用于打印webservice调用的时候传递的参数以及返回的参数。如果我想自定义拦截器怎么办呢?比如我想定义一个拦截器,指定调用我这个服务需要输入用户名密码,该怎么实现呢?
一、服务端在入拦截器list中加入获取权限信息的拦截器:
其中,AuthInterc的代码如下:
package cn.zhao.server;
import javax.xml.namespace.QName;
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 org.w3c.dom.NodeList;
/**
* 定义cxf权限拦截器
*/
public class AuthInterc extends AbstractPhaseInterceptor<SoapMessage>{
public AuthInterc() {
//webservice有很多阶段,这里指定在哪个阶段使用这个拦截器
super(Phase.PRE_PROTOCOL);
}
/**
* <soap:Envelope>
* <head>
* <userinfo>
* <name>roadjava></name>
* <pwd>123</pwd>
* </userinfo>
* </head>
* <soap:Body>
* <ns2:auth>
* <arg0>MTkyLjE2OA==</arg0>
* </ns2:auth>
* </soap:Body>
* </soap:Envelope>
*/
@Override
public void handleMessage(SoapMessage message) throws Fault {
//获取soap消息头部,从头部获取userinfo标签,userinfo标签是客户端调用时在
//出拦截器列表中加入的
Header header = message.getHeader(new QName("userinfo"));
if(header!=null){
Element element=(Element) header.getObject();
NodeList nameList = element.getElementsByTagName("name");
NodeList pwdList = element.getElementsByTagName("pwd");
String name=nameList.item(0).getFirstChild().getNodeValue();
String pwd=pwdList.item(0).getFirstChild().getNodeValue();
if ("roadjava".equals(name)&&"123".equals(pwd)) {
//不拦截,执行调用
}else {
throw new Fault(new RuntimeException("用户名或密码不正确"));
}
}else {
throw new Fault(new RuntimeException("用户信息不存在"));
}
}
}
二、客户端调用webservice的时候在出拦截器list中加入权限验证拦截器
其中AuthIntercClient类代码:
package cn.zhao.client;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.helpers.DOMUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.xml.utils.DOMHelper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* 自定义拦截器
* @author zhao
*
*/
public class AuthIntercClient extends AbstractPhaseInterceptor<SoapMessage>{
private String name;
private String pwd;
public AuthIntercClient(String name,String pwd) {
super(Phase.PRE_PROTOCOL);
this.name=name;
this.pwd=pwd;
}
/**
* <soap:Envelope>
* <head>
* <userinfo>
* <name>roadjava</name>
* <pwd>123</pwd>
* </userinfo>
* </head>
* <soap:Body>
* <ns2:auth>
* <arg0>MTkyLjE2OA==</arg0>
* </ns2:auth>
* </soap:Body>
* </soap:Envelope>
*/
@Override
public void handleMessage(SoapMessage message) throws Fault {
List<Header> headers = message.getHeaders();
//Document createDocument = DOMUtils.createDocument();
//采用java自带的dom生成器
DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
DocumentBuilder db=null;
try {
db = dbf.newDocumentBuilder();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
//创建userinfo标签
Document document = db.newDocument();
Element userinfoEl = document.createElement("userinfo");
Element name = document.createElement("name");
Element pwd = document.createElement("pwd");
name.setTextContent(this.name);
pwd.setTextContent(this.pwd);
userinfoEl.appendChild(name);
userinfoEl.appendChild(pwd);
document.appendChild(userinfoEl);
//创建header标签并加入到headers这个list中
headers.add(new Header(new QName("userinfo"), userinfoEl));
}
}
三、运行服务端和客户端
客户端打印信息:
信息: Outbound Message
---------------------------
ID: 1
Address: http://localhost:8888/ceshi_webservice_server/svc01
Encoding: UTF-8
Content-Type: text/xml
Headers: {Accept=[*/*], SOAPAction=[""]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><userinfo><name>roadjava</name><pwd>123</pwd></userinfo></soap:Header><soap:Body><ns2:auth xmlns:ns2="http://svc.zhao.cn/"><arg0>MTkyLjE2OC4xLjE=</arg0></ns2:auth></soap:Body></soap:Envelope>
--------------------------------------
八月 18, 2018 2:40:43 下午 org.apache.cxf.services.IpManageImplService.IpManageImplPort.IpManage
信息: Inbound Message
----------------------------
ID: 1
Response-Code: 200
Encoding: UTF-8
Content-Type: text/xml;charset=UTF-8
Headers: {Content-Length=[213], content-type=[text/xml;charset=UTF-8], Server=[Jetty(7.5.4.v20111024)]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:authResponse xmlns:ns2="http://svc.zhao.cn/"><return>success192.168.1.1</return></ns2:authResponse></soap:Body></soap:Envelope>
--------------------------------------
服务端返回的值:success192.168.1.1
服务端打印信息:
信息: Inbound Message
----------------------------
ID: 3
Address: http://localhost:8888/ceshi_webservice_server/svc01
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml; charset=UTF-8
Headers: {Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], Content-Length=[274], content-type=[text/xml; charset=UTF-8], Host=[localhost:8888], Pragma=[no-cache], SOAPAction=[""], User-Agent=[Apache CXF 2.5.9]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><userinfo><name>roadjava</name><pwd>123</pwd></userinfo></soap:Header><soap:Body><ns2:auth xmlns:ns2="http://svc.zhao.cn/"><arg0>MTkyLjE2OC4xLjE=</arg0></ns2:auth></soap:Body></soap:Envelope>
--------------------------------------
客户端传来的ip:192.168.1.1
八月 18, 2018 2:40:43 下午 org.apache.cxf.services.IpManageImplService.IpManageImplPort.IpManage
信息: Outbound Message
---------------------------
ID: 3
Encoding: UTF-8
Content-Type: text/xml
Headers: {}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:authResponse xmlns:ns2="http://svc.zhao.cn/"><return>success192.168.1.1</return></ns2:authResponse></soap:Body></soap:Envelope>
--------------------------------------
这是没有任何问题的。
四、如果我在客户端调用的时候传入错误的用户名和密码呢?当然会调用不成功了,验证如下:
client.getOutInterceptors().add(new AuthIntercClient("xxx", "123"));
客户端打印信息:
信息: Outbound Message
---------------------------
ID: 1
Address: http://localhost:8888/ceshi_webservice_server/svc01
Encoding: UTF-8
Content-Type: text/xml
Headers: {Accept=[*/*], SOAPAction=[""]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><userinfo><name>xxx</name><pwd>123</pwd></userinfo></soap:Header><soap:Body><ns2:auth xmlns:ns2="http://svc.zhao.cn/"><arg0>MTkyLjE2OC4xLjE=</arg0></ns2:auth></soap:Body></soap:Envelope>
--------------------------------------
八月 18, 2018 2:44:53 下午 org.apache.cxf.services.IpManageImplService.IpManageImplPort.IpManage
信息: Inbound Message
----------------------------
ID: 1
Response-Code: 500
Encoding: UTF-8
Content-Type: text/xml;charset=UTF-8
Headers: {Content-Length=[222], content-type=[text/xml;charset=UTF-8], Server=[Jetty(7.5.4.v20111024)]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><soap:Fault><faultcode>soap:Server</faultcode><faultstring>用户名或密码不正确</faultstring></soap:Fault></soap:Body></soap:Envelope>
--------------------------------------
Exception in thread "main" javax.xml.ws.soap.SOAPFaultException: 用户名或密码不正确
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:156)
at com.sun.proxy.$Proxy25.auth(Unknown Source)
at cn.zhao.client.IpManageClient.main(IpManageClient.java:27)
Caused by: org.apache.cxf.binding.soap.SoapFault: 用户名或密码不正确
at org.apache.cxf.binding.soap.interceptor.Soap11FaultInInterceptor.unmarshalFault(Soap11FaultInInterceptor.java:75)
at org.apache.cxf.binding.soap.interceptor.Soap11FaultInInterceptor.handleMessage(Soap11FaultInInterceptor.java:46)
at org.apache.cxf.binding.soap.interceptor.Soap11FaultInInterceptor.handleMessage(Soap11FaultInInterceptor.java:35)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.interceptor.AbstractFaultChainInitiatorObserver.onMessage(AbstractFaultChainInitiatorObserver.java:114)
at org.apache.cxf.binding.soap.interceptor.CheckFaultInterceptor.handleMessage(CheckFaultInterceptor.java:69)
at org.apache.cxf.binding.soap.interceptor.CheckFaultInterceptor.handleMessage(CheckFaultInterceptor.java:34)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.endpoint.ClientImpl.onMessage(ClientImpl.java:801)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponseInternal(HTTPConduit.java:1679)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponse(HTTPConduit.java:1517)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1425)
at org.apache.cxf.io.CacheAndWriteOutputStream.postClose(CacheAndWriteOutputStream.java:50)
at org.apache.cxf.io.CachedOutputStream.close(CachedOutputStream.java:188)
at org.apache.cxf.transport.AbstractConduit.close(AbstractConduit.java:56)
at org.apache.cxf.transport.http.HTTPConduit.close(HTTPConduit.java:650)
at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:62)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:531)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:462)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:365)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:318)
at org.apache.cxf.frontend.ClientProxy.invokeSync(ClientProxy.java:95)
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:134)
... 2 more
五、cxf与spring整合在web项目中的应用
webservice服务端
一、新建动态web工程ws02server,加入cxfjar包,配置web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>ws02server</display-name>
<!-- 设置Spring容器加载配置文件路径 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>
<!-- 加载Spring容器配置 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置cxf的servlet -->
<servlet>
<servlet-name>CXFService</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CXFService</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
新建接口LogisticsService:
package cn.zhao.spring.logistics;
import javax.jws.WebMethod;
import javax.jws.WebService;
/**
* 物流信息接口
* @author zhao
*
*/
@WebService
public interface LogisticsService {
@WebMethod
public LogisticsInfo getByOrderNo(Integer id);
}
新建LogisticsService接口的实现类:
package cn.zhao.spring.logistics;
import javax.jws.WebService;
/**
* 接口实现
* @author zhao
*
*/
@WebService
public class LogisticsServiceImp implements LogisticsService{
public LogisticsInfo getByOrderNo(Integer id){
System.out.println("传来的订单id号:"+id);
LogisticsInfo logisticsInfo = new LogisticsInfo(1,id,"八千家物流");
System.out.println(logisticsInfo);
return logisticsInfo;
}
}
返回的实体类LogisticsInfo的信息:
package cn.zhao.spring.logistics;
/**
* 物流信息
* @author zhao
*
*/
public class LogisticsInfo {
//主键
private Integer id;
//该物流信息关联的订单号
private Integer orderNo;
//物流公司名称
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getOrderNo() {
return orderNo;
}
public void setOrderNo(Integer orderNo) {
this.orderNo = orderNo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "LogisticsInfo [id=" + id + ", orderNo=" + orderNo + ", name="
+ name + "]";
}
public LogisticsInfo(Integer id, Integer orderNo, String name) {
super();
this.id = id;
this.orderNo = orderNo;
this.name = name;
}
public LogisticsInfo(){
}
}
调用权限拦截器代码:
package cn.zhao.spring.logistics;
import javax.xml.namespace.QName;
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 org.w3c.dom.NodeList;
public class CheckUserinfo extends AbstractPhaseInterceptor<SoapMessage>{
public CheckUserinfo() {
super(Phase.PRE_PROTOCOL);
}
/**
* <soap:Envelope>
* <head>
* <userinfo>
* <name>roadjava</name>
* <pwd>123</pwd>
* </userinfo>
* </head>
* <soap:Body>
* <ns2:auth>
* <arg0>MTkyLjE2OA==</arg0>
* </ns2:auth>
* </soap:Body>
* </soap:Envelope>
*/
@Override
public void handleMessage(SoapMessage message) throws Fault {
Header header = message.getHeader(new QName("userinfo"));
if(header!=null){
Element element=(Element) header.getObject();
NodeList nameList = element.getElementsByTagName("name");
NodeList pwdList = element.getElementsByTagName("pwd");
String name=nameList.item(0).getFirstChild().getNodeValue();
String pwd=pwdList.item(0).getFirstChild().getNodeValue();
if ("roadjava".equals(name)&&"123".equals(pwd)) {
}else {
throw new Fault(new RuntimeException("用户名或密码不正确"));
}
}else {
throw new Fault(new RuntimeException("没有信息"));
}
}
}
在spring的配置文件beans.xml中配置并发布服务:
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<!-- 不用再src下建立文件夹了,这些文件都是cxf jar包里的文件 -->
<import resource="classpath:META-INF/cxf/cxf.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
<import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
<!-- 终端的配置 ,暴露webservice,直接写类没法注入其他依赖的对象,要单独一个bean,
本身就是一个web工程,故address不在需要加那些http
-->
<bean id="logisticsServiceImpl" class="cn.zhao.spring.logistics.LogisticsServiceImp"></bean>
<!-- #代表使用spring容器中的类 ,address="/logistics"表示将来暴露的webservice的地址为:
http://localhost:8080/ws02server/logistics-->
<jaxws:endpoint address="/logistics" implementor="#logisticsServiceImpl">
<jaxws:inInterceptors >
<bean class="cn.zhao.spring.logistics.CheckUserinfo"></bean>
</jaxws:inInterceptors>
</jaxws:endpoint>
</beans>
二、服务端项目结构:
三、启动服务端,并访问http://localhost:8080/ws02server/,因为配置了org.apache.cxf.transport.servlet.CXFServlet拦截所有的地址,所以这里你访问首页就可以看到本项目提供的webservice服务列表。
webservice客户端
一、新建ws02client web工程,导入cxfjar包以及其他使用的jar包(spring,struts2等)
二、配置web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>ws02client</display-name>
<!-- 设置Spring容器加载配置文件路径 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>
<!-- 加载Spring容器配置 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 只使用别人的webservice就不用配置cxf的servlet了 -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
三、使用wsdl2java 自动生成客户端的类,执行命令wsdl2java http://localhost:8080/ws02server/logistics?wsdl:
生成之后项目结构如下所示:
四、配置struts2.xml:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<!-- struts2的实例化由web容器交由spring -->
<constant name="struts.objectFactory" value="spring" />
<package extends="struts-default" name="ws02client" namespace="/">
<action name="listLogistics" class="listLogistics" >
<result name="listLogisticsInfo">/listLogisticsInfo.jsp</result>
</action>
</package>
</struts>
五、struts2的action:
package cn.spring.controller;
import javax.annotation.Resource;
import org.apache.struts2.dispatcher.RequestMap;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import cn.zhao.spring.logistics.LogisticsInfo;
import cn.zhao.spring.logistics.LogisticsService;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
/**
* 控制器依赖的不再是自己的service,而是第三方的ws,直接到别人的数据库中去取了
*/
@Controller @Scope("prototype")
public class ListLogistics extends ActionSupport {
@Resource
private LogisticsService logisticsService;
private LogisticsInfo logisticsInfo;
public LogisticsInfo getLogisticsInfo() {
return logisticsInfo;
}
public void setLogisticsInfo(LogisticsInfo logisticsInfo) {
this.logisticsInfo = logisticsInfo;
}
@Override
public String execute() throws Exception {
logisticsInfo= logisticsService.getByOrderNo(22);
RequestMap object = (RequestMap) ActionContext.getContext().get("request");
object.put("logisticsInfo", logisticsInfo);
return "listLogisticsInfo";
}
}
六、beans.xml:
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
">
<!-- 扫描 -->
<context:component-scan base-package="cn" />
<!-- 客户端的配置 serviceClass配置成接口-->
<jaxws:client id= "logisticsService" serviceClass="cn.zhao.spring.logistics.LogisticsService"
address="http://localhost:8080/ws02server/logistics">
<jaxws:outInterceptors>
<bean class="cn.spring.client.test.AddUserInfo" id="addUserInfo">
<constructor-arg index="0" value="roadjava"/>
<constructor-arg index="1" value="123"/>
</bean>
</jaxws:outInterceptors>
</jaxws:client>
</beans>
七、权限类:
package cn.spring.client.test;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
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;
public class AddUserInfo extends AbstractPhaseInterceptor<SoapMessage>{
private String name;
private String pwd;
public AddUserInfo(String name,String pwd) {
super(Phase.PRE_PROTOCOL);
this.name=name;
this.pwd=pwd;
}
/**
* <soap:Envelope>
* <head>
* <userinfo>
* <name>roadjava</name>
* <pwd>123</pwd>
* </userinfo>
* </head>
* <soap:Body>
* <ns2:auth>
* <arg0>MTkyLjE2OA==</arg0>
* </ns2:auth>
* </soap:Body>
* </soap:Envelope>
*/
@Override
public void handleMessage(SoapMessage message) throws Fault {
List<Header> headers = message.getHeaders();
DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
DocumentBuilder db=null;
try {
db = dbf.newDocumentBuilder();
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Document document = db.newDocument();
Element userinfoEl = document.createElement("userinfo");
Element name = document.createElement("name");
Element pwd = document.createElement("pwd");
name.setTextContent(this.name);
pwd.setTextContent(this.pwd);
userinfoEl.appendChild(name);
userinfoEl.appendChild(pwd);
document.appendChild(userinfoEl);
headers.add(new Header(new QName("userinfo"), userinfoEl));
}
}
八、listLogisticsInfo.jsp文件:
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>显示物流信息</title>
</head>
<body>
${logisticsInfo.id }----
${logisticsInfo.name }----
${logisticsInfo.orderNo }----
</body>
</html>
九、写好webservice的客户端之后,把客户端也启动,访问客户端的action:http://localhost:8081/ws02client/listLogistics,徐国struts2的同学都知道,会默认调用ListLogistics类里面的execute方法,运行结果:
服务端也打印了相关信息:
好,这样就完成了cxf与spring整合在web项目中的应用。
六、使用eclipse自带的webservice explorer来调用webservice
一、使用eclipse自带的webservice explorer来调用webservice,可以在写客户端调用代码之前测试webservice
二、输入后点击go:
三、这里的operations区域展示的就是提供的可供调用的方法,点击方法名称即可调用
如果要加入参数,可以直接点击add,加入参数,如果需要加入header,可以切到source模式,输入要加入的header:
四、运行返回结果:
七、axis2实现webservice
服务端
一、新建web工程d3(名字叫什么没有关系啦),导入axis2所需jar包,我这里使用的是axis2 1.7.8的版本,配置web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>d3</display-name><!-- 项目名,创建项目时候自动生成的 -->
<servlet>
<servlet-name>AxisServlet</servlet-name>
<servlet-class>org.apache.axis2.transport.http.AxisServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>AxisServlet</servlet-name>
<!-- 原先拦截的是/apis/*,按照最简默认配置,这里要写为/services/*,wsdl中生成的会默认使用services,如果配置成apis,
将会拦截不到:
<http:address location="http://localhost:8081/d3/services/companySvc.companySvcHttpEndpoint/"/>
-->
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
</web-app>
注意:如注释中所说,这里拦截url要配置成/services/*,不能随便写。
二、新建接口,用来提供webservice服务
package com.roadjava.apis;
public interface CompanySvc {
String companyScore(String name);
}
三、新建上面这个接口的实现类
package com.roadjava.apis.impl;
import com.roadjava.apis.CompanySvc;
public class CompanySvcImpl implements CompanySvc{
@Override
public String companyScore(String name) {
return "企业"+name+"综合评分为3分(满10分)";
}
}
作用很简单,就是传入一个公司名称,返回这个公司的综合评分,这是模拟的业务。
四、在WEB-INF/services/config/META-INF文件夹下面新建一个services.xml文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<service name="companySvc"> <!-- 指定服务名,随便定义 -->
<description>测试axis2实现webservice</description><!-- 服务的作用说明,可写可不写 -->
<!-- 指定要发布的类路径 -->
<parameter name="ServiceClass">com.roadjava.apis.impl.CompanySvcImpl</parameter>
<!-- 类里面的方法,有其他方法就在写个operation标签 -->
<operation name="companyScore"> <!-- 类里面的方法名 -->
<messageReceiver class="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />
</operation>
</service>
注意:上面的路径除了config这个名字是自己起的,其他路径名字都是固定的,不能修改。项目结构:
五、运行项目,那么按照上面的配置,访问http://localhost:8081/d3/services/companySvc?wsdl即可看到webservice的wsdl文件,其中companySvc是上面services.xml中的service的name值。
客户端
一、新建axis2client项目,导入axis2所需jar包,我这里使用的是axis2 1.7.8的版本,新建一个类axis2client.controller.CompanyController,用来对webservice服务发起请求:
package axis2client.controller;
import javax.xml.namespace.QName;
import org.apache.axis2.AxisFault;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.rpc.client.RPCServiceClient;
public class CompanyController {
public static void main(String[] args) {
try {
//本机tomcat端口默认为8081,参数是wsdl网址的一部分
EndpointReference eRef = new EndpointReference("http://localhost:8081/d3/services/companySvc");
RPCServiceClient sender = new RPCServiceClient();
Options options = sender.getOptions();
options.setTimeOutInMilliSeconds(2*20000L);//超时时间20s
options.setTo(eRef);
/**
* 参数:
* 1:在网页上执行http://localhost:8081/d3/apis/companySvc?wsdl后
* types--->schema标签的targetNamespace="http://impl.apis.roadjava.com">
* 2:types--->schema-->element的name="companyScore",即接口名
*/
QName qname = new QName("http://impl.apis.roadjava.com", "companyScore");
String str = "阿里巴巴"; //方法的入参
Object[] param = new Object[]{str};
Class<?>[] types = new Class[]{String.class}; //这是针对返回值类型的
/**
* RPCServiceClient类的invokeBlocking方法调用了WebService中的方法。
* invokeBlocking方法有三个参数
* 第一个参数的类型是QName对象,表示要调用的方法名;
* 第二个参数表示要调用的WebService方法的参数值,参数类型为Object[];
* 第三个参数表示WebService方法的返回值类型的Class对象,参数类型为Class[]。
* 当方法没有参数时,invokeBlocking方法的第二个参数值不能是null,而要使用new Object[]{}。
*/
Object[] response1 = sender.invokeBlocking(qname, param, types);
System.out.println(response1[0]);
} catch (AxisFault e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
二、运行结果:
三、客户端项目代码结构:
好了,这样就完成了axis2发布webservice以及客户端的调用过程。
总结
①、axis2跟cxf还有jdk实现webservice的方式有所不同,axis2不需要通过wsdl2java命令或者wsimport命令进行类的自动生成。
②、axis2有自己的配置文件,而且默认有自己的固定路径,cxf还有jdk实现webservice是不需要自己的配置文件的。
③、axis2不依赖@WebService、@WebMethod这些注解