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>4.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>4.0.5</version>
</dependency>要查看 Apache CXF 最新版本的 Artifact,請訪問 apache-cxf。
此外,以下依賴項是支持 Spring 所必需的:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.1.8</version>
</dependency>最新版本的 Spring 構件可以在這裏找到:這裏。
此外,由於我們將使用 Java Servlet 3.0+ API 編程方式地配置應用程序,而不是使用傳統的 web.xml 部署描述符,因此我們需要以下構件:
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>此處是我們查找 Servlet API 最新版本的地點。
3. 服務器端組件
現在我們來探討一下為了發佈 Web 服務端點時,服務器端需要存在的組件。
3.1. WebApplicationInitilizer 接口
WebApplicationInitilizer 接口用於通過編程方式配置應用程序的 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));CXFServlet 類,由 Apache CXF 定義,用於處理傳入的請求並進行生成和註冊。
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 Framework 配合工作:
@Bean
public SpringBus springBus() {
return new SpringBus();
}一個 EnpointImpl Bean 也需要使用 SpringBus Bean 和一個 Web 服務 實現者 創建。該 Bean 用於在給定的 HTTP 地址上發佈 Endpoint:
@Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), new BaeldungImpl());
endpoint.publish("http://localhost:8080/services/baeldung");
return endpoint;
}BaeldungImpl 類用於實現 Web 服務接口。其定義將在下一節中給出。
或者,我們也可以在 XML 配置文件中聲明服務器端點。具體而言,下面的 cxf-servlet.xml 文件與在 3.1 節中定義的 web.xml 部署描述符一起使用,並描述了相同的端點:
<jaxws:endpoint
id="baeldung"
implementor="com.baeldung.cxf.spring.BaeldungImpl"
address="http://localhost:8080/services/baeldung" />請注意,XML 配置文件的名稱與部署描述文件中定義的 servlet 名稱相同,即 cxf。
3.4. 類型定義
以下是先前小節中已提及的 實現者 的定義:
@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 端點接口的實現,Apache CXF 將在發佈的 WSDL 元數據中包含該實現:
@WebService
public interface Baeldung {
String hello(String name);
String register(Student student);
}端點接口以及實施者都使用學生類,該類定義如下:
public class Student {
private String name;
// constructors, getters and setters
}4. 客户端 Bean
為了充分利用 Spring Framework,我們在一個帶有 @Configuration 註解的類中聲明一個 Bean:
@Configuration
public class ClientConfiguration {
// Bean definitions
}定義一個名為 client 的 Bean:
@Bean(name = "client")
public Object generateProxy() {
return proxyFactoryBean().create();
}客户端 Bean 代表了 Baeldung Web 服務的代理。它通過對 create 方法在 JaxWsProxyFactoryBean Bean 上進行的調用創建的,該 Bean 是 JAX-WS 代理創建工廠。
JaxWsProxyFactoryBean 對象由以下方法創建和配置:
@Bean
public JaxWsProxyFactoryBean proxyFactoryBean() {
JaxWsProxyFactoryBean proxyFactory = new JaxWsProxyFactoryBean();
proxyFactory.setServiceClass(Baeldung.class);
proxyFactory.setAddress("http://localhost:8080/services/baeldung");
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.baeldung.cxf.spring.Baeldung" />
<property name="address" value="http://localhost:8080/services/baeldung" />
</bean>5. 測試用例
本節描述了用於演示 Apache CXF 對 Spring 的支持的測試用例。測試用例定義在一個名為 StudentTest 的類中。
首先,我們需要從上述的 ServiceConfiguration 配置類中加載 Spring 應用上下文,並將其緩存到 context 字段中:
private ApplicationContext context
= new AnnotationConfigApplicationContext(ClientConfiguration.class);接下來,聲明並從應用程序上下文加載服務端點接口的代理:
private Baeldung baeldungProxy = (Baeldung) context.getBean("client");本 Baeldung 代理將在下面描述的測試用例中被使用。
在第一個測試用例中,我們證明當代理上本地調用 hello 方法時,響應與端點 implementor 從遠程 Web 服務返回的完全相同:
@Test
public void whenUsingHelloMethod_thenCorrect() {
String response = baeldungProxy.hello("John Doe");
assertEquals("Hello John Doe!", response);
}在第二個測試用例中,學生通過在代理對象上本地調用 register 方法來註冊 Baeldung 課程,這反過來又會調用 Web 服務。該 Web 服務將計算學生的學號並將其返回給調用者。以下代碼片段確認了我們的預期:
@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. 集成測試
為了將其作為 Web 應用程序部署到服務器上,本教程中的代碼片段需要首先打包成 WAR 文件。這可以通過在 POM 文件中聲明 packaging 屬性來實現:
<packaging>war</packaging>打包任務由 Maven WAR 插件執行:
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</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>最新版本的此插件可以在 這裏找到。
已聲明一個 配置 部分,其 ID 為 集成,以方便集成測試:
<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.hostname 和 cargo.servlet.port 配置屬性僅用於提供清晰度。由於其值與默認值相同,因此可以省略這些配置屬性,而不會對應用程序產生任何影響。此插件啓動服務器,等待連接,並最終停止服務器以釋放系統資源。
此處 允許我們查看 Maven Cargo 插件的最新版本。
Maven Surefire 插件在 integration 配置文件中再次聲明,以覆蓋主 build 部分中的配置,並執行上一部分中描述的測試用例:
<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。
7. 結論
本教程介紹了 Apache CXF 對 Spring 的支持。 尤其,它展示瞭如何使用 Spring 配置文件發佈 Web 服務,以及客户端如何通過 Apache CXF 代理工廠創建的代理與該服務進行交互,該代理工廠在另一個配置文件中聲明。