因为最近开发的项目需求中涉及到了webservice,正好对这块知识以前学过但是又忘记了,于是想着从新学习下,整理一个笔记,便于后面的复习。于是有了本文,下面开始介绍webservice。
一、简介
大家或多或少都听过 WebService(Web服务),有一段时间甚至很多计算机期刊、书籍和网站都大肆的提及和宣传WebService技术,其中不乏很多吹嘘和做广告的成 分。但是不得不承认的是WebService真的是一门新兴和有前途的技术,那么WebService到底是什么?何时应该用?下面将会详细介绍,这一节我们先有一个感性认识。具体举个例子,比如在Windows Server服务器上有个C#.Net开发的应用A,在Linux上有个Java语言开发的应用B,B应用要调用A应用,或者是互相调用。用于查看对方的业务数据。再举个例子,天气预报接口。无数的应用需要获取天气预报信息;这些应用可能是各种平台,各种技术实现;而气象局的项目,估计也就一两种,要对外提供天气预报信息,这个时候,如何解决呢?这些应用的时候的都可以通过WebService来很好的实现其应用。通过Web Service,客户端和服务器才能够自由的用HTTP进行通信,不论两个程序的平台和编程语言是什么。当然有人会说使用Socket通信业可以达到效果,但是两者之间还是有区别的。比如:
客户端:
package com.pony1223; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; public class WeatherClient { public static void main(String[] args) throws UnknownHostException, IOException { //1.创建Socket对象,和服务端建立连接 Socket socket = new Socket("127.0.0.1",12345); //2.发送城市名称 DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeUTF("北京"); System.out.println("请求查询天气: 北京"); //3.接受返回结果使用输入流 DataInputStream dis = new DataInputStream(socket.getInputStream()); String result = dis.readUTF(); System.out.println("北京的天气: " + result); //4.关闭流 dis.close(); dos.close(); } }
服务端:
package com.pony1223; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class WeatherServer { public static void main(String[] args) throws IOException{ // 1.创建ServerSocket对象 ServerSocket serverSocket = new ServerSocket(12345); while(true){ // 2.等待客户端连接,阻塞的方法 final Socket socket = serverSocket.accept(); Runnable runnable = new Runnable(){ @Override public void run(){ try{ // 3.使用输入流接受客户端发送的请求 DataInputStream dis = new DataInputStream(socket.getInputStream()); String cityName = dis.readUTF(); System.out.println("接收到客户端发送的请求: " + cityName); Thread.sleep(1000); // 4.根据城市名查询天气 String result = "今天天气很热"; System.out.println("返回天气信息: " + result); // 5.返回查询结果,使用输出流。 DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeUTF(result); // 6.关闭流 dis.close(); dos.close(); }catch(Exception e){ e.printStackTrace(); } } }; //启动线程 new Thread(runnable).start(); } } }
输出结果为:
请求查询天气: 北京
北京的天气: 今天天气很热
接收到客户端发送的请求: 北京
返回天气信息: 今天天气很热
然后我们采用WebService来实现下:
第一步: 创建一个java工程,不需要导入jar包。
第二步: 创建接口。在Webservice中叫做SEI(wsdl中叫做PortType)。
第三步: 创建实现类。需要在实现类上加一个@Webservice注解。
第四步: 发布服务。EndPoint.publish发布服务。
WeatherInterface.java
package com.pony1223.wsservice; import javax.jws.WebService; @WebService public interface WeatherInterface { String getWeatherByCity(String city); }
实现类:
package com.pony1223.wsservice.impl; import javax.jws.WebService; import com.pony1223.wsservice.WeatherInterface; @WebService public class WeatherInterfaceImpl implements WeatherInterface { @Override public String getWeatherByCity(String city) { System.out.println("接收客户端发送过来的城市名字:"+city); //调用天气等服务 //..... //这里模拟所以直接返回结果 String result = "天气比较冷"; System.out.println("返回天气查询结果:"+result); return result; } }
WeatherServer.java 发布服务;
package com.pony1223.test; import javax.xml.ws.Endpoint; import com.pony1223.wsservice.WeatherInterface; import com.pony1223.wsservice.impl.WeatherInterfaceImpl; public class WeatherServer { public static void main(String[] args) { WeatherInterface server = new WeatherInterfaceImpl(); String address = "http://192.168.31.159:1111/WeatherInterface"; Endpoint.publish(address, server); } }
看看服务是否发布成功,访问wsdl:http://192.168.31.159:1111/WeatherInterface?wsdl
可以看到服务发布成功:
客户端代码编写,可以采用工具生成:
刷新工程:
因此我们只需要编写client的调用即可:
第1步:创建服务视图对象。
第2步: 从服务视图中获得PortType对象。
第3步: 调用PortType的方法(可以实现远程通信)
第4步: 接收方法的返回值(服务端响应的结果)。
package com.pony1223.wsservice.impl; public class Client { public static void main(String[] args) { WeatherInterfaceImplService server = new WeatherInterfaceImplService(); WeatherInterfaceImpl impl = server.getWeatherInterfaceImplPort(); System.out.println(impl.getWeatherByCity("北京")); } }
输出结果:
天气比较冷
接收客户端发送过来的城市名字:北京
返回天气查询结果:天气比较冷
上面编写了两种方式,那么区别是什么?
(1)Socket是基于TCP/IP的传输层协议。
Webservice是基于HTTP协议传输数据,http是基于tcp的应用层协议。
Webservice采用了基于http的soap协议传输数据。
(2)Socket接口通过流传输,不支持面向对象。
Webservice 接口支持面向对象,最终webservice将对象进行序列化后通过流传输。
Webservice采用soap协议进行通信,不需专门针对数据流的发送和接收进行处理,是一种跨平台的面向对象远程调用技术。
(3)Socket适用于高性能大数据的传输,传输的数据需要手动处理,socket通信的接口协议需要自定义。
比如:自定义一种字符串拼接的格式,比如自定义的xml数据,自定义麻烦之处在接口调用方和接口服务端需要互相讨论确定接口的协议内容,不方便。
缺点
程序员需要自己去解析输入、输出流,解析发送和接收的数据。数据传输的格式不固定,需要程序员开发socket接口时自定义接口协议。
优点
如果要传输大数据量,socket可以满足,如果存在大并发使用socket也可以实现,程序用socket灵活性更大,比如可以socket的高并发框架mina开发。
Webservcie由于是遵循标准的soap协议,soap 协议的内容格式固定,soap协议传递的内容是xml数据,由于webservice是基于http的,所以简单理解为soap=http+xml,适用于没有性能要求情况下且数据传输量小,推荐在公开接口上使用webservice,因为soap协议的标准的。
优点
jaxws可以通过面向对象开发webservice,程序员不需要解析输入、输出流。
由于webservice传输数据使用标准的soap协议(基于http传输xml),soap协议已经被w3c管理了。
缺点
如果传输大数据量,webservice不适用。如果webservice开发大并发的应用,webservice依靠web容器提高并发数。
说明:大部分场景,WebService 已经足够使用,所以本文的的重点是webservice.
二、WebService的本质
一句话:WebService是一种跨语言和跨平台的远程调用技术。
所谓跨编程语言和跨操作平台,就是说服务端程序采用java编写,客户端程序则可以采用其他编程语言编写,反之亦然!跨操作系统平台则是指服务端程序和客户端程序可以在不同的操作系统上运行。
所谓远程调用,就是一台计算机a上 的一个程序可以调用到另外一台计算机b上的一个对象的方法,譬如,银联提供给商场的pos刷卡系统,商场的POS机转账调用的转账方法的代码其实是跑在银 行服务器上。再比如,amazon,天气预报系统,淘宝网,校内网,百度等把自己的系统服务以webservice服务的形式暴露出来,让第三方网站和程 序可以调用这些服务功能,这样扩展了自己系统的市场占有率,往大的概念上吹,就是所谓的SOA应用。
其实可以从多个角度来理解 WebService,从表面上看,WebService就是一个应用程序向外界暴露出一个能通过Web进行调用的API,也就是说能用编程的方法通过 Web来调用这个应用程序。我们把调用这个WebService的应用程序叫做客户端,而把提供这个WebService的应用程序叫做服务端。从深层次 看,WebService是建立可互操作的分布式应用程序的新平台,是一个平台,是一套标准。它定义了应用程序如何在Web上实现互操作性,你可以用任何 你喜欢的语言,在任何你喜欢的平台上写Web service ,只要我们可以通过Web service标准对这些服务进行查询和访问。
WebService平台需要一套协议来实现分布式应用程序的创建。任何平台都有它的数据表示方法和类型系统。要实现互操作性,WebService平台 必须提供一套标准的类型系统,用于沟通不同平台、编程语言和组件模型中的不同类型系统。Web service平台必须提供一种标准来描述 Web service,让客户可以得到足够的信息来调用这个Web service。最后,我们还必须有一种方法来对这个Web service进行远 程调用,这种方法实际是一种远程过程调用协议(RPC)。为了达到互操作性,这种RPC协议还必须与平台和编程语言无关。
三、WebService的技术基础
XML+XSD,SOAP和WSDL就是构成WebService平台的三大技术。
3.1、XML+XSD
WebService采用HTTP协议传输数据,采用XML格式封装数据(即XML中说明调用远程服务对象的哪个方法,传递的参数是什么,以及服务对象的 返回结果是什么)。XML是WebService平台中表示数据的格式。除了易于建立和易于分析外,XML主要的优点在于它既是平台无关的,又是厂商无关 的。无关性是比技术优越性更重要的:软件厂商是不会选择一个由竞争对手所发明的技术的。
XML解决了数据表示的问题,但它没有定义一套标准的数据类型,更没有说怎么去扩展这套数据类型。例如,整形数到底代表什么?16位,32位,64位?这 些细节对实现互操作性很重要。XML Schema(XSD)就是专门解决这个问题的一套标准。它定义了一套标准的数据类型,并给出了一种语言来扩展这套数据类型。WebService平台就 是用XSD来作为其数据类型系统的。当你用某种语言(如VB.NET或C#)来构造一个Web service时,为了符合WebService标准,所 有你使用的数据类型都必须被转换为XSD类型。你用的工具可能已经自动帮你完成了这个转换,但你很可能会根据你的需要修改一下转换过程。
3.2、SOAP
WebService通过HTTP协议发送请求和接收结果时,发送的请求内容和结果内容都采用XML格式封装,并增加了一些特定的HTTP消息头,以说明 HTTP消息的内容格式,这些特定的HTTP消息头和XML内容格式就是SOAP协议。SOAP提供了标准的RPC方法来调用Web Service。
SOAP协议 = HTTP协议 + XML数据格式
SOAP协议定义了SOAP消息的格式,SOAP协议是基于HTTP协议的,SOAP也是基于XML和XSD的,XML是SOAP的数据编码方式。打个比 喻:HTTP就是普通公路,XML就是中间的绿色隔离带和两边的防护栏,SOAP就是普通公路经过加隔离带和防护栏改造过的高速公路。
3.3、WSDL
好比我们去商店买东西,首先要知道商店里有什么东西可买,然后再来购买,商家的做法就是张贴广告海报。 WebService也一样,WebService客户端要调用一个WebService服务,首先要有知道这个服务的地址在哪,以及这个服务里有什么方 法可以调用,所以,WebService务器端首先要通过一个WSDL文件来说明自己家里有啥服务可以对外调用,服务是什么(服务中有哪些方法,方法接受 的参数是什么,返回值是什么),服务的网络地址用哪个url地址表示,服务通过什么方式来调用。
WSDL(Web Services Description Language)就是这样一个基于XML的语言,用于描述Web Service及其函数、参数和返回值。它是WebService客户端和服务器端都 能理解的标准格式。因为是基于XML的,所以WSDL既是机器可阅读的,又是人可阅读的,这将是一个很大的好处。一些最新的开发工具既能根据你的 Web service生成WSDL文档,又能导入WSDL文档,生成调用相应WebService的代理类代码。
WSDL 文件保存在Web服务器上,通过一个url地址就可以访问到它。客户端要调用一个WebService服务之前,要知道该服务的WSDL文件的地址。 WebService服务提供商可以通过两种方式来暴露它的WSDL文件地址:1.注册到UDDI服务器,以便被人查找;2.直接告诉给客户端调用者。
四、WebService知识小节
1、WebService是什么?
1.1 基于Web的服务:服务器端整出一些资源让客户端应用访问(获取数据)
1.2 一个跨语言、跨平台的规范(抽象)
1.3 多个跨平台、跨语言的应用间通信整合的方案(实际)
2、为什么要用 Web service?
web service能解决:跨平台调用 跨语言调用 远程调用
3、什么时候使用web Service?
3.1. 同一家公司的新旧应用之间
3.2. 不同公司的应用之间
3.3. 一些提供数据的内容聚合应用:天气预报、股票行情
4、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服务器端和客户端的框架。
五、WebService实战
1.使用CXF开发WebService服务器端接口
CXF主页:http://cxf.apache.org/
1.1首先建一个Maven的j2se项目;项目的jre用1.7,因为1.7有webservice的默认实现。不要用1.5 不然下面你用我的代码会有问题,用1.5的话,还需要另外加jar包。
根据规范,我们先建一个接口类:HelloWorld
package com.pony1223.wsservice; import javax.jws.WebService; @WebService public interface HelloWorld { String say(String str); }
再建一个具体的实现类:HelloWorldImpl
package com.pony1223.wsservice.impl; import javax.jws.WebService; import com.pony1223.wsservice.HelloWorld; @WebService public class HelloWorldImpl implements HelloWorld { @Override public String say(String str) { return "Hello "+str; } }
最后建一个发布服务的主类:Server
package com.pony1223.test; import javax.xml.ws.Endpoint; import com.pony1223.wsservice.HelloWorld; import com.pony1223.wsservice.impl.HelloWorldImpl; public class Server { public static void main(String[] args) { System.out.println("start......"); HelloWorld hw = new HelloWorldImpl(); String address = "http://192.168.31.151:1122/helloworld"; Endpoint.publish(address, hw); System.out.println("started......"); } }
这里的Endpoint是Jdk自身实现的WebService。所以到这里我们不需要用到CXF的任何东西。
这里的address,写上自己的本机IP
我们运行下Server类:
运行效果如下:
服务已发布成功。
下面我们介绍使用CXF来实现webservice接口:
我们先在pom.xml中加入:
<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>
这里要额外加入jetty,作为webservice发布的服务器。jetty是一个内嵌的web服务器
我们把Server改下。换成CXF实现:
import org.apache.cxf.jaxws.JaxWsServerFactoryBean; import com.java1234.webservice.impl.HelloWorldImpl; public class Server { public static void main(String[] args) { System.out.println("web service start"); HelloWorld implementor = new HelloWorldImpl(); String address = "http://192.168.1.103/helloWorld"; // Endpoint.publish(address, implementor); // JDK实现 JaxWsServerFactoryBean factoryBean = new JaxWsServerFactoryBean(); factoryBean.setAddress(address); // 设置暴露地址 factoryBean.setServiceClass(HelloWorld.class); // 接口类 factoryBean.setServiceBean(implementor); // 设置实现类 factoryBean.create(); System.out.println("web service started"); } }
运行效果和刚才一样
2.使用CXF开发WebService客户端
前面一讲开发了webservice服务器端接口,今天的话,我们来开发webservice客户端,让大家来体验下过程;
首先建一个Maven项目,项目名字,WS_Client;
然后我们要用CXF给我们提供的工具wsdl2java 来根据请求的url生成客户端代码;
wsdl2java工具在CXF开发包里;开发下载地址:http://cxf.apache.org/download.html
下载二进制包,然后解压到D盘 ,wsdl2java命令;当然要用的话,还得配置Path。我们打开环境变量配置,加入路径 D:apache-cxf-3.1.5in 可能你的和我不一样;
现在我们要干的事是在我们项目里生成我们需要的webservice客户端代码,
我们找到项目的本地路径,
然后我们进入dos,进入上面的本地硬盘地址,然后执行命令:wsdl2java http://192.168.1.103/helloWorld?wsdl
这样就完成了代码的生成,我们刷新下工程:最关键的代码是HelloWorldService.java 我们下面写请求主类要用到;
我们下面写下主类 Client ,自己建下:
public class Client { public static void main(String[] args) { HelloWorldService service=new HelloWorldService(); HelloWorld helloWorld=service.getHelloWorldPort(); System.out.println(helloWorld.say("java")); } }
运行后即可调用服务端代码。
3.CXF处理JavaBean以及复合类型
前面讲的是处理简单类型,今天这里来讲下CXF处理JavaBean以及复合类型,比如集合;
这里实例是客户端传一个JavaBean,服务器端返回集合类型;
在原来的项目实例基础上,我们先创建一个实体类User:
/** * 用户实体类 * @author Administrator * */ public class User { private Integer id; // 编号 private String userName; // 用户名 private String password; // 密码 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
再创建一个Role实体类:
/** * 角色实体 * @author Administrator * */ public class Role { private Integer id; // 编号 private String roleName; // 角色名称 public Role() { super(); // TODO Auto-generated constructor stub } public Role(Integer id, String roleName) { super(); this.id = id; this.roleName = roleName; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } }
然后HelloWorld再加一个接口方法getRoleByUser,通过用户查找角色:
import java.util.List; import javax.jws.WebService; import com.java1234.entity.Role; import com.java1234.entity.User; @WebService public interface HelloWorld { public String say(String str); public List<Role> getRoleByUser(User user); }
然后HelloWorld接口实现类 HelloWorldImpl写下新增的方法的具体实现,我们这里写死,模拟下即可:
import java.util.ArrayList; import java.util.List; import javax.jws.WebService; import com.java1234.entity.Role; import com.java1234.entity.User; import com.java1234.webservice.HelloWorld; @WebService public class HelloWorldImpl implements HelloWorld{ public String say(String str) { return "Hello "+str; } public List<Role> getRoleByUser(User user) { List<Role> roleList=new ArrayList<Role>(); // 模拟 直接写死 if(user!=null){ if(user.getUserName().equals("java1234") && user.getPassword().equals("123456")){ roleList.add(new Role(1,"技术总监")); roleList.add(new Role(2,"架构师")); }else if(user.getUserName().equals("jack") && user.getPassword().equals("123456")){ roleList.add(new Role(3,"程序员")); } return roleList; }else{ return null; } } }
服务端其他地方不用动;客户端代码从新生成。改下Client类:
import java.util.List; public class Client { public static void main(String[] args) { HelloWorldService service=new HelloWorldService(); HelloWorld helloWorld=service.getHelloWorldPort(); //System.out.println(helloWorld.say("java1234")); User user=new User(); user.setUserName("jack"); user.setPassword("123456"); List<Role> roleList=helloWorld.getRoleByUser(user); for(Role role:roleList){ System.out.println(role.getId()+","+role.getRoleName()); } } }
运行截图:
4.CXF处理一些Map等复杂类型
前面讲的一些都是简单类型,cxf都支持。但是有些复杂类型,cxf是不支持,比如常用的Map类型;
下面我们在前面的实例基础上在加一个方法,比如我们现在有个需求,获取所有用用户以及对应的每个用户所有角色信息;
服务器端:
HelloWorld接口加方法:
/** * 获取所有用户以及对应的角色 * @return */ public Map<String,List<Role>> getRoles();
HelloWorldImpl实现类加方法实现:
public Map<String, List<Role>> getRoles() { Map<String,List<Role>> map=new HashMap<String,List<Role>>(); List<Role> roleList1=new ArrayList<Role>(); roleList1.add(new Role(1,"技术总监")); roleList1.add(new Role(2,"架构师")); map.put("java1234", roleList1); List<Role> roleList2=new ArrayList<Role>(); roleList2.add(new Role(1,"程序员")); map.put("jack", roleList2); return map; }
然后我们启动Server类:发现报错:
这个报错信息说,不支持该类型;
这里我们有好几种解决方案,这里我们用最常用的一种,使用适配器,把cxf不能接受的类型通过适配器,转能接受的类型。
我们使用@XmlJavaTypeAdapter注解,加在接口定义上,完整接口代码如下:
@WebService public interface HelloWorld { public String say(String str); public List<Role> getRoleByUser(User user); /** * 获取所有用户以及对应的角色 * @return */ @XmlJavaTypeAdapter(MapAdapter.class) public Map<String,List<Role>> getRoles(); }
这里参数需要一个实现了XmlAdapter类的适配器类;
/** * Map适配器 * @author Administrator * */ public class MapAdapter extends XmlAdapter<MyRole[], Map<String,List<Role>>>{ /** * 适配转换 MyRole[] -> Map<String, List<Role>> */ @Override public Map<String, List<Role>> unmarshal(MyRole[] v) throws Exception { Map<String, List<Role>> map=new HashMap<String,List<Role>>(); for(int i=0;i<v.length;i++){ MyRole r=v[i]; map.put(r.getKey(), r.getValue()); } return map; } /** * 适配转换 Map<String, List<Role>> -> MyRole[] */ @Override public MyRole[] marshal(Map<String, List<Role>> v) throws Exception { MyRole[] roles=new MyRole[v.size()]; int i=0; for(String key:v.keySet()){ roles[i]=new MyRole(); roles[i].setKey(key); roles[i].setValue(v.get(key)); i++; } return roles; } }
这里的话XmlAdapter要加两个参数,XmlAdapter<ValueType,BoundType>
ValueType是cxf能接收的类型,这里我用了数组;
BoundType是cxf不能接受的类型,也就是我例子里的需求的Map类型;
这里大家会看到,还有一个MyRole自定义类型,key:value。我们搞成两个属性,具体实现如下:
/** * 自定义实体 cxf能接受 * @author Administrator * */ public class MyRole { private String key; private List<Role> value; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public List<Role> getValue() { return value; } public void setValue(List<Role> value) { this.value = value; } }
OK 这样就行了。我们运行Server类,发布webservice接口:
然后就到了webservice客户端,我们用wsdl2java工具生成下最新代码,具体过程前面讲过,这里不重复讲:修改下Client类:
public class Client { public static void main(String[] args) { HelloWorldService service=new HelloWorldService(); HelloWorld helloWorld=service.getHelloWorldPort(); //System.out.println(helloWorld.say("java1234")); /*User user=new User(); user.setUserName("jack"); user.setPassword("123456"); List<Role> roleList=helloWorld.getRoleByUser(user); for(Role role:roleList){ System.out.println(role.getId()+","+role.getRoleName()); }*/ MyRoleArray array=helloWorld.getRoles(); List<MyRole> roleList=array.item; for(int i=0;i<roleList.size();i++){ MyRole my=roleList.get(i); System.out.print(my.key+":"); for(Role r:my.value){ System.out.print(r.getId()+","+r.getRoleName()+" "); } System.out.println(); } } }
运行效果:
5.CXF添加拦截器
今天开始讲下拦截器,前面大家学过servlet,struts2 都有拦截器概念,主要作用是做一些权限过滤,编码处理等;
webservice也可以加上拦截器,我们可以给webservice请求加权限判断功能;
webservice分服务端和客户端,服务端和客户端都是可以加拦截器的,无论是服务端还是客户端,都分进,出(In,Out)拦截器;
我们先来改下服务端的Server类:
import org.apache.cxf.interceptor.LoggingInInterceptor; import org.apache.cxf.interceptor.LoggingOutInterceptor; import org.apache.cxf.jaxws.JaxWsServerFactoryBean; import com.java1234.webservice.impl.HelloWorldImpl; public class Server { public static void main(String[] args) { System.out.println("web service start"); HelloWorld implementor = new HelloWorldImpl(); String address = "http://192.168.1.103/helloWorld"; JaxWsServerFactoryBean factoryBean = new JaxWsServerFactoryBean(); factoryBean.setAddress(address); // 设置暴露地址 factoryBean.setServiceClass(HelloWorld.class); // 接口类 factoryBean.setServiceBean(implementor); // 设置实现类 factoryBean.getInInterceptors().add(new LoggingInInterceptor()); // 添加in拦截器 日志拦截器 factoryBean.getOutInterceptors().add(new LoggingOutInterceptor()); // 添加out拦截器 factoryBean.create(); System.out.println("web service started"); } }
这里的话,我们通过factoryBean对象可以获取拦截器组,添加进或者出拦截器,这里有个经典的拦截器,我们开发的时候经常用,就是日志拦截器,
我们可以把客户端的请求,以及服务端返回的信息打印出来,可以打印控制台,也可以打印到执行文件;这里为了演示方便,直接搞无参的拦截器,
打印到控制台;
无论是自定义的拦截器,还是CXF自带的拦截器,都必须实现Interceptor接口。
同理,我们在客户端也可以加进出拦截器,修改Client代码:
import java.util.List; import org.apache.cxf.frontend.ClientProxy; import org.apache.cxf.interceptor.LoggingInInterceptor; import org.apache.cxf.interceptor.LoggingOutInterceptor; public class Client { public static void main(String[] args) { HelloWorldService service=new HelloWorldService(); HelloWorld helloWorld=service.getHelloWorldPort(); org.apache.cxf.endpoint.Client client=ClientProxy.getClient(helloWorld); client.getInInterceptors().add(new LoggingInInterceptor()); // 添加in拦截器 日志拦截器 client.getOutInterceptors().add(new LoggingOutInterceptor()); // 添加out拦截器 //System.out.println(helloWorld.say("java1234")); /*User user=new User(); user.setUserName("jack"); user.setPassword("123456"); List<Role> roleList=helloWorld.getRoleByUser(user); for(Role role:roleList){ System.out.println(role.getId()+","+role.getRoleName()); }*/ MyRoleArray array=helloWorld.getRoles(); List<MyRole> roleList=array.item; for(int i=0;i<roleList.size();i++){ MyRole my=roleList.get(i); System.out.print(my.key+":"); for(Role r:my.value){ System.out.print(r.getId()+","+r.getRoleName()+" "); } System.out.println(); } } }
这里的话,我们用到了ClientProxy,客户端代理。
6.CXF添加自定义拦截器
前面我们说到CXF添加内置的拦截器,今天的话,我们来讲下如何添加自定义拦截器;
我们的实例是客户端访问服务端webservice接口要加权限认证。
我们思路先说下。我们可以通过在SOAP消息的Header头信息中添加自定义信息,然后发送到服务端端,服务器端通过获取
Header头消息,然后进行认证;这里的添加消息,和获取消息认证,我们都是通过自定义拦截器来实现;
OK下面我们来实现下:
首先是服务器端:
我们自定义拦截器:MyInterceptor
/** * 自定义拦截器 * @author Administrator * */ public class MyInterceptor extends AbstractPhaseInterceptor<SoapMessage>{ public MyInterceptor(){ // 在调用方法之前调用拦截器 super(Phase.PRE_INVOKE); } /** * 拦截获取消息 */ public void handleMessage(SoapMessage message) throws Fault { List<Header> headers=message.getHeaders(); if(headers==null || headers.size()==0){ throw new Fault(new IllegalArgumentException("没有Header,拦截器实施拦截")); } Header firstHeader=headers.get(0); Element ele=(Element) firstHeader.getObject(); NodeList userIds=ele.getElementsByTagName("userName"); NodeList userPasses=ele.getElementsByTagName("password"); if(userIds.getLength()!=1){ throw new Fault(new IllegalArgumentException("用户名格式不对")); } if(userPasses.getLength()!=1){ throw new Fault(new IllegalArgumentException("密码格式不对")); } String userId=userIds.item(0).getTextContent(); String userPass=userPasses.item(0).getTextContent(); if(!userId.equals("java1234") || ! userPass.equals("123456")){ throw new Fault(new IllegalArgumentException("用户名或者密码不正确")); } } }
这里的话,我们主要是获取Header头消息,然后获取userName和password节点,然后获取值,进行权限判断,假如认证不通过,我们抛出异常;
在Server类里,我们要添加一个in 拦截器,在进入的时候,我们要进行验证;
public class Server { public static void main(String[] args) { System.out.println("web service start"); HelloWorld implementor = new HelloWorldImpl(); String address = "http://10.10.7.18/helloWorld"; JaxWsServerFactoryBean factoryBean = new JaxWsServerFactoryBean(); factoryBean.setAddress(address); // 设置暴露地址 factoryBean.setServiceClass(HelloWorld.class); // 接口类 factoryBean.setServiceBean(implementor); // 设置实现类 factoryBean.getInInterceptors().add(new LoggingInInterceptor()); // 添加in拦截器 日志拦截器 factoryBean.getOutInterceptors().add(new LoggingOutInterceptor()); // 添加out拦截器 factoryBean.getInInterceptors().add(new MyInterceptor()); // 添加自定义拦截器 factoryBean.create(); System.out.println("web service started"); } }
接下来是修改客户端代码:
我们同样要添加一个自定义拦截器:AddHeaderInterceptor
public class AddHeaderInterceptor extends AbstractPhaseInterceptor<SoapMessage> { private String userName; private String password; public AddHeaderInterceptor(String userName, String password) { super(Phase.PREPARE_SEND); // 发送SOAP消息之前调用拦截器 this.userName=userName; this.password=password; } public void handleMessage(SoapMessage message) throws Fault { List<Header> headers=message.getHeaders(); Document doc=DOMUtils.createDocument(); Element ele=doc.createElement("authHeader"); Element idElement=doc.createElement("userName"); idElement.setTextContent(userName); Element passElement=doc.createElement("password"); passElement.setTextContent(password); ele.appendChild(idElement); ele.appendChild(passElement); headers.add(new Header(new QName("java1234"),ele)); }
这里的话,我们主要是在拦截器里创建头消息;
Client类里我们要修改下,加下Out 拦截器:
public class Client { public static void main(String[] args) { HelloWorldService service=new HelloWorldService(); HelloWorld helloWorld=service.getHelloWorldPort(); org.apache.cxf.endpoint.Client client=ClientProxy.getClient(helloWorld); // client.getInInterceptors().add(new LoggingInInterceptor()); // 添加in拦截器 日志拦截器 client.getOutInterceptors().add(new AddHeaderInterceptor("java1234","123456")); // 添加自定义拦截器 client.getOutInterceptors().add(new LoggingOutInterceptor()); // 添加out拦截器 //System.out.println(helloWorld.say("java1234")); /*User user=new User(); user.setUserName("jack"); user.setPassword("123456"); List<Role> roleList=helloWorld.getRoleByUser(user); for(Role role:roleList){ System.out.println(role.getId()+","+role.getRoleName()); }*/ MyRoleArray array=helloWorld.getRoles(); List<MyRole> roleList=array.item; for(int i=0;i<roleList.size();i++){ MyRole my=roleList.get(i); System.out.print(my.key+":"); for(Role r:my.value){ System.out.print(r.getId()+","+r.getRoleName()+" "); } System.out.println(); } } }
7.Spring整合CXF之发布WebService服务
今天我们来讲下如何用Spring来整合CXF,来发布WebService服务;
给下官方文档地址:http://cxf.apache.org/docs/writing-a-service-with-spring.html
根据官方文档。我们把前面的实例用Spring整合CXF来处理下。会简化很多;
首先我们来建一个Maven项目 WebService_CXF
建好项目第一步,我们打开pom.xml
我们来添加下Spring支持:
<!-- 添加Spring支持 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.1.7.RELEASE</version> </dependency> 接下来添加下CXF支持: <!-- 添加cxf支持 --> <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</artifactId> <version>3.1.5</version> </dependency>
我们在项目里添加下 applicationContext.xml spring配置文件 我们要额外添加下命名路径,因为我们要用新的标签;
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
这里的我是参考官方文档上,添加了 jaxws支持。。大家直接贴下即可;
然后我们再导入下cxf里的一些bean配置,参考官方文档:
<import resource="classpath:META-INF/cxf/cxf.xml"/> <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
这里的HelloWorldImpl类上,我们加一个 @Component("helloWorld")
Spring配置文件里,我加下扫描:
<!-- 自动扫描 --> <context:component-scan base-package="com.java1234.webservice" />
前面搞完后,我们在处理下web.xml文件 首先启动的时候,必须加载Spring:
<!-- Spring配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- Spring监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
然后我们要定义一个Servlet,主要是处理WebService请求:
<servlet> <servlet-name>CXFServlet</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/webservice/*</url-pattern> </servlet-mapping>
这里的话,我们所有的 /webservice请求,都交给CXFServlet类处理;
最后一步,我们在Spring配置文件里,定义下webservice接口发布:
<!-- 定义服务提供者 --> <jaxws:endpoint implementor="#helloWorld" address="/HelloWorld" ></jaxws:endpoint>
这里implementor指定webservice接口实现类
address是具体的接口路径
最终完整的applicationContext.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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"> <import resource="classpath:META-INF/cxf/cxf.xml"/> <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/> <!-- 自动扫描 --> <context:component-scan base-package="com.java1234.webservice" /> <!-- 定义服务提供者 --> <jaxws:endpoint implementor="#helloWorld" address="/HelloWorld" ></jaxws:endpoint> </beans>
我们来启动下项目,然后访问 http://localhost:8080/WebService_CXF/webservice/
8.Spring整合CXF之添加拦截器
<!-- 定义服务提供者 --> <jaxws:endpoint implementor="#helloWorld" address="/HelloWorld"> <!-- 添加in拦截器 --> <jaxws:inInterceptors> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/> <bean class="com.java1234.interceptor.MyInterceptor"/> </jaxws:inInterceptors> <!-- 添加out拦截器 --> <jaxws:outInterceptors> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/> </jaxws:outInterceptors> </jaxws:endpoint>
参考资料:
http://www.cnblogs.com/xdp-gacl
java1234