基于CXF的WebServie技术应用
基于CXF的WebServie技术应用
1 概述
面向服务的体系结构(service-oriented architecture,SOA)是一种思想,它将应用程序的不同功能单元通过中立的契约(独立于硬件平台、操作系统、编程语言)联系起来,使各个形式的功能单元更好的集成。WebService是SOA思想的一种较好实现方式,它和普通的web程序一样(如JSP,ASP)采用HTTP协议进行通讯,但是它只用POST方式通讯,它的数据是基于XML格式的,采用SOAP(Simple Object Access Protocol)协议。SOAP协议实际上就是基于XML编码规范的文本协议,它是WebService特有的应用协议。
1.1 WebService体系架构
1.2 CXF说明
JAVA平台上对WebService支持的成熟框架很多,如CXF、AXIS1&2、XFire等,本文使用CXF。
Apache CXF = Celtix + XFire,前身叫 Apache CeltiXfire,已经正式更名为 Apache CXF 了,以下简称为 CXF。CXF 继承了 Celtix 和 XFire 两大开源项目的精华,是Apache基金会组织下的一个*项目,简化了WebService服务端的创建过程。
CXF实现了JAX-WS2.0规范,并通过了JAX-WS2.0 TCK;
CXF可以和Spring无缝集成;
CXF支持多种传输协议(HTTP, JMS, Corba等);
CXF支持多种Binding数据格式(SOAP,XML,JSON等);
CXF 支持多种DataBinding数据类型(JAXB, Aegis) 。
CXF基于Interceptor的架构,使得整个框架非常易于扩展。本次我主要讨论CXF在HTTP/SOAP模式下的处理机制。
CXF的下载地址为:http://cxf.apache.org/download.html,下载后解压压缩包,从lib中至少要拿出如下jar到应用:
cxf-2.2.5.jar
antlr-2.7.7.jar
commons-codec-1.3.jar
commons-collections-3.2.1.jar
commons-lang-2.4.jar
commons-logging-1.1.1.jar
commons-pool-1.5.2.jar
geronimo-annotation_1.0_spec-1.1.1.jar
geronimo-jaxws_2.1_spec-1.0.jar
geronimo-ws-metadata_2.0_spec-1.1.2.jar
jaxb-api-2.1.jar
xalan-2.7.1.jar
wsdl4j-1.6.2.jar
XmlSchema-1.4.5.jar
2 构建服务端
2.1 Servlet构建的简单服务端
1) 定义webservice接口和实现类
publicinterface IHelloWorld {
public String sayHello(String name);
}
publicclass HelloWorldImpl implements IHelloWorld {
public String sayHello(String name) {
return"Hello " + name + ".";
}
}
2) 定义webservice
publicclass HelloWorldServlet extends CXFNonSpringServlet {
privatestaticfinallongserialVersionUID = 1L;
@Override
protectedvoid loadBus(ServletConfig sc) {
super.loadBus(sc);
Bus bus = getBus();
//如果我们要发布多个WebService,仅仅需要修改WebService的接口定义、发布地址、实现类,然后不断重复下面的代码即可
ServerFactoryBean factory = new ServerFactoryBean();
factory.setBus(bus);
factory.setServiceClass(IHelloWorld.class);
factory.setAddress("/HelloWorld");
factory.setServiceBean(new HelloWorldImpl());
factory.create();
}
}
首先CXF的内部上下文对象Bus,然后我再使用CXF提供的ServerFactoryBean工具类把自己写的一个普通的JavaBean发布成WebService。
ServerFactoryBean需要以下参数:
1) Bus。也就是上面我们所得到的Bus对象。
2) WebService的接口定义,也就是指自己的WebService实现了哪个Java Interface。
3) WebService的发布地址,也就是访问WebService的URL地址。
4) WebService的实现类。
web.xml中配置HelloWorldServlet
<servlet>
<servlet-name> HelloWorldServlet </servlet-name>
<servlet-class> HelloWorldServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>HelloWorldServlet</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
启动服务器,访问http://127.0.0.1:8080/CXFDemo/services/?wsdl,可以看到服务列表,http://127.0.0.1:8080/CXFDemo/services/HelloWorld?wsdl就是我们的服务地址。
2.2 java注解的简单服务端
为webservice实现类加上注解
@WebService
public class HelloWorldImpl implements IHelloWorld {
public String sayHello(@WebParam(name = "text") String name) {
return "Hello " + name + ".";
}
}
构建服务端
public class SimpleServer {
public SimpleServer() {
System.out.println("Starting Server");
IHelloWorld helloService = new HelloWorldImpl();
String address = "http://localhost:9000/helloWorld";
Endpoint.publish(address, helloService);
}
public static void main(String[] args) throws InterruptedException {
//System.out.println(System.getProperty("java.endorsed.dirs"));
new SimpleServer();
System.out.println("Server ready...");
Thread.sleep(6000000);
System.out.println("Server exiting");
System.exit(0);
}
}
运行应用程序,访问http://localhost:9000/helloWorld?wsdl,已经发布成功,可以看到服务没有在web服务器中发布,这是因为CXF内置了Jetty应用服务器,可以直接在Jetty上发布。
2.3 结合spring构建web服务
1) 声明实体
@XmlType(name = "User")
@XmlAccessorType(XmlAccessType.FIELD)
publicclass User implements Serializable {
privatestaticfinallongserialVersionUID = 1L;
private String username;
private String password;
public String getUsername() {
returnusername;
}
publicvoid setUsername(String username) {
this.username = username;
}
public String getPassword() {
returnpassword;
}
publicvoid setPassword(String password) {
this.password = password;
}
}
2) 声明接口
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
@WebService
@SOAPBinding(style = Style.RPC)
publicinterface IHelloService {
User saveUser(User user);
User getMaxLongNameUser(User u1, User u2);
}
3) 声明实现
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
import org.apache.log4j.Logger;
@WebService
@SOAPBinding(style = Style.RPC)
publicclass HelloServiceImpl implements IHelloService {
privatestatic Logger log = Logger.getRootLogger();
public User saveUser(User user) {
log.debug("saveUser is called!");
return user;
}
public User getMaxLongNameUser(User u1, User u2) {
log.debug("getMaxLongNameUser is called!");
return u1;
}
}
4) spring配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<!-- 还需要引入以下3个关于CXF的资源文件,这三个文件在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" />
<bean id="inInter" class="org.apache.cxf.interceptor.LoggingInInterceptor" />
<bean id="outInter" class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
<jaxws:endpoint id="userManager" address="/UserManager"
implementorClass="com.pgw.test.webservice.IHelloService">
<jaxws:implementor>
<bean id="userServiceImpl" class="com.pgw.test.webservice.HelloServiceImpl">
</bean>
</jaxws:implementor>
<!-- 加入拦截器 -->
<jaxws:inInterceptors>
<ref bean="inInter" />
<ref bean="outInter" />
</jaxws:inInterceptors>
</jaxws:endpoint>
</beans>
5) web.xml配置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
<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程序,在浏览器中输入
http://127.0.0.1:8080/CXFDemo/UserManager?wsdl查看服务。另外在部署中可能会遇到JAXB 2.0 API is being loaded from the bootstrap classloader这个错误,这是因为cxf2.2.5需要jaxb-api-2.1.jar支持,而低版本的jdk1.6默认的是jaxb-api-2.0.jar,解决办法如下:
重新构建一个相同类型的项目(不同的项目类型,输出的结果不一样),在项目中打印输出System.getProperty("java.endorsed.dirs"),如果文件系统中不存在输出的目录则创建,把jaxb-api-2.1.jar放置到该目录下,问题解决。
3 构建客户端
3.1 WSDL2Java构建客户端
WSDL2Java位于cxf的bin目录下,可以根据wsdl生成java客户端代码。在命令行中输入 WSDL2JAVA –help 可以获取该指令的帮助。
如下所示:
wsdl2java -frontend jaxws21
-p com.ailk.demo.webservice.client.wsdl2Java
-d D:\workspace\olcom5\CXFDemo\src\ http://localhost:8080/CXFDemo/services/HelloWorld?wsdl
客户端生成后进行调用:
publicclass ClientMain {
publicstaticvoid main(String[] args) {
IHelloWorld helloWorld = new IHelloWorld();
IHelloWorldPortType client = helloWorld.getIHelloWorldPort();
String resultStr = client.sayHello("test");
System.out.println("HelloWorld webService response :" + resultStr);
}
}
3.2 JAX-WS Proxy构建客户端
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import com.ailk.demo.service.IHelloWorld;
publicclass ClientMain {
publicstaticvoid main(String[] args) throws MalformedURLException {
URL wsdlURL = new URL("http://localhost:9000/helloWorld?wsdl");
QName SERVICE_NAME = new QName("http://impl.service.demo.ailk.com/", "HelloWorldImplService");
QName PORT_NAME = new QName("http://impl.service.demo.ailk.com/", "HelloWorldImplPort");
Service service = Service.create(wsdlURL, SERVICE_NAME);
IHelloWorld client = service.getPort(PORT_NAME, IHelloWorld.class);
String result = client.sayHello("test");
System.out.println("HelloWorld webService response :" + result);
}
}
3.3 JaxWsProxyFactoryBean构建客户端
JaxWsProxyFactoryBean简化了Proxy
publicclass ClientMain {
publicstaticvoid main(String[] args) {
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setServiceClass(IHelloWorld.class);
factory.setAddress("http://localhost:9000/helloWorld?wsdl");
IHelloWorld client = (IHelloWorld) factory.create();
String result = client.sayHello("test");
System.out.println("HelloWorld webService response :" + result);
}
}
3.4 JaxWsClientProxy动态客户端
DynamicClientFactory factory = DynamicClientFactory.newInstance();
Client client = factory.
createClient("http://localhost:8080/CXFDemo/services/HelloWorld?wsdl");
try {
Object[] objs = client.invoke("sayHello",
new Object[] { "test" });
System.out.println("HelloWorld webService response :" + objs[0]);
} catch (Exception e) {
e.printStackTrace();
}
3.5 JaxWsDynamicClientFactory动态客户端
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.ConduitSelector;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.endpoint.UpfrontConduitSelector;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
publicclass ClientMain {
publicstaticvoid main(String[] args) throws Exception {
JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
Client client = dcf.createClient("http://localhost:8080/CXFDemo/services/HelloWorld?wsdl");
// sayHello 为接口中定义的方法名称 test为传递的参数返回一个Object数组
Object[] objects = client.invoke("sayHello", "test");
// 输出调用结果
System.out.println("HelloWorld webService response :" + objects[0]);
// 动态设置Address
Endpoint ep = client.getEndpoint();
ep.getEndpointInfo().setAddress("http://localhost:8080/CXFDemo/services/HelloWorld");
ConduitSelector cs = new UpfrontConduitSelector();
cs.setEndpoint(ep);
client.setConduitSelector(cs);
// 设置连接超时时间、相应超时时间
HTTPConduit http = (HTTPConduit) client.getConduit();
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setConnectionTimeout(60000);
httpClientPolicy.setAllowChunking(false);
httpClientPolicy.setReceiveTimeout(600000);
http.setClient(httpClientPolicy);
objects = client.invoke("sayHello", "test");
// 输出调用结果
System.out.println("HelloWorld webService response :" + objects[0]);
}
}
客户端创建的过程中,生产服务下所有的方法类、参数类、返回值类,耗时比较惊人,建议在实际应用中预先初始化。
在实际的项目应用中,有这样的场景,提供的服务是相同的,但是服务地址会随着业务的需求动态的变化,就需要我们动态的设置服务地址。