Contents

Apache CXF 对 Spring 的支持

1.概述

本教程的重点是配置和使用Apache CXF 框架以及 Spring - 使用 Java 或 XML 配置。

这是 Apache CXF 系列的第二篇;第一个 侧重于将 CXF 的基础知识作为 JAX-WS 标准 API 的实现。

2. Maven依赖

与上一个教程类似,需要包含以下两个依赖项:

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-frontend-jaxws</artifactId>
    <version>3.1.6</version>
</dependency>
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-transports-http</artifactId>
    <version>3.1.6</version>
</dependency>

有关最新版本的 Apache CXF 工件,请查看apache-cxf

此外,支持 Spring 还需要以下依赖项:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.1.RELEASE</version>
</dependency>

可以在此处 找到最新版本的 Spring 。

最后,因为我们将使用 Java Servlet 3.0+ API 而不是传统的web.xml部署描述符以编程方式配置应用程序,所以我们需要以下工件:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>

这里 我们可以找到最新版本的 Servlet API。

3. 服务器端组件

现在让我们看一下为了发布 Web 服务端点而需要在服务器端出现的组件。

3.1.WebApplicationInitilizer接口

WebApplicationInitializer接口被实现为以编程方式为应用程序配置ServletContext接口。当出现在类路径上时,它的onStartup方法由 servlet 容器自动调用,然后ServletContext被实例化和初始化。

下面是如何定义一个类来实现WebApplicationInitializer接口:

public class AppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext container) {
        // Method implementation
    }
}

*onStartup()*方法是使用如下所示的代码片段实现的。

首先,创建并配置 Spring 应用程序上下文以注册包含配置元数据的类:

AnnotationConfigWebApplicationContext context 
  = new AnnotationConfigWebApplicationContext();
context.register(ServiceConfiguration.class);

ServiceConfiguration类使用*@Configuration*注解进行注解,以提供 bean 定义。这个类将在下一小节中讨论。

以下片段显示了如何将 Spring 应用程序上下文添加到 servlet 上下文中:

container.addListener(new ContextLoaderListener(context));

由 Apache CXF 定义的CXFServlet类被生成并注册以处理传入的请求:

ServletRegistration.Dynamic dispatcher 
  = container.addServlet("dispatcher", new CXFServlet());

应用程序上下文加载配置文件中定义的 Spring 元素。在这种情况下,servlet 的名称是cxf ,因此默认情况下,上下文会在名为cxf-servlet.xml的文件中查找这些元素。

最后,CXF servlet 被映射到一个相对 URL:

dispatcher.addMapping("/services");

3.2. 旧的web.xml

或者,如果我们想使用(有点过时的)部署描述符而不是WebApplicationInitilizer接口,则相应的web.xml文件应该包含以下 servlet 定义:

