关于webservice框架CXF的总结
首先,先说一下webservice的优点吧,websevice最大的优点就是可以跨平台跨语言远程调用,就是你在webservice平台上用java写了一个api发布了,其他象用C++,paython写的平台也能通过webservice调用你写的api,同样反过来也行的通。现在主流的webservice框架有Apache Axis1、Apache Axis2、Codehaus XFire、Apache CXF、sun JAX-WS(最简单、方便)等等,我接触到的就是Axis2和CXF,感觉CXF要比Axis2简洁,方便使用一点,https://www.cnblogs.com/ruiati/p/6640287.html 这位老哥对现在较为流行的webservice框架进行了对比,有兴趣的可以看看。好了接下来开始总结webservice知识和CXF框架。(因为楼主之前没有接触过webservice,所以会从最基本的开始)。
1.webservice组成
webservice由XML+XSD,SOAP和WSDL组成。
1.1 xml+xsd
webservice通过xml来发送数据,但是由于xml并不能明确定义数据格式,而不同平台之间的数据类型可能不一致,可能导致接受数据出现错误,所以,不同平台之间传输数据时需要统一数据规范,以统一的规范发送数据,再根据规范接受数据,这样就解决了传输数据类型问题。
1.2 soap
SOAP(simple object access protocal)使用基于XML的数据结构和超文本传输协议(HTTP)来传输数据。SOAP协议 = HTTP协议 + XML数据格式。
1.3 wsdl
wsdl:Web Service Description Language,即网络服务描述语言,用来告知使用或者准备使用的群体,该webservice接口的地址,需要传输什么内容,会返回什么内容,会调用那个方法等详细信息。客户端要调用一个WebService服务之前,要知道该服务的WSDL文件的地址。 WebService服务提供商可以通过两种方式来暴露它的WSDL文件地址:1.注册到UDDI服务器,以便被人查找;2.直接告诉给客户端调用者。
1.4 SEI(WebService EndPoint Interface)
SEI是web service的终端接口,就是WebService服务器端用来处理请求的接口。
2.CXF框架
这里我们只是写一下CXF发布服务和创建客户端的大概流程,不整合其他框架,只使用CXF。
2.1创建服务端
首先,创建一个maven项目(省的手动导包),jdk最好用1.7的,然后新建一个接口和其实现类,添加一个方法,重点是在准备发布的接口和其实现类上添加@webservice注解,标明这是要发布的接口。
例如,我在接口中添加了两个方法,一个直接返回信息,一个做判断后返回。
然后,我们需要发布已经写好的接口,首先我们要新建一个类,为方便理解命名为server。发布接口有两种方式,一种为Javax.xml.ws包下的Endpoint,是java自带的发布webservice的类,直接用该类的publish方法发布接口,发布的具体地址自己定义;另一种为org.apache.cxf.jaxws包下的JaxWsServerFactoryBean,是cxf的,可以设置要发布的地址,接口类,实现类,还可以添加输入输出拦截器,用拦截器可以实现接口的安全验证,添加白名单等操作。
这样服务端基本就配置完了,对了,还要在maven中添加依赖,具体依赖如下。
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-core</artifactId> <version>3.1.5</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>3.1.5</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>3.1.5</version> </dependency>
然后我们启动tomcat,在浏览器上粘贴上我们定义的地址,并在后边添加?wsdl后缀,如果能够正常打开,看到service名称地址等,说明我们发布成功。
2.2 创建客户端
在我们创建客户端前,需要下载CXF框架,我们需要用到其中的wsdl2java工具,去CXF官网下载就可以,具体下载过程不再赘述,要注意的是下载完需要配置环境变量,将其添加到path中。
现在我们新建一个maven项目,为客户端,我们创建客户端需要根据服务端发布的wsdl来生成,所以,首先我们需要启动之前的server端,然后在客户端项目中找到要生成文件的位置,在命令行中打开要生成文件的位置,用wsdl工具生成所需文件。
若运行之后,如上图所示,表示成功生成了客户端代码,生成文件如下图所示。
接下来,新建一个类,名为client,在其中new一个webservice客户端类,根据客户端类生成接口类,根据接口类来调用我们所需方法。
public class Client { public static void main(String[] args) { HelloWorldServiceService helloworldServiceService =new HelloWorldServiceService(); HelloWorldService helloworldService = helloworldServiceService.getHelloWorldServicePort(); org.apache.cxf.endpoint.Client client = ClientProxy.getClient(helloworldService); client.getOutInterceptors().add(new AddHeaderInterceptor("lili","123"));//自定义输出拦截器 client.getInInterceptors().add(new LoggingInInterceptor());//接收数据日志拦截器 client.getOutInterceptors().add(new LoggingOutInterceptor());//发送数据日志拦截器 // List<String> nums = new ArrayList<String>(); // nums.add("一号"); // nums.add("二号"); // nums.add("三号"); // nums.add("六号"); // for (String num:nums // ) { // System.out.println(helloworldService.hi(num)); // } List<User> users = new ArrayList<User>(); users.add(new User("丽丽","123")); users.add(new User("花花","123")); users.add(new User("菲菲","123")); for (User user:users ) { Authority au = helloworldService.judgAuthority(user); System.out.println(au); } } }
然后运行客户端main方法即可看到运行结果。
这是client端的输出,因为我们这是简单测试,所以在server端方法的实现中直接写死了验证,用户名必须是“丽丽”或者“菲菲”密码都必须是123,而在client端我们第一个和第三个测试例子正确,第二个不正确,所以客户端的返回如下图。
这是server端输出,server端我们让他输出他所接收到的内容。如下图。
2.3 创建拦截器
拦截器是webservice进行验证或过滤的重要方式,通过拦截器可以进行用户名密码验证(当然,你也可以在接口内部实现用户名密码验证,但是拦截器验证实在接受数据之前进行了拦截验证,更为安全严谨一些),ip验证等等,也是webservice保证接口安全的重要方式。
CXF的拦截器主要有四种,客户端的输入拦截器,输出拦截器,服务器端的输入拦截器,输出拦截器,它们的总体流程是这样的,客户端输出拦截器——>服务端输入拦截器——>服务端输出拦截器——>客户端输入拦截器。
首先我们可以先添加日志拦截器,给客户端服务端都加上输入输出拦截。主要方法为:getInInterceptor().add(要添加的拦截器),getOutInterceptor().add(要添加的拦截器)。
然后启动服务端,运行客户端,会出现以下内容:
上图是客户端的日志记录,可以详细看到客户端发送接收数据的详细情况,服务端同理,不再贴了。
现在我们定义一个验证用户名密码的拦截器,分别在客户端和服务端添加输出拦截器和输入拦截器。
首先在客户端天界一个输出拦截器,AddHeaderInterceptor,添加头信息的拦截器,用来给发送的数据的头部,添加用户名密码,方便服务端验证。注意,实际操作中,怎么验证,要服务端和客户端双方沟通,协商好通信验证的内容方式。
public class AddHeaderInterceptor extends AbstractPhaseInterceptor<SoapMessage> { private String userName; private String password; public AddHeaderInterceptor(String userName, String password) { super(Phase.PREPARE_SEND);//这里的PREPARE_SEND表示在准备发送之前进行拦截 this.userName=userName; this.password=password; } public void handleMessage(SoapMessage soapMessage) throws Fault { List<Header> headers = soapMessage.getHeaders(); //因为发送的数据是xml格式的,所以此处是通过添加节点的方式添加用户名密码 Document document = DOMUtils.createDocument(); Element element = document.createElement("Auth"); Element uElement = document.createElement("userName");//添加一个userName节点 Element pElement = document.createElement("password");//添加一个password节点 uElement.setTextContent(userName);//将用户名填写在节点中 pElement.setTextContent(password);//将密码填写在节点中 element.appendChild(uElement); element.appendChild(pElement); headers.add(new Header(new QName("auth"),element)); } }
客户端添加完成之后,同理,我们在服务端添加进行验证的拦截器。
public class MyInterceptor extends AbstractPhaseInterceptor<SoapMessage> { public MyInterceptor() { super(Phase.PRE_INVOKE);//调用方法前调用自定义拦截器 } public void handleMessage(SoapMessage soapMessage) throws Fault { List<Header> headers = soapMessage.getHeaders(); if (headers==null||headers.size()==0){ throw new Fault(new IllegalArgumentException("头部为空,请填写完整")); } //根据节点来读取数据 Header header = headers.get(0); Element element = (Element) header.getObject(); NodeList uList = element.getElementsByTagName("userName"); NodeList pList = element.getElementsByTagName("password"); if (uList.getLength()!=1){ throw new Fault(new IllegalArgumentException("用户名格式不正确")); } if (pList.getLength()!=1){ throw new Fault(new IllegalArgumentException("密码格式不正确")); } String userName = uList.item(0).getTextContent(); String password = pList.item(0).getTextContent(); if (!"lili".equals(userName)||!"123".equals(password)){ throw new Fault(new IllegalArgumentException("用户名密码不正确")); } } }
为了方便,我们直接写死用户名密码,接下来我们测试一下。
首先,测试一个错误的用户名密码,即用户名不为“lili”或者密码不为“123”,比如“feifei”,“123”
从以上两图可以看到,客户端在接受完第一条返回值后就报了用户名密码错误的异常,服务端也是在接受完第一条数据后就报了异常,说明拦截器成功了。
将客户端用户名密码改为正确的用户名密码后再次进行测试,结果正常。