本文聚焦于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-transports-http</artifactId>
    <version>3.1.6</version>
</dependency>

最新版本可查阅apache-cxf仓库

此外,需添加Spring支持依赖:

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

Spring最新版本见Spring仓库

最后,由于使用Servlet 3.0+ API替代传统web.xml,需添加:

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

Servlet API最新版本在这里

3. 服务端组件

3.1 WebApplicationInitializer接口

WebApplicationInitializer用于程序化配置ServletContext。当容器检测到该类时,会自动调用其onStartup方法初始化上下文:

public class AppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext container) {
        // 配置实现
    }
}

关键步骤分三步走:

  1. 创建并注册Spring应用上下文
  2. 添加上下文加载监听器
  3. 注册CXFServlet并映射URL

具体实现代码:

// 创建Spring上下文
AnnotationConfigWebApplicationContext context 
  = new AnnotationConfigWebApplicationContext();
context.register(ServiceConfiguration.class);

// 添加上下文监听器
container.addListener(new ContextLoaderListener(context));

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

// 映射URL路径
dispatcher.addMapping("/services");

3.2 传统web.xml方式

如果偏爱XML配置,可在web.xml中这样声明:

<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类

核心配置类,用于定义服务端Bean:

@Configuration
public class ServiceConfiguration {
    // Bean定义
}

必须声明两个关键Bean:

  1. SpringBus:CXF与Spring的桥梁
  2. Endpoint:服务发布端点
@Bean
public SpringBus springBus() {
    return new SpringBus();
}

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

等价XML配置(需存为cxf-servlet.xml):

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

文件名需与servlet-name匹配(此处为cxf)

3.4 类型定义

服务实现类BaeldungImpl

@WebService(endpointInterface = "com.baeldung.cxf.spring.Baeldung")
public class BaeldungImpl implements Baeldung {
    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;
    }
}

对应的服务接口Baeldung

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

数据传输对象Student

public class Student {
    private String name;
    // 构造器、getter/setter
}

4. 客户端Bean

客户端配置类ClientConfiguration

@Configuration
public class ClientConfiguration {
    // Bean定义
}

通过代理工厂创建服务代理:

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

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

等价XML配置:

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

5. 测试用例

测试类StudentTest关键配置:

private ApplicationContext context 
  = new AnnotationConfigApplicationContext(ClientConfiguration.class);
private Baeldung baeldungProxy = (Baeldung) context.getBean("client");

测试场景1:基础调用

验证hello方法远程调用:

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

测试场景2:对象传递

验证register方法处理对象参数:

@Test
public void whenUsingRegisterMethod_thenCorrect() {
    Student student1 = new Student("Adam");
    Student student2 = new Student("Eve");
    String student1Response = baeldungProxy.register(student1);
    String student2Response = baeldungProxy.register(student2);

    assertEquals("Adam is registered student number 1", student1Response);
    assertEquals("Eve is registered student number 2", student2Response);
}

6. 集成测试

6.1 WAR打包配置

POM中声明打包类型:

<packaging>war</packaging>

配置Maven WAR插件(跳过web.xml检查):

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

6.2 测试插件配置

Surefire插件(排除普通测试):

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

集成测试Profile

<profiles>
   <profile>
      <id>integration</id>
      <build>
         <plugins>
            <!-- 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>
            
            <!-- 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>
         </plugins>
      </build>
   </profile>
</profiles>

执行命令:mvn -Pintegration clean install

7. 总结

本文展示了Apache CXF与Spring的深度集成能力:

  • 通过Spring配置发布WebService接口
  • 使用CXF代理工厂创建客户端代理
  • 支持Java配置和XML配置两种方式
  • 完整的服务端/客户端测试方案

所有示例代码可在GitHub项目中获取。简单粗暴地说,这套方案让WebService开发变得像配置Spring Bean一样直观,踩坑点主要在于Servlet初始化和代理工厂的配置细节。


原始标题:A Guide to Apache CXF with Spring