<servlet>
    <servlet-name>cxf</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
    <servlet-mapping>
    <servlet-name>cxf</servlet-name>
    <url-pattern>/services/*</url-pattern>
</servlet-mapping>

3.3. ServiceConfiguration

现在让我们看一下服务配置——首先是一个包含 Web 服务端点的 bean 定义的基本框架:

@Configuration
public class ServiceConfiguration {
    // Bean definitions
}

第一个需要的 bean 是SpringBus——它为 Apache CXF 提供扩展以与 Spring 框架一起工作:

@Bean
public SpringBus springBus() {
    return new SpringBus();
}

还需要使用SpringBus bean和 Web 服务实现器来创建EnpointImpl bean 。此 bean 用于在给定的 HTTP 地址发布端点:

@Bean
public Endpoint endpoint() {
    EndpointImpl endpoint = new EndpointImpl(springBus(), new BlogdemoImpl());
    endpoint.publish("http://localhost:8080/services/blogdemo");
    return endpoint;
}

BlogdemoImpl类用于实现 Web 服务接口。其定义在下一小节中给出。

或者,我们也可以在 XML 配置文件中声明服务器端点。具体来说,下面的cxf-servlet.xml文件与 3.1 小节中定义的web.xml部署描述符一起使用,并描述了完全相同的端点:

<jaxws:endpoint
  id="blogdemo"
  implementor="com.blogdemo.cxf.spring.BlogdemoImpl"
  address="http://localhost:8080/services/blogdemo" />

请注意,XML 配置文件以部署描述符中定义的 servlet 名称命名,即cxf

3.4. 类型定义

接下来——这里是前面小节中已经提到的implementor的定义:

@WebService(endpointInterface = "com.blogdemo.cxf.spring.Blogdemo")
public class BlogdemoImpl implements Blogdemo {
    private int counter;
    public String hello(String name) {
        return "Hello " + name + "!";
    }
    public String register(Student student) {
        counter++;
        return student.getName() + " is registered student number " + counter;
    }
}

此类为Apache CXF 将包含在已发布的 WSDL 元数据中的Blogdemo端点接口提供了一个实现:

@WebService
public interface Blogdemo {
    String hello(String name);
    String register(Student student);
}

端点接口和实现者都使用Student类,定义如下:

public class Student {
    private String name;
    // constructors, getters and setters
}

4. 客户端 Bean

为了利用 Spring 框架,我们在*@Configuration*注解的类中声明一个 bean:

@Configuration
public class ClientConfiguration {
    // Bean definitions
}

定义了一个名为client的 bean :

@Bean(name = "client")
public Object generateProxy() {
    return proxyFactoryBean().create();
}

客户端bean 代表Blogdemo Web 服务的代理。它是通过调用JaxWsProxyFactoryBean bean 上的create方法创建的,这是一个用于创建 JAX-WS 代理的工厂。

JaxWsProxyFactoryBean对象通过以下方法创建和配置:

@Bean
public JaxWsProxyFactoryBean proxyFactoryBean() {
    JaxWsProxyFactoryBean proxyFactory = new JaxWsProxyFactoryBean();
    proxyFactory.setServiceClass(Blogdemo.class);
    proxyFactory.setAddress("http://localhost:8080/services/blogdemo");
    return proxyFactory;
}

工厂的serviceClass属性表示 Web 服务接口,而address属性表示代理进行远程调用的 URL 地址。

此外,对于客户端的 Spring bean,可以恢复为 XML 配置文件。以下元素声明了与我们上面以编程方式配置的相同的 bean:

<bean id="client" factory-bean="clientFactory" factory-method="create" />
<bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
    <property name="serviceClass" value="com.blogdemo.cxf.spring.Blogdemo" />
    <property name="address" value="http://localhost:8080/services/blogdemo" />
</bean>

5. 测试用例

本节描述用于说明 Apache CXF 对 Spring 的支持的测试用例。测试用例在名为StudentTest的类中定义。

首先,我们需要从前面提到的ServiceConfiguration配置类中加载一个 Spring 应用上下文,并将其缓存在context字段中:

private ApplicationContext context 
  = new AnnotationConfigApplicationContext(ClientConfiguration.class);

接下来,声明服务端点接口的代理并从应用程序上下文加载:

private Blogdemo blogdemoProxy = (Blogdemo) context.getBean("client");

这个Blogdemo代理将用于下面描述的测试用例。

在第一个测试用例中,我们证明当在代理上本地调用hello方法时,响应与端点实现者从远程 Web 服务返回的响应完全相同:

@Test
public void whenUsingHelloMethod_thenCorrect() {
    String response = blogdemoProxy.hello("John Doe");
    assertEquals("Hello John Doe!", response);
}

在第二个测试用例中,学生通过在本地调用代理上的register方法注册 Blogdemo 课程,代理反过来又调用 Web 服务。然后,该远程服务将计算学生人数并将其返回给呼叫者。以下代码片段证实了我们的预期:

@Test
public void whenUsingRegisterMethod_thenCorrect() {
    Student student1 = new Student("Adam");
    Student student2 = new Student("Eve");
    String student1Response = blogdemoProxy.register(student1);
    String student2Response = blogdemoProxy.register(student2);
    assertEquals("Adam is registered student number 1", student1Response);
    assertEquals("Eve is registered student number 2", student2Response);
}

6. 集成测试

为了在服务器上部署为 Web 应用程序,需要先将本教程中的代码片段打包到 WAR 文件中。这可以通过在 POM 文件中声明包属性来实现:

<packaging>war</packaging>

打包作业由 Maven WAR 插件实现:

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </configuration>
</plugin>

该插件将编译后的源代码打包成 WAR 文件。由于我们使用 Java 代码配置 servlet 上下文,因此不需要存在传统的web.xml部署描述符。因此,failOnMissingWebXml属性必须设置为false以避免在执行插件时失败。

我们可以通过此链接 获取最新版本的 Maven WAR 插件。

为了说明 Web 服务的操作,我们创建了一个集成测试。该测试首先生成 WAR 文件并启动嵌入式服务器,然后让客户端调用 Web 服务,验证后续响应并最终停止服务器。

以下插件需要包含在 Maven POM 文件中。有关更多详细信息,请查看此集成测试教程

这是 Maven Surefire 插件:

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
    <configuration>
        <excludes>
            <exclude>StudentTest.java</exclude>
        </excludes>
    </configuration>
</plugin>

这个插件的最新版本可以在这里 找到。

声明了一个带有integration idprofile部分以方便集成测试:

<profiles>
   <profile>
      <id>integration</id>
      <build>
         <plugins>
            ...
         </plugins>
      </build>
   </profile>
</profiles>

Maven Cargo 插件包含在集成配置文件中:

<plugin>
    <groupId>org.codehaus.cargo</groupId>
    <artifactId>cargo-maven2-plugin</artifactId>
    <version>1.5.0</version>
    <configuration>
        <container>
            <containerId>jetty9x</containerId>
            <type>embedded</type>
        </container>
        <configuration>
            <properties>
                <cargo.hostname>localhost</cargo.hostname>
                <cargo.servlet.port>8080</cargo.servlet.port>
            </properties>
        </configuration>
    </configuration>
    <executions>
        <execution>
            <id>start-server</id>
            <phase>pre-integration-test</phase>
            <goals>
                <goal>start</goal>
            </goals>
        </execution>
        <execution>
            <id>stop-server</id>
            <phase>post-integration-test</phase>
            <goals>
                <goal>stop</goal>
            </goals>
        </execution>
    </executions>
</plugin>

请注意,为了清楚起见,仅包含cargo.hostnamecargo.servlet.port配置属性。这些配置属性可以省略而不会对应用程序产生任何影响,因为它们的值与默认值相同。该插件启动服务器,等待连接,最后停止服务器以释放系统资源。

这个链接 允许我们查看最新版本的 Maven Cargo 插件。

Maven Surefire 插件在集成配置文件中再次声明,以覆盖其在主构建部分中的配置并执行上一节中描述的测试用例:

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
    <executions>
        <execution>
            <phase>integration-test</phase>
            <goals>
                <goal>test</goal>
            </goals>
            <configuration>
                <excludes>
                    <exclude>none</exclude>
                </excludes>
            </configuration>
        </execution>
    </executions>
</plugin>

现在整个过程可以通过命令运行:mvn -Pintegration clean install