如何使用 Apache CXF 快速实现一个 WebService

在单体架构向分布式架构演进的过程中,跨进程的通信成为刚需,如果直接利用 Socket 进行通信,那么就会使得编程变得很复杂。

在 1984 年,Bruce Jay Nelson 提出了跨进程通信的标准,也就是我们通常说的 RPC(Remote Procedure Call)。后面所有的所有的 RPC 框架都是通过这个标准来设计的。

RPC 的理念其实很简单,当客户端想发起一个调用时,实际上是通过本地调用方的 Stub(可以理解为接口),这个 Stub 将要客户端需要执行的方法和参数通过约定的协议进行编码,然后通过网络传输到服务端,服务端接收到这些信息之后执行真正的方法,得到结果后再通过网络传输给客户端。

Web Services 简介

Web Services 属于 RPC 的一种。Web Services 使用 SOAP 作为传输协议,使用 WSDL 作为服务的描述语言,使用 UDDI 作为服务注册发现(虽然没有发展起来)。

虽然 Web Services 的相关协议在 2007 年之后基本就没再更新,但是在一些银行等金融机构,Web Services 还在被大量使用。

WebService 的框架很多,比如 Axis2,XFire,CXF 等等。

Apache Cxf 是其中最优秀,最具有生命力的一个,而且 CXF 框架不仅仅是一个 Web Services 框架,甚至可以通过 CXF 来实现 RESTful 风格的服务。

CXF 实现一个简单的 Web Services

使用 CXF 实现 Web Services 非常简单。

一个完整的 Web Services 服务分为服务器端和服务端,先来看服务器端的代码实现。

首先创建一个 Maven 项目,在 pom.xml 中引入如下的依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.4.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.4.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<version>3.4.0-SNAPSHOT</version>
</dependency>

然后就可以开始写代码了,首先需要定义一个可以暴露出去的 API 接口:

1
2
3
4
5
6
import javax.jws.WebService;

@WebService
public interface CxfDemoService {
String hello(String name);
}

@WebService 注解表明这是一个对外的接口。然后需要实现具体的业务逻辑:

1
2
3
4
5
6
public class CxfDemoServiceImpl implements CxfDemoService{
@Override
public String hello(String name) {
return "hello " + name;
}
}

就像实现一个普通的类一样,没有任何特殊的地方。然后创建一个服务:

1
2
3
4
5
6
CxfDemoServiceImpl implementor = new CxfDemoServiceImpl();
JaxWsServerFactoryBean svrFactory = new JaxWsServerFactoryBean();
svrFactory.setServiceClass(CxfDemoService.class);
svrFactory.setAddress("http://localhost:9000/cxfDemoService");
svrFactory.setServiceBean(implementor);
svrFactory.create();

代码也很简单,我们把服务的地址设置为 http://localhost:9000/cxfDemoService,只要端口号不冲突,这个地址可以根据需要定义。服务端的代码就这些了,只需要把这段代码放到一个 main 方法中,启动就可以。

上面我们说到 Web Servces 使用 WSDL 来作为服务描述语言,这个怎么理解呢?写好的服务是要提供给其他人调用的,那么别人需要知道这个接口需要什么参数,返回的结果是什么,这个时候,只需要访问 http://localhost:9000/cxfDemoService?wsdl,在服务地址的后面加上 ?wsdl 就可以,返回的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://cxf.rayjun.cn/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="CxfDemoServiceService" targetNamespace="http://cxf.rayjun.cn/">
<wsdl:types>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://cxf.rayjun.cn/" elementFormDefault="unqualified" targetNamespace="http://cxf.rayjun.cn/" version="1.0">
<xs:element name="hello" type="tns:hello"/>
<xs:element name="helloResponse" type="tns:helloResponse"/>
<xs:complexType name="hello">
<xs:sequence>
<xs:element minOccurs="0" name="arg0" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="helloResponse">
<xs:sequence>
<xs:element minOccurs="0" name="return" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
</wsdl:types>
<wsdl:message name="helloResponse">
<wsdl:part element="tns:helloResponse" name="parameters"> </wsdl:part>
</wsdl:message>
<wsdl:message name="hello">
<wsdl:part element="tns:hello" name="parameters"> </wsdl:part>
</wsdl:message>
<wsdl:portType name="CxfDemoService">
<wsdl:operation name="hello">
<wsdl:input message="tns:hello" name="hello"> </wsdl:input>
<wsdl:output message="tns:helloResponse" name="helloResponse"> </wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="CxfDemoServiceServiceSoapBinding" type="tns:CxfDemoService">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="hello">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="hello">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="helloResponse">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="CxfDemoServiceService">
<wsdl:port binding="tns:CxfDemoServiceServiceSoapBinding" name="CxfDemoServicePort">
<soap:address location="http://localhost:9000/cxfDemoService"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

返回的结果是一个 xml 文档,这个就是通过 WSDL 来描述服务的全部信息,可以从这个文档中找到调用这个服务所需要的参数及返回的结果等详细信息。

到这里为止,服务端的代码就写好了,那么接下来就需要写客户端的代码,客户端的代码也很简单:

1
2
3
4
5
6
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setServiceClass(CxfDemoService.class);
factory.setAddress("http://localhost:8080/CxfDemoService");
CxfDemoService client = (CxfDemoService) factory.create();
String reply = client.hello("Rayjun");
System.out.println("Server : " + reply); // Server: hello rayjun

上面的代码简单粗暴,如果直接这样进行开发,代码很容易就会变得难以维护,使用 Spring 来组织代码则可以很轻易的解决这个问题。

CXF 与 Spring 集成

CXF 可以与 Spring 无缝连接,只需要引入下面的 Spring 依赖:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>

然后创建Spring容器的配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
<?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-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"/>

<jaxws:endpoint id="rmsEngineService" implementor="cn.rayjun.cxf.CxfDemoServiceImpl" address="/CxfDemoService"/>
</beans>
在配置文件中直接使用 CXF 默认的配置文件,并且使用 jaxws 来定义服务的接口。JAX-WS 是 J2EE 中对 Web Services 的实现,JAX-RS 同样也是 J2EE WEB Services 的一部分,是 RESTful 风格的 API,在这里我们使用的是 JAX-WS。

并将配置文件加入到 web.xml 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<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>
<servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

然后就可以将程序打包放到 web 容器中,如果为了测试方便,也可以直接使用 maven 的 jetty 插件直接运行项目,jetty 插件配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.29.v20200521</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<war>${project.basedir}/target/cxfdemo.war</war>
<webApp>
<contextPath>/</contextPath>
</webApp>
<httpConnector>
<port>8080</port>
</httpConnector>
</configuration>
</plugin>

然后使用 mvn jetty:run-war,服务端就运行起来了,客户端的代码不需要变,就可以很轻松的访问服务了。

到这里,利用 CXF 就把服务的脚手架搭建起来了,就可以根据自己的需要加上其他的配置或者开始业务逻辑的开发。

微信公众号

© 2020 ray