历史:
Axis:-->Axis2
XFire:-->(WebService框架)
Celtrix:(ESB框架)
CXF(XFire+Celtrix)
优点:
CXF号称是SOA框架,我们做WS只会用到XFire。
CXF内置Jetty Web服务器。
使用CXF开发WebServer端组件都需要“接口”和“实现类”两部分。
支持多种数据格式:xml和JSON(Restful)。
并可以与SPRing进行快速无缝的整合
灵 活的 部 署 : ant(build.xml) maven(pom.xml)
可 以运 行 有Tomcat,Jboss,Jetty(内 置web 服 务器),IBMWebsphere,BeaWebLogic上面。
官网地址:http://cxf.apache.org/
为了方便使用CXF下的工具,把CXF/bin配置到PATH里去。当然最好先配置CXF_HOmE,然后在Path里面直接引用。
常用jar库文件:
除了jetty服务器相关的类外,有7个必须的包:
asm是字节码相关的包;
logging是日志相关的包;
cxf是框架核心包;
geronimo-servlet是servlet相关包;
neethi是网络安全相关包;
wsdl4j是wsdl解析用的包;
xmlschema是schema相关的包。
在cxf中,也提供了一个用于生成客户端调用代码的工具。它的功能就如同wsimport一样。
先让我们了解一下cxf的wsdl2java工具,可以生成一堆客户端调用的代码。
此工具位于cxf_home/bin目录下。参数与wsimport有所不同。
它主要包含以下参数:
-d参数,指定代码生成的目录。
-p参数,指定生成的新的包结构。
需要说明的是,由于wsdl2java是根据jdk1.7生成的本地代码,所以,需要对生成的代码做一点点修改。
Wsdl2java -d . -p cn.itsource.ws.cxf.aahttp://localhost:9998/hi?wsdl
1、注意:由于使用的是apache-cxf较新版本,所以支持jdk1.7的。对于生成的Service要进行稍微的修改。
在jdk1.6中 的javax.xml.ws.Service的 构 造 方 法 接 收 二个 参 数 为:(URLurl,QName qname);
在jdk1.7中的javax.xml.ws.Service的构造方法中接 收三个参数为:(URLurl,QName qname,WebServiceFeature... features);
2、将生成的代码,拷贝到项目目录,然后使用以前相同方法调用。
注意:如果你对@WebMethod设置了header=true参数,将会在调用时多传递一个参数。它参数可以直接传null值。对于这种情况,可以将header=true修改成header=false然后再重要获取客户端源代码。
3、大家可能发现了,使用cxf生成的客户端代码与wsimport差不多,甚至是一样,
那为什么还要使用cxf呢?它较好的发布方式、较容易的与其他服务器集成、及与Spring的完美结合都不得不让我们使用cxf。
此时服务类和接口,都可以不使用@WebService标注。(因为这套标准在JDK1.6之前就有了)
//服务端发布
1.创建ServerFactoryBean的对象,用于发布服务
2.设置服务发布地址
3.设置服务发布的接口
4.设置服务的发布对象
5.使用create方法发布服务
JaxWsServerFactoryBeanbean=newJaxWsServerFactoryBean();
// 2.设置服务发布地址
bean.setAddress("http://localhost:9998/hi");
// 3.设置服务发布的接口
bean.setServiceClass(IHelloService.class);//接口设置ServiceClass
// 4.设置服务的发布对象
bean.setServiceBean(new HelloServiceImpl());
//============拦截器==========//
bean.getInInterceptors().add(newLoggingInInterceptor());
bean.getInInterceptors().add(new AuthInInterceptor());
//============拦截器==========//
// 5.使用create方法发布服务
bean.create();
System.out.println("服务发布成功!");
其中设置接口实现类或设置服务类对象任意一个都可以。
//客户端:
1.创建ClientProxyFactoryBean的对象,用于接收服务
2.设置服务的发布地址,表示去哪里过去服务
3.设置服务的发布接口,使用本地的代理接口
4.通过create方法返回接口代理实例
5.调用远程方法
JaxWsProxyFactoryBeanbean=newJaxWsProxyFactoryBean();
// 2.设置服务的发布地址,表示去哪里过去服务
bean.setAddress("http://192.168.1.254:9999/hi");//不能加?wsdl
// 3.设置服务的发布接口,使用本地的代理接口
bean.setServiceClass(IHelloService.class);
//===================拦截器=======================//
bean.getOutInterceptors().add(newLoggingOutInterceptor()); //先获取框子,直接往里面放
//自定义拦截器
bean.getOutInterceptors().add(new AuthOutInterceptor("admin","1")); //先获取框子,直接往里面放
//===================拦截器=======================//
// 4.通过create方法返回接口代理实例
IHelloServicehelloService= (IHelloService)bean.create();
System.out.println(helloService.getClass().getName());
System.out.println(helloService.sayHello("小猪"));
支持@WebService等注解
JaxWsServerFactoryBean是ServerFactoryBean的子类,也是功能扩展类。
但在CXF的API文档中没有提供此类API,请通过查看源代码的方式获取此类的帮助。此类,必须要在被发布为服务的类和接口上添加@WebService注解,如果不加注解,虽然不出错,但也不会对外暴露任何方法。使用此类生成的wsdl文件更加规范,建议使用该类。
服务端:
1.创建JaxWsServerFactoryBean的对象,用于发布服务
2.设置服务发布地址
3.设置服务发布的接口
4.设置服务的发布对象
5.使用create方法发布服务
这种方式发布必须使用接口。
客户端:
1.创建JaxWsProxyFactoryBean的对象,用于接收服务
2.设置服务的发布地址,表示去哪里过去服务
3.设置服务的发布接口,使用本地的代理接口
4.通过create方法返回接口代理实例
5.调用远程方法
使用自带的日志拦截器:可以用来捕获soap的消息
LoggingInInterceptor–信息输入时的拦截器-请求
LoggingOutInterceptor–信息输出时的拦截器-响应
JaxWsServerFactoryBean bean = newJaxWsServerFactoryBean();
// 2.设置服务发布地址
bean.setAddress("http://localhost:9998/hi");
// 3.设置服务发布的接口
bean.setServiceClass(IHelloService.class);//接口设置ServiceClass
// 4.设置服务的发布对象
bean.setServiceBean(new HelloServiceImpl());
//============拦截器==========//
bean.getInInterceptors().add(new LoggingInInterceptor());
bean.getInInterceptors().add(new AuthInInterceptor());
//============拦截器==========//
// 5.使用create方法发布服务
bean.create();
System.out.println("服务发布成功!")
// 1.创建JaxWsProxyFactoryBean的对象,用于接收服务
JaxWsProxyFactoryBeanbean=newJaxWsProxyFactoryBean();
// 2.设置服务的发布地址,表示去哪里过去服务
bean.setAddress("http://localhost:9998/hi");//不能加?wsdl
// 3.设置服务的发布接口,使用本地的代理接口
bean.setServiceClass(IHelloService.class);
//===================拦截器=======================//
bean.getOutInterceptors().add(newLoggingOutInterceptor()); //先获取框子,直接往里面放
//自定义拦截器
bean.getOutInterceptors().add(new AuthOutInterceptor("admin","1")); //先获取框子,直接往里面放
//===================拦截器=======================//
// 4.通过create方法返回接口代理实例
IHelloServicehelloService= (IHelloService)bean.create();
System.out.println(helloService.getClass().getName());
System.out.println(helloService.sayHello("小猪"));
场景就是做服务的访问权限判断
拦截器使用的步骤:
1、声明一个类,来充当拦截器。这个类需要继承或实现某个接口或基类。
2、完成拦截器的逻辑。
3、把拦截器配置到访问或者发布逻辑中。
自 定 义 拦 截 器 需 要 实 现Interceptor接 口 , 一 般 的 我 们 直 接 继承AbstractPhaseInterceptor类。
做一个权限控制例子。
要求客户端把请求发送到服务端的时候,服务端根据客户端发送的SAOP消息,从<Header>里取出登录信息,以做权限验证。那么应该在客户端添加一个自定义的输出拦截器,在服务端添加一个自定义的输入拦截器。
1.1.1 客户端添加权限拦截器
得在拦截器里拼装一个SOAP消息的Header片段,如下:
<soap:Header>
<authHeader>
<username>用户名</username>
<passWord>密码</password>
</authHeader>
</soap:Header>
AuthOutInterceptor类:
publicclass AuthOutInterceptor
extendsAbstractPhaseInterceptor<SoapMessage>{
private Stringusername;
private Stringpassword;
publicAuthOutInterceptor(String username,String password) {
//在哪个阶段拦截
super(Phase.PREPARE_SEND);//准备发送SOAP消息的时候,启用拦截器,添加
this.username=username;
this.password=password;
}
public voidhandleMessage(SoapMessage msg) throws Fault {
//使用工具创建dom对象
Documentdoc = DOMUtils.createDocument();
//使用DOM对象创建符合XML规范的子元素
//创建头信息中认证信息的根
Element root =doc.createElement("autherHeader");
//创建头信息中认证信息的内容
Element nameEle =doc.createElement("username");
Element pwdEle =doc.createElement("password");
nameEle.setTextContent(username);
pwdEle.setTextContent(password);
root.appendChild(nameEle);
root.appendChild(pwdEle);
Headerheader =new Header(new QName("sirius"),root);
msg.getHeaders().add(header);
}
}
客户端代码:
publicvoid testCxf() {
JaxWsProxyFactoryBeanbean = new JaxWsProxyFactoryBean();
//设置访问地址
bean.setAddress("http://localhost:9527/hello");
//设置服务接口,直接使用本项目中的接口
bean.setServiceClass(IHelloKity.class);
//通过create方法返回接口实例
bean.getOutInterceptors().add(
new AuthOutInterceptor("sirius","1314"));
bean.getOutInterceptors().add(newLoggingOutInterceptor());
IHelloKity hello =(IHelloKity) bean.create();
String ret =hello.sayHi("sirius", 18);
System.out.println(ret);
}
添加拦截后SOAP内容如下:
信息:Outbound Message
---------------------------
ID:1
Address:http://localhost:9527/hello
Encoding:UTF-8
Content-Type:text/xml
Headers:{Accept=[*/*], SOAPAction=[""]}
Payload:
<soap:Envelopexmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<autherHeader>
<username>sirius</username>
<password>1314</password>
</autherHeader>
</soap:Header>
<soap:Body>
<ns2:sayHixmlns:ns2="http://cxf.ws.itc
<arg0>sirius</arg0>
<arg1>18</arg1>
</ns2:sayHi>
</soap:Body>
</soap:Envelope>
服务端从SOAP中去取Header消息,若取到,再根据以下格式解析XML,得到username和password数据。
<soap:Header>
<authHeader>
<username>用户名</username>
<password>密码</password>
</authHeader>
</soap:Header>
AuthInInterceptor类:
publicclass AuthInInterceptor
extendsAbstractPhaseInterceptor<SoapMessage> {
publicAuthInInterceptor() {
super(Phase.PRE_INVOKE);
System.out.println("构造器");
}
publicvoid handleMessage(SoapMessage msg) throws Fault {
List<Header>headers = msg.getHeaders();
if (headers == null|| headers.isEmpty()) {
throw new Fault(newIllegalArgumentException("没有header"));
}
Header header=headers.get(0);
//在客户端的过滤器里,把element放入了header中,所以这里可以
Element root =(Element) header.getObject();
String username =
root.getElementsByTagName("username").item(0).getTextContent();
String password =
root.getElementsByTagName("password").item(0).getTextContent();
//查询数据库验证
if (!"sirius".equals(username) || !"1314".equals(password)) {
thrownewFault(newIllegalArgumentException("用户名或密码不对!"));
}
System.out.println("恭喜你可以开始了");
}
}
服务端代码:
publicstatic void main(String[] args) {
JaxWsServerFactoryBeanbean = new JaxWsServerFactoryBean();
//绑定到发布地址的端口
bean.setAddress("http://localhost:9527/hello");
//设置服务接口,如果没有接口,则为本类
bean.setServiceClass(IHelloKity.class);
//设置服务类对象
bean.setServiceBean(newHelloKityWS());
//发布服务
bean.getInInterceptors().add(newAuthInInterceptor());
bean.create();
System.out.println("启动OK");
}
package cn.itsource.ws.cxf.server;
import java.util.List;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
importorg.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Element;
public class AuthInInterceptor extendsAbstractPhaseInterceptor<SoapMessage> {
publicAuthInInterceptor() {
super(Phase.PRE_INVOKE);//必须在执行之前,接收之后
//TODO Auto-generated constructor stub
}
@Override
publicvoid handleMessage(SoapMessage soapMessage) throws Fault {
//获取所有的头信息
List<Header>headers = soapMessage.getHeaders();
if(null == headers || headers.size()==0) {
thrownew Fault(new IllegalaccessException("请携带认证信息!"));
}
//很确定认证头就是第一个
Headerheader = headers.get(0);
if(null == header) {
thrownew Fault(new IllegalAccessException("请携带认证信息!"));
}
//获取AuthInfo节点
Elementele = (Element) header.getObject();
StringuserName = ele.getElementsByTagName("userName").item(0).getTextContent();
Stringpassword =ele.getElementsByTagName("password").item(0).getTextContent();
//可以使用用户名和密码到数据库中做验证,现在写死。
if("admin".equals(userName)&&"1".equals(password)) {
return;//直接返回就是放行
}
thrownew Fault(new IllegalAccessException("用户名和密码不正确!"));
}
}
package cn.itsource.ws.cxf.client;
import javax.xml.namespace.QName;
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;
importorg.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
*
*@author Administrator
*
*/
public class AuthOutInterceptor extendsAbstractPhaseInterceptor<SoapMessage> {
privateString userName;
privateString password;
publicAuthOutInterceptor(String userName,String password) {
super(Phase.PREPARE_SEND);//设置拦截器的阶段
this.userName= userName;
this.password= password;
}
/**
* 把权限认证的信息加到SOAP的Header里面
* <soap:Header>
<authInfo>
<userName>admin</userName>
<password>1</password>
</authInfo>
</soap:Header>
*/
@Override
publicvoid handleMessage(SoapMessage message) throws Fault {
//第一步:通过工具类获取Document对象,就可以使用对象创建权限信息的节点
Documentdocument = DOMUtils.createDocument();
//第二步:创建权限信息的节点
ElementauthInfoEle = document.createElement("authInfo");
ElementuserNameEle = document.createElement("userName");
userNameEle.setTextContent(userName);
ElementpasswordEle = document.createElement("password");
passwordEle.setTextContent(password);
authInfoEle.appendChild(userNameEle);
authInfoEle.appendChild(passwordEle);
//localPart是一个唯一标识,可以随便写
QNameqName = new QName("xxxx");// <autoInfo></authInfo>
//QNameqName = new QName(namespaceURI, localPart)//<autoInfoxmlns="http://itsource.cn"></authInfo>
//QNameqName = new QName(namespaceURI, localPart, prefix)//<autoInfoxmlns:xxx="http://itsource.cn"></authInfo>
//有两个参数:后面的一个为要添加节点,第一个为修改该节点的Namespace
Headerheader = new Header(qName , authInfoEle);
//第三步:把权限信息的节点添加到header里面
message.getHeaders().add(header);
}
publicString getUserName() {
returnuserName;
}
publicvoid setUserName(String userName) {
this.userName= userName;
}
publicString getPassword() {
returnpassword;
}
publicvoid setPassword(String password) {
this.password= password;
}
}
使用CXF的最大好处就是能快速的Spring进行集成,现在在服务端来做一个和集成Spring应用。分为如下6步:
l 建立标准的Web工程结构;
l 拷贝CXF的核心jar包;
l 拷贝Spring的核心jar包;
l 在web.xml中配置cxf的核心servlet--CXFServlet;
l 在Spring文件中配置CXF核心bean;
l 服务器端发布服务;
官方提供的lib里有75个包,但不是所有的我们都需要,在这里抽取一个mini版的CXF库,一共7个包。
导入17spring依赖包:
在resource中添加:
applicationContext.xml
在web.xml中配置spring上下文:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
在web.xml中配置cxf的核心servlet—CXFServlet
在web.xml中添加CXF核心servlet
<servlet>
<servlet-name>cxf</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>cxf</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
通过查看前面配置的CXFServlet,可以看出,如果没有在工程WEB-INF下有个cxf-servlet.xml,在servlet代码执行时不会创建spring环境。
这时我们直接在applicationContext中手动集成CXF和Spring
<!-- cxf的核心bean -->
<import resource="classpath:META-INF/cxf/cxf.xml" />
<!-- SOAP相关配置 -->
<importresource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
服务端的发布分为两种情况:
<!—1、简单发布(不支持接口,实现类不能直接实例化)-->
<beanid="hellobean" class="test.HelloKityWSImpl"/>
<jaxws:endpointid="hello" implementor="#hellobean" address="/hi"/>
<!-- 2、通过接口的方式发布服务 -->
<beanid="hellobean" class="test.HelloKityWSImpl" />
<jaxws:server id="hello"address="/hi" serviceClass="test.IHelloKity">
<jaxws:serviceBean>
<refbean="hellobean" />
</jaxws:serviceBean>
<!-- 添加拦截器 -->
<jaxws:inInterceptors>
<beanclass="org.apache.cxf.interceptor.LoggingInInterceptor" />
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<beanclass="org.apache.cxf.interceptor.LoggingOutInterceptor" />
</jaxws:outInterceptors>
</jaxws:server>
第一种达到的效果和最初使用JDK中的Endpoint.publish效果相同,可以直接理解为这样配置,由Spring容器自动调用上面方法发布服务。这种发布不支持接口,应用不广。
第二种方式配置更为细腻,做到了接口和实现类的分离,配置灵活性更大
红色部分是拦截器配置,与之前代码中添加的拦截器效果相相同。
完成了上面配置,整个服务端服务就发布成功了。
使用CXF的最大好处就是能快速的Spring进行集成,现在在客户端来做一个和集成Spring应用。分为如下5步:
l 建立标准的Web工程结构;
l 拷贝CXF的核心jar包;
l 拷贝Spring的核心jar包;
l 在Spring文件中配置服务代理接口;
l 客户端使用服务;
官方提供的lib里有75个包,但不是所有的我们都需要,在这里抽取一个mini版的CXF库,一共7个包。
导入17spring依赖包:
在resource中添加:
applicationContext.xml
在web.xml中配置spring上下文:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
在客户端先找到需要消费服务的WSDL文件地址,使用wsimport导入服务生成客户端代码,保留接口和接口所依赖的domain。其他都删掉。
这时我们直接在applicationContext中手动配置需要消费的接口
<!-- 客户端通过jaxws:client标签配置以后,就可以当成本地bean来进行使用 -->
<jaxws:client id="hello"
serviceClass="ws.client.test.IHelloKity"
wsdlLocation="http://localhost:8080/ws/hi?wsdl"/>
在客户端的编写测试代码:
public class TestClient {
@Test
public void testCall() throws Exception {
ApplicationContext ctx =
newClassPathXmlApplicationContext("applicationContext.xml");
IHelloKityservice = ctx.getBean("hello", IHelloKity.class);
System.out.println("service: " +service.sayHi("sirius"));
}
}
得到打印结果:
service : sirius,你好!
新闻热点
疑难解答