Web Service 那点事儿(2)—— 使用 CXF 开发 SOAP 服务

选框架犹如选媳妇,选来选去,最后我仍是选了“丑媳妇(CXF)”,为何是它?由于 CXF 是 Apache 旗下的一款很是优秀的 WS 开源框架,具有轻量级的特性,并且能无缝整合到 Spring 中。html

其实 CXF 是两个开源框架的整合,它们分别是:CeltixXFire,前者是一款 ESB 框架,后者是一款 WS 框架。话说早在 2007 年 5 月,当 XFire 发展到了它的鼎盛时期(最终版本是 1.2.6),忽然对业界宣布了一个使人震惊的消息:“XFire is now CXF”,随后 CXF 2.0 诞生了,直到 2014 年 5 月,CXF 3.0 降临了。真是 7 年磨一剑啊!CXF 终于长大了,相信在不久的未来,必定会取代 Java 界 WS 龙头老大 Axis 的江湖地位,貌似 Axis 自从 2012 年 4 月之后就没有升级了,这是要告别 Java 界的节奏吗?仍是后面有更大的动做?java

如何使用 CXF 开发基于 SOAP 的 WS 呢?web

这就是我今天要与您分享的内容,重点是在 Web 容器中发布与调用 WS,这样也更加贴近咱们实际工做的场景。spring

在 CXF 这个主角正是登台以前,我想先请出今天的配角 Oracle JAX-WS RI,简称:RI(日),全称:Reference Implementation,它是 Java 官方提供的 JAX-WS 规范的具体实现。apache

先让 RI 来跑跑龙套,先来看看如何使用 RI 发布 WS 吧!编程

1. 使用 RI 发布 WS

第一步:整合 Tomcat 与 RI浏览器

这一步稍微有一点点繁琐,不过也很容易作到。首先您须要经过如下地址,下载一份 RI 的程序包:安全

https://jax-ws.java.net/2.2.8/app

下载完毕后,只需解压便可,假设解压到 D:/Tool/jaxws-ri 目录下。随后须要对 Tomcat 的 config/catalina.properties 文件进行配置:框架

common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar,D:/Tool/jaxws-ri/lib/*.jar

注意:以上配置中的最后一部分,其实就是在 Tomcat 中添加一系列关于 RI 的 jar 包。

看起来并不复杂哦,只是对现有的 Tomcat 有所改造而已,固然,您将这些 jar 包所有放入本身应用的 WEB-INF/lib 目录中也是可行的。

第二步:编写 WS 接口及其实现

接口部分:

<!-- lang: java -->
package demo.ws.soap_jaxws;

import javax.jws.WebService;

@WebService
public interface HelloService {

    String say(String name);
}

实现部分:

<!-- lang: java -->
package demo.ws.soap_jaxws;

import javax.jws.WebService;

@WebService(
    serviceName = "HelloService",
    portName = "HelloServicePort",
    endpointInterface = "demo.ws.soap_jaxws.HelloService"
)
public class HelloServiceImpl implements HelloService {

    public String say(String name) {
        return "hello " + name;
    }
}

注意:接口与实现类上都标注 javax.jws.WebService 注解,可在实现类的注解中添加一些关于 WS 的相关信息,例如:serviceNameportName 等,固然这是可选的,为了让生成的 WSDL 的可读性更增强而已。

第三步:在 WEB-INF 下添加 sun-jaxws.xml 文件

就是在这个 sun-jaxws.xml 文件里配置须要发布的 WS,其内容以下:

<!-- lang: xml -->
<?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0">

    <endpoint name="HelloService"
              implementation="demo.ws.soap_jaxws.HelloServiceImpl"
              url-pattern="/ws/soap/hello"/>

</endpoints>

这里仅发布一个 endpoint,并配置三个属性:WS 的名称、实现类、URL 模式。正是经过这个“URL 模式”来访问 WSDL 的,立刻您就能够看到。

第四步:部署应用并启动 Tomcat

当 Tomcat 启动成功后,会在控制台上看到以下信息:

2014-7-2 13:39:31 com.sun.xml.ws.transport.http.servlet.WSServletDelegate <init>
信息: WSSERVLET14: JAX-WS servlet 正在初始化
2014-7-2 13:39:31 com.sun.xml.ws.transport.http.servlet.WSServletContextListener contextInitialized
信息: WSSERVLET12: JAX-WS 上下文监听程序正在初始化

哎呦,不错哦!仍是中文的。

随后,立马打开您的浏览器,输入如下地址:

http://localhost:8080/ws/soap/hello

若是不出意外的话,您如今应该能够看到以下界面了:

RI 控制台

看起来这应该是一个 WS 控制台,方便咱们查看发布了哪些 WS,能够点击上面的 WSDL 连接可查看具体信息。

看起来 RI 确实挺好的!不只仅有一个控制台,并且还能与 Tomcat 无缝整合。但 RI 彷佛与 Spring 的整合能力并非太强,也许是由于 Oracle 是 EJB 拥护者吧。

那么,CXF 也具有 RI 这样的特性吗?而且可以与 Spring 很好地集成吗?

CXF 不只能够将 WS 发布在任何的 Web 容器中,并且还提供了一个便于测试的 Web 环境,实际上它内置了一个 Jetty。

咱们先看看如何启动 Jetty 发布 WS,再来演示如何在 Spring 容器中整合 CXF。

2. 使用 CXF 内置的 Jetty 发布 WS

第一步:配置 Maven 依赖

若是您是一位 Maven 用户,那么下面这段配置相信必定不会陌生:

<!-- lang: xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>demo.ws</groupId>
    <artifactId>soap_cxf</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <cxf.version>3.0.0</cxf.version>
    </properties>

    <dependencies>
        <!-- CXF -->
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <version>${cxf.version}</version>
        </dependency>
    </dependencies>

</project>

若是您目前尚未使用 Maven,那么就须要从如下地址下载 CXF 的相关 jar 包,并将其放入应用中。

http://cxf.apache.org/download.html

第二步:写一个 WS 接口及其实现

接口部分:

<!-- lang: java -->
package demo.ws.soap_cxf;

import javax.jws.WebService;

@WebService
public interface HelloService {

    String say(String name);
}

实现部分:

<!-- lang: java -->
package demo.ws.soap_cxf;

import javax.jws.WebService;

@WebService
public class HelloServiceImpl implements HelloService {

    public String say(String name) {
        return "hello " + name;
    }
}

这里简化了实现类上的 WebService 注解的配置,让 CXF 自动为咱们取默认值便可。

第三步:写一个 JaxWsServer 类来发布 WS

<!-- lang: java -->
package demo.ws.soap_cxf;

import org.apache.cxf.jaxws.JaxWsServerFactoryBean;

public class JaxWsServer {

    public static void main(String[] args) {
        JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean();
        factory.setAddress("http://localhost:8080/ws/soap/hello");
        factory.setServiceClass(HelloService.class);
        factory.setServiceBean(new HelloServiceImpl());
        factory.create();
        System.out.println("soap ws is published");
    }
}

发布 WS 除了以上这种基于 JAX-WS 的方式之外,CXF 还提供了另外一种选择,名为 simple 方式。

经过 simple 方式发布 WS 的代码以下:

<!-- lang: java -->
package demo.ws.soap_cxf;

import org.apache.cxf.frontend.ServerFactoryBean;

public class SimpleServer {

    public static void main(String[] args) {
        ServerFactoryBean factory = new ServerFactoryBean();
        factory.setAddress("http://localhost:8080/ws/soap/hello");
        factory.setServiceClass(HelloService.class);
        factory.setServiceBean(new HelloServiceImpl());
        factory.create();
        System.out.println("soap ws is published");
    }
}

注意:以 simple 方式发布的 WS,不能经过 JAX-WS 方式来调用,只能经过 simple 方式的客户端来调用,下文会展现 simple 方式的客户端代码。

第四步:运行 JaxWsServer 类

当 JaxWsServer 启动后,在控制台中会看到打印出来的一句提示。随后,在浏览器中输入如下 WSDL 地址:

http://localhost:8080/ws/soap/hello?wsdl

注意:经过 CXF 内置的 Jetty 发布的 WS,仅能查看 WSDL,却没有像 RI 那样的 WS 控制台。

可见,这种方式很是容易测试与调试,大大节省了咱们的开发效率,但这种方式并不适合于生产环境,咱们仍是须要依靠于 Tomcat 与 Spring。

那么,CXF 在实战中是如何集成在 Spring 容器中的呢?见证奇迹的时候到了!

3. 在 Web 容器中使用 Spring + CXF 发布 WS

Tomcat + Spring + CXF,这个场景应该更加接近咱们的实际工做状况,开发过程也是很是天然。

第一步:配置 Maven 依赖

<!-- lang: xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>demo.ws</groupId>
    <artifactId>soap_spring_cxf</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>4.0.5.RELEASE</spring.version>
        <cxf.version>3.0.0</cxf.version>
    </properties>

    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- CXF -->
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>${cxf.version}</version>
        </dependency>
    </dependencies>

</project>

第二步:写一个 WS 接口及其实现

接口部分:

<!-- lang: java -->
package demo.ws.soap_spring_cxf;

import javax.jws.WebService;

@WebService
public interface HelloService {

    String say(String name);
}

实现部分:

<!-- lang: java -->
package demo.ws.soap_spring_cxf;

import javax.jws.WebService;
import org.springframework.stereotype.Component;

@WebService
@Component
public class HelloServiceImpl implements HelloService {

    public String say(String name) {
        return "hello " + name;
    }
}

须要在实现类上添加 Spring 的 org.springframework.stereotype.Component 注解,这样才能被 Spring IOC 容器扫描到,认为它是一个 Spring Bean,能够根据 Bean ID(这里是 helloServiceImpl)来获取 Bean 实例。

第三步:配置 web.xml

<!-- lang: xml -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

    <!-- Spring -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- CXF -->
    <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>

</web-app>

全部带有 /ws 前缀的请求,将会交给被 CXFServlet 进行处理,也就是处理 WS 请求了。目前主要使用了 Spring IOC 的特性,利用了 ContextLoaderListener 加载 Spring 配置文件,即这里定义的 spring.xml 文件。

第四步:配置 Spring

配置 spring.xml:

<!-- lang: 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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="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">

    <context:component-scan base-package="demo.ws"/>

    <import resource="spring-cxf.xml"/>

</beans>

以上配置作了两件事情:

  1. 定义 IOC 容器扫描路径,即这里定义的 demo.ws,在这个包下面(包括全部子包)凡是带有 Component 的类都会扫描到 Spring IOC 容器中。
  2. 引入 spring-cxf.xml 文件,用于编写 CXF 相关配置。将配置文件分离,是一种很好的开发方式。

第五步:配置 CXF

配置 spring-cxf.xml:

<!-- lang: 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: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">

    <jaxws:server id="helloService" address="/soap/hello">
        <jaxws:serviceBean>
            <ref bean="helloServiceImpl"/>
        </jaxws:serviceBean>
    </jaxws:server>

</beans>

经过 CXF 提供的 Spring 命名空间,即 jaxws:server,来发布 WS。其中,最重要的是 address 属性,以及经过 jaxws:serviceBean 配置的 Spring Bean。

可见,在 Spring 中集成 CXF 比想象的更加简单,此外,还有一种更简单的配置方法,那就是使用 CXF 提供的 endpoint 方式,配置以下:

<!-- lang: 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: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">

    <jaxws:endpoint id="helloService" implementor="#helloServiceImpl" address="/soap/hello"/>

</beans>

使用 jaxws:endpoint 能够简化 WS 发布的配置,与 jaxws:server 相比,确实是一种进步。

注意:这里的 implementor 属性值是 #helloServiceImpl,这是 CXF 特有的简写方式,并不是是 Spring 的规范,意思是经过 Spring 的 Bean ID 获取 Bean 实例。

一样,也能够在 Spring 中使用 simple 方式来发布 WS,配置以下:

<!-- lang: 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:simple="http://cxf.apache.org/simple"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://cxf.apache.org/simple
       http://cxf.apache.org/schemas/simple.xsd">

    <simple:server id="helloService" serviceClass="#helloService" address="/soap/hello">
        <simple:serviceBean>
            <ref bean="#helloServiceImpl"/>
        </simple:serviceBean>
    </simple:server>

</beans>

可见,simple:serverjaxws:server 的配置方式相似,都须要配置一个 serviceBean

比较以上这三种方式,我我的更加喜欢第二种,也就是 endpoint 方式,由于它够简单!

至于为何 CXF 要提供如此之多的 WS 发布方式?我我的认为,CXF 为了知足广大开发者的喜爱,也是为了向前兼容,因此这些方案所有保留下来了。

第六步:启动 Tomcat

将应用部署到 Tomcat 中,在浏览器中输入如下地址可进入 CXF 控制台:

http://localhost:8080/ws

CXF 控制台

经过以上过程,能够看出 CXF 彻底具有 RI 的易用性,而且与 Spring 有很好的可集成性,并且配置也很是简单。

一样经过这个地址能够查看 WSDL:

http://localhost:8080/ws/soap/hello?wsdl

注意:紧接在 /ws 前缀后面的 /soap/hello,实际上是在 address="/soap/hello" 中配置的。

如今已经成功地经过 CXF 对外发布了 WS,下面要作的事情就是用 WS 客户端来调用这些 endpoint 了。

您能够再也不使用 JDK 内置的 WS 客户端,也没必要经过 WSDL 打客户端 jar 包,由于 CXF 已经为您提供了多种 WS 客户端解决方案,根据您的口味自行选择吧!

4. 关于 CXF 提供的 WS 客户端

方案一:静态代理客户端

<!-- lang: java -->
package demo.ws.soap_cxf;

import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;

public class JaxWsClient {

    public static void main(String[] args) {
        JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
        factory.setAddress("http://localhost:8080/ws/soap/hello");
        factory.setServiceClass(HelloService.class);

        HelloService helloService = factory.create(HelloService.class);
        String result = helloService.say("world");
        System.out.println(result);
    }
}

这种方案须要自行经过 WSDL 打客户端 jar 包,经过静态代理的方式来调用 WS。这种作法最为原始,下面的方案更有特点。

方案二:动态代理客户端

<!-- lang: java -->
package demo.ws.soap_cxf;

import org.apache.cxf.endpoint.Client;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;

public class JaxWsDynamicClient {

    public static void main(String[] args) {
        JaxWsDynamicClientFactory factory = JaxWsDynamicClientFactory.newInstance();
        Client client = factory.createClient("http://localhost:8080/ws/soap/hello?wsdl");

        try {
            Object[] results = client.invoke("say", "world");
            System.out.println(results[0]);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这种方案无需经过 WSDL 打客户端 jar 包,底层实际上经过 JDK 的动态代理特性完成的,CXF 实际上作了一个简单的封装。与 JDK 动态客户端不同的是,此时无需使用 HelloService 接口,能够说是货真价实的 WS 动态客户端。

方案三:通用动态代理客户端

<!-- lang: java -->
package demo.ws.soap_cxf;

import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.dynamic.DynamicClientFactory;

public class DynamicClient {

    public static void main(String[] args) {
        DynamicClientFactory factory = DynamicClientFactory.newInstance();
        Client client = factory.createClient("http://localhost:8080/ws/soap/hello?wsdl");

        try {
            Object[] results = client.invoke("say", "world");
            System.out.println(results[0]);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这种方案与“方案三”相似,但不一样的是,它不只用于调用 JAX-WS 方式发布的 WS,也用于使用 simple 方式发布的 WS,更加智能了。

方案四:基于 CXF simple 方式的客户端

<!-- lang: java -->
package demo.ws.soap_cxf;

import org.apache.cxf.frontend.ClientProxyFactoryBean;

public class SimpleClient {

    public static void main(String[] args) {
        ClientProxyFactoryBean factory = new ClientProxyFactoryBean();
        factory.setAddress("http://localhost:8080/ws/soap/hello");
        factory.setServiceClass(HelloService.class);
        HelloService helloService = factory.create(HelloService.class);
        String result = helloService.say("world");
        System.out.println(result);
    }
}

这种方式仅用于调用 simple 方式发布的 WS,不能调用 JAX-WS 方式发布的 WS,这是须要注意的。

方案五:基于 Spring 的客户端

方法一:使用 JaxWsProxyFactoryBean

<!-- lang: 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <bean id="factoryBean" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
        <property name="serviceClass" value="demo.ws.soap_spring_cxf.HelloService"/>
        <property name="address" value="http://localhost:8080/ws/soap/hello"/>
    </bean>

    <bean id="helloService" factory-bean="factoryBean" factory-method="create"/>

</beans>

方法二:使用 jaxws:client(推荐)

<!-- lang: 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: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">

    <jaxws:client id="helloService"
                  serviceClass="demo.ws.soap_spring_cxf.HelloService"
                  address="http://localhost:8080/ws/soap/hello"/>

</beans>

客户端代码:

<!-- lang: java -->
package demo.ws.soap_spring_cxf;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Client {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-client.xml");

        HelloService helloService = context.getBean("helloService", HelloService.class);
        String result = helloService.say("world");
        System.out.println(result);
    }
}

谈不上那种方案更加优秀,建议根据您的实际场景选择最为合适的方案。

5. 总结

经过阅读本文,相信您已经大体了解了 CXF 的基本用法。可独立使用,也可与 Spring 集成;可面向 API 来编程,也可以使用 Spring 配置;发布 WS 的方式有多种,调用 WS 的方式一样也有多种。

尤为是 Spring + CXF 这对搭档,让发布 WS 更加简单,只需如下四个步骤:

  1. 配置 web.xml
  2. 编写 WS 接口及其实现
  3. 配置 CXF 的 endpoint
  4. 启动 Web 容器

固然,目前您看到的都是 WS 的基础特性,下期我将带您走进 WS 的高级话题 —— 基于 WS 的 Security 解决方案,业界称为 WS-Security 规范,使用它可确保您的 SOAP 服务更加安全。感谢您阅读本文,咱们下期再见!

相关文章
相关标签/搜索