第 5 章 Quarkus CXF 用户指南


本章提供有关 Quarkus CXF 用法和配置的信息。

5.1. 用户指南

此用户指南解释了 Quarkus CXF 的典型用例。

您可能想要从以下一些主题开始:

5.1.1. 创建新项目

本指南介绍了如何为托管 CXF 客户端或服务器或两者的 Quarkus 应用程序设置新项目。

5.1.1.1. 先决条件

请参阅 Quarkus 入门指南 的先决条件部分

除此之外,您可能需要

  • 安装了 native-image 命令以及 GRAALVM_HOME 环境变量集的 GraalVM。请参阅 Quarkus 文档中的 构建原生可执行文件 部分。
  • 如果您在 Linux 上,如 docker 的容器运行时也足以满足原生模式。如果选择这个选项,则使用 -Pnative -Dquarkus.native.container-build=true 而不是 -Pnative

5.1.1.2. 创建一个项目

可以使用 code.quarkus.redhat.com 生成新项目框架。

https://code.quarkus.io
  • 您可以在此处选择您要使用的扩展。
  • 对于简单的 Hello world Web 服务或客户端,quarkus-cxf 扩展就足够了。
  • 单击 blue Generate your application 按钮,以下载基本的框架项目。
  • 解包 zip 文件,并将项目 导入到您首选的 IDE 中。

5.1.1.3. Quarkus 平台

Quarkus CXF 是 Quarkus Platform 版本 3.1.0.Final 的一部分。

Quarkus 平台聚合了由各种独立项目生成的 Quarkus 扩展,如 Quarkus Core、Quarkus CXF、Apache Camel、Qpid JMS、Debezium 等。

其主要目标是:

5.1.1.4. 依赖项管理

我们建议使用 Quarkus Platform BOM 来管理 Quarkus CXF 依赖项。这是您获得的内容,当您使用 code.quarkus.redhat.com 或其他 Quarkus 开发工具 时,如 Quarkus CLI。

<project ...>
  ...
  <properties>
    ...
    <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
    <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
    <quarkus.platform.version><!-- Check the latest https://repo1.maven.org/maven2/io/quarkus/platform/quarkus-cxf-bom/ --></quarkus.platform.version>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>${quarkus.platform.group-id}</groupId>
        <artifactId>${quarkus.platform.artifact-id}</artifactId>
        <version>${quarkus.platform.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>${quarkus.platform.group-id}</groupId>
        <artifactId>quarkus-cxf-bom</artifactId>
        <version>${quarkus.platform.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
...

您应该始终谨慎将同一版本的 io.quarkus.platform:quarkus-bomio.quarkus.platform:quarkus-cxf-bom 导入到项目中。这是获取 Quarkus、CXF、Pa Quarkus CXF 及其所有传输依赖项的最可靠方法。

5.1.1.5. 进入下一个位置

我们建议使用以下任何章节:

5.1.2. Quarkus 上的第一个 SOAP Web 服务

在本指南中,我们将说明如何创建 Quarkus 应用程序公开简单的 SOAP Web 服务。

首先创建项目

在继续此处前,请按照 项目创建 指南操作。

5.1.2.1. 您好好!Web 服务

pom.xml 已就绪,您可以添加一个简单的 Hello world!src/main/java 中的 Web 服务.

代码示例

本节中使用的代码片段示例来自 Quarkus CXF 源树中的服务器 集成测试

首先添加服务接口:

HelloService.java

package io.quarkiverse.cxf.it.server;

import jakarta.jws.WebMethod;
import jakarta.jws.WebService;

/**
 * The simplest Hello service.
 */
@WebService(name = "HelloService", serviceName = "HelloService")
public interface HelloService {

    @WebMethod
    String hello(String text);

}

然后实现:

HelloServiceImpl.java

package io.quarkiverse.cxf.it.server;

import jakarta.jws.WebMethod;
import jakarta.jws.WebService;

/**
 * The simplest Hello service implementation.
 */
@WebService(serviceName = "HelloService")
public class HelloServiceImpl implements HelloService {

    @WebMethod
    @Override
    public String hello(String text) {
        return "Hello " + text + "!";
    }

}

要使在特定路径下公开的实现,您需要将以下配置添加到 application.properties 中:

# The context path under which all services will be available
quarkus.cxf.path = /soap

# Publish "HelloService" under the context path /${quarkus.cxf.path}/hello
quarkus.cxf.endpoint."/hello".implementor = io.quarkiverse.cxf.it.server.HelloServiceImpl
quarkus.cxf.endpoint."/hello".features = org.apache.cxf.ext.logging.LoggingFeature
提示

所有配置属性都记录在 配置属性参考 中。

提示

检查 服务端点和路径 章节,以了解在特定路径下公开服务端点的替代方法。

使用这些文件,您可以在 dev 模式中启动 Quarkus:

$ mvn quarkus:dev

这将编译项目,并在后台启动应用程序。

您可以使用 curl 或其它 SOAP 客户端测试服务。

首先,让我们在 http://localhost:8080/soap/hello?wsdl 下查看自动生成的 WSDL:

$ curl http://localhost:8080/soap/hello?wsdl
<?xml version='1.0' encoding='UTF-8'?>
<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://server.it.cxf.quarkiverse.io/"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http"
    name="HelloService" targetNamespace="http://server.it.cxf.quarkiverse.io/">
  <wsdl:types>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://server.it.cxf.quarkiverse.io/" attributeFormDefault="unqualified" elementFormDefault="unqualified" targetNamespace="http://server.it.cxf.quarkiverse.io/">
  <xsd:element name="hello" type="tns:hello"/>
  <xsd:complexType name="hello">
    <xsd:sequence>
      <xsd:element minOccurs="0" name="arg0" type="xsd:string"/>
    </xsd:sequence>
  </xsd:complexType>
  <xsd:element name="helloResponse" type="tns:helloResponse"/>
  <xsd:complexType name="helloResponse">
    <xsd:sequence>
      <xsd:element minOccurs="0" name="return" type="xsd:string"/>
    </xsd:sequence>
  </xsd:complexType>
</xsd: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="HelloService">
    <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="HelloServiceSoapBinding" type="tns:HelloService">
    <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="HelloService">
    <wsdl:port binding="tns:HelloServiceSoapBinding" name="HelloServicePort">
      <soap:address location="http://localhost:8080/soap/hello"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

其次,让我们向服务发送 SOAP 请求:

$ curl -v -X POST -H "Content-Type: text/xml;charset=UTF-8" \
    -d \
      '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
        <soap:Body><ns2:hello xmlns:ns2="http://server.it.cxf.quarkiverse.io/"><arg0>World</arg0></ns2:hello></soap:Body>
       </soap:Envelope>' \
    http://localhost:8080/soap/hello
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ns1:helloResponse xmlns:ns1="http://server.it.cxf.quarkiverse.io/">
      <return>Hello World!</return>
    </ns1:helloResponse>
  </soap:Body>
</soap:Envelope>

您可以在 SOAP 响应中看到预期的 & lt;return>Hello World!</return >。

5.1.2.2. 在 dev 模式运行时添加日志记录功能

有时,可以方便检查由服务器或客户端接收或发送的 SOAP 消息。这可以通过将 quarkus-cxf-rt-features-logging 扩展添加到 pom.xml 来轻松实现。

提示

尝试在 Quarkus dev 模式运行时执行此操作。您应该会在源树中保存更改时重新编译并重新部署应用程序。

将其添加到 pom.xml

<dependency>
    <groupId>io.quarkiverse.cxf</groupId>
    <artifactId>quarkus-cxf-rt-features-logging</artifactId>
</dependency>

application.properties中启用 SOAP 有效负载日志记录

quarkus.cxf.endpoint."/hello".features=org.apache.cxf.ext.logging.LoggingFeature

在发送一个新的 SOAP 请求并在应用程序控制台中看到一些 SOAP 有效负载后:

2023-01-11 22:12:21,315 INFO  [org.apa.cxf.ser.Hel.REQ_IN] (vert.x-worker-thread-0) REQ_IN
    Address: http://localhost:8080/soap/hello
    HttpMethod: POST
    Content-Type: text/xml;charset=UTF-8
    ExchangeId: af10747a-8477-4c17-bf5f-2a4a3a95d61c
    ServiceName: HelloService
    PortName: HelloServicePort
    PortTypeName: HelloService
    Headers: {Accept=*/*, User-Agent=curl/7.79.1, content-type=text/xml;charset=UTF-8, Host=localhost:8080, Content-Length=203, x-quarkus-hot-deployment-done=true}
    Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body><ns2:hello xmlns:ns2="http://server.it.cxf.quarkiverse.io/"><arg0>World</arg0></ns2:hello></soap:Body>
</soap:Envelope>


2023-01-11 22:12:21,327 INFO  [org.apa.cxf.ser.Hel.RESP_OUT] (vert.x-worker-thread-0) RESP_OUT
    Address: http://localhost:8080/soap/hello
    Content-Type: text/xml
    ResponseCode: 200
    ExchangeId: af10747a-8477-4c17-bf5f-2a4a3a95d61c
    ServiceName: HelloService
    PortName: HelloServicePort
    PortTypeName: HelloService
    Headers: {}
    Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns1:helloResponse xmlns:ns1="http://server.it.cxf.quarkiverse.io/"><return>Hello World!</return></ns1:helloResponse></soap:Body></soap:Envelope>

5.1.2.3. 进一步的步骤

您可能希望继续打包应用程序,以便在 JVM 上运行或原生运行

5.1.3. Quarkus 上的第一个 SOAP 客户端

在本指南中,我们解释了如何创建充当远程 Web 服务客户端的简单 Quarkus 应用程序。

首先创建项目

在继续此处前,请按照 项目创建 指南操作。

5.1.3.1. 用于测试的远程 Web 服务

首先,我们需要一些远程 Web 服务来连接。我们可以使用一个在容器中运行的简单 计算器 Web 服务 来实现这一目的。

$ docker run -p 8082:8080 quay.io/l2x6/calculator-ws:1.0

容器启动并运行后,我们可以检查其 WSDL

$ curl -s http://localhost:8082/calculator-ws/CalculatorService?wsdl
<?xml version="1.0" ?>
<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://www.jboss.org/eap/quickstarts/wscalculator/Calculator" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="CalculatorService" targetNamespace="http://www.jboss.org/eap/quickstarts/wscalculator/Calculator">

  ...

  <wsdl:binding name="CalculatorServiceSoapBinding" type="tns:CalculatorService">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"></soap:binding>
    <wsdl:operation name="add">
      <soap:operation soapAction="" style="document"></soap:operation>
      <wsdl:input name="add">
        <soap:body use="literal"></soap:body>
      </wsdl:input>
      <wsdl:output name="addResponse">
        <soap:body use="literal"></soap:body>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="subtract">
      <soap:operation soapAction="" style="document"></soap:operation>
      <wsdl:input name="subtract">
        <soap:body use="literal"></soap:body>
      </wsdl:input>
      <wsdl:output name="subtractResponse">
        <soap:body use="literal"></soap:body>
      </wsdl:output>
    </wsdl:operation>

    ...

  </wsdl:binding>

  ...

</wsdl:definitions>

正如您在 WSDL 中看到的那样,该服务提供一些基本的算术操作,如添加、减去 等。

我们使用 curl 测试它:

$ curl -s \
    -X POST \
    -H "Content-Type: text/xml;charset=UTF-8" \
    -d \
        '<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
            <Body>
                <add xmlns="http://www.jboss.org/eap/quickstarts/wscalculator/Calculator">
                    <arg0 xmlns="">7</arg0> 
1

                    <arg1 xmlns="">4</arg1>
                </add>
            </Body>
        </Envelope>' \
    http://localhost:8082/calculator-ws/CalculatorService
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ns2:addResponse xmlns:ns2="http://www.jboss.org/eap/quickstarts/wscalculator/Calculator">
      <return>11</return> 
2

    </ns2:addResponse>
  </soap:Body>
</soap:Envelope>
1
添加 74的请求
2
11 - 返回操作的值

5.1.3.2. SOAP 客户端

现在,让我们来看看如何在 Quarkus 应用程序中获取客户端。

首先,我们需要服务端点接口(SEI)以及它所需的所有所有其他模型类。

获取它们的方法有几种:

  • 手动写入
  • 使用 Java 编写 Web Sevice 项目复制
  • 具有包含模型类的 Maven 工件,可能由 Service 项目提供
  • 从 WSDL 生成模型类

最后一个选项往往是客户端应用程序最简单且最灵活的选项。

提示

如果要使用这种方法,请首先遵循 从 WSDL 部分中生成 Java,然后继续后续步骤。

5.1.3.3. 使用 SEI 作为客户端

在我们的情形中,Service Endpoint Interface (SEI)是 org.jboss.eap.quickstarts.wscalculator.calculator.CalculatorService

在 Quarkus 中,我们可以通过 CDI 获取其实例。

为了便于测试,我们将将其嵌套在 REST 服务中:

CxfClientResource.java

package io.quarkiverse.cxf.client.it;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;

import org.jboss.eap.quickstarts.wscalculator.calculator.CalculatorService;

import io.quarkiverse.cxf.annotation.CXFClient;

@Path("/cxf/calculator-client")
public class CxfClientRestResource {

    @CXFClient("myCalculator") 
1

    CalculatorService myCalculator;

    @GET
    @Path("/add")
    @Produces(MediaType.TEXT_PLAIN)
    public int add(@QueryParam("a") int a, @QueryParam("b") int b) {
        return myCalculator.add(a, b); 
2

    }

}

1
让 CDI 容器注入客户端的实例。@CXFClient ("myCalculator") 实际上等同于 @Inject @CXFClient ("myCalculator")
2
调用 add 操作,从而调用远程 Web 服务

除了上述之外,我们是否需要使用此功能?- 除了上述之外,我们需要向 application.properties 中的 CXF Quarkus 扩展告知其他内容:

application.properties

cxf.it.calculator.baseUri=http://localhost:8082
quarkus.cxf.client.myCalculator.wsdl = ${cxf.it.calculator.baseUri}/calculator-ws/CalculatorService?wsdl
quarkus.cxf.client.myCalculator.client-endpoint-url = ${cxf.it.calculator.baseUri}/calculator-ws/CalculatorService
quarkus.cxf.client.myCalculator.service-interface = org.jboss.eap.quickstarts.wscalculator.calculator.CalculatorService

提示

所有客户端配置属性都记录在 配置属性参考 中。

对于以上所有文件,我们应当能够在 Quarkus dev 模式中启动应用程序

$ mvn quarkus:dev
...
INFO  [io.quarkus] (Quarkus Main Thread) ... Listening on: http://localhost:8080

并通过向它发送一些请求来测试它:

$ curl -s 'http://localhost:8080/cxf/calculator-client/add?a=5&b=6'
11

其中 11 是添加 56 的正确结果。

5.1.3.4. 进一步的步骤

您可能需要继续

5.1.4. 配置

Quarkus CXF 会公开大量配置选项。每个扩展都会在其 参考页面底部 记录了其选项。

配置选项可以在 application.properties 文件中设置,或者通过环境变量设置 - 请参阅 Quarkus 配置参考

5.1.4.1. Bean 引用

Quarkus CXF 的多个配置选项允许引用 Quarkus CDI 容器中出现的 Bean。功能和拦截器 是它们的典型示例。

可以通过两种方式在配置中设置 bean 引用:根据类型或 bean 名称。

5.1.4.1.1. 根据类型进行 bean 引用

下面是一个示例:

application.properties

# bean reference by type
quarkus.cxf.endpoint."/hello".features = org.apache.cxf.ext.logging.LoggingFeature

当使用按类型名称的引用时,解析进行如下:

  • 按照类型在 Quarkus CDI 容器中查找 bean。
  • 如果 bean 可用,则会使用它。
  • 如果多个 Bean 分配给给定类型,则抛出异常。
  • 如果没有匹配的 bean 可用,则会加载类并执行尝试以使用其默认构造器实例化它。
5.1.4.1.2. Bean 名称引用

下面是一个示例:

application.properties

# bean reference by bean name
quarkus.cxf.endpoint."/fruit".features = #myCustomLoggingFeature

使用 bean 名称的引用时,记者会按名称查找 Quarkus CDI 容器。名为 myCustomLoggingFeature 的命名 Bean 可以定义如下:

import org.apache.cxf.ext.logging.LoggingFeature;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;

class Producers {

    @Produces
    @ApplicationScoped
    @Named("myCustomLoggingFeature")
    LoggingFeature myCustomLoggingFeature() {
        LoggingFeature loggingFeature = new LoggingFeature();
        loggingFeature.setPrettyLogging(true);
        return loggingFeature;
    }
}

5.1.5. 用于在 JVM 或原生运行的软件包

在本章中,我们解释了如何打包 Quarkus CXF 应用程序,以便在 JVM 上运行或原生运行它。

5.1.5.1. JVM 模式

在 SOAP 客户端和 SOAP 服务的入门指南中,我们只在 Quarkus dev mode: Quarkus 工具中工作,在后台监视工作区的更改,根据需要重新编译并重新载入应用程序。

我们使用开发完成后,如何在 JVM 上运行应用程序?

首先,我们需要使用 Maven 打包它:

$ mvn package

在 JVM 上运行应用程序所需的库可在 target/quarkus-app 目录中找到:

$ ls -lh target/quarkus-app
drwxr-xr-x. 2 ppalaga ppalaga 4.0K Jan 12 22:29 app
drwxr-xr-x. 4 ppalaga ppalaga 4.0K Jan 12 22:29 lib
drwxr-xr-x. 2 ppalaga ppalaga 4.0K Jan 12 22:29 quarkus
-rw-r-r--. 1 ppalaga ppalaga 6.1K Jan 12 22:29 quarkus-app-dependencies.txt
-rw-r-r--. 1 ppalaga ppalaga  678 Jan 12 22:29 quarkus-run.jar

我们可以按如下所示启动应用程序:

$ java -jar target/quarkus-app/quarkus-run.jar

您可以使用 curl 发送一些 SOAP 请求,以确保应用程序正常工作。

5.1.5.2. 原生模式

Quarkus 为构建 GraalVM 原生镜像和 Quarkus CXF 完全遵循的类支持。

镜像

GraalVM 原生镜像是平台特定的可执行文件,您可以在没有 JVM 的情况下直接运行。与在 JVM 模式下运行相同的应用相比,它们启动速度更快,且内存更少。

code.quarkus.redhat.com 生成的 pom.xml 文件包含构建 原生 镜像所需的原生配置集:

<profile>
  <id>native</id>
  <activation>
    <property>
      <name>native</name>
    </property>
  </activation>
  <properties>
    <skipITs>false</skipITs>
    <quarkus.package.type>native</quarkus.package.type>
  </properties>
</profile>

此外,如 第 5.1.1 节 “创建新项目” 部分所述,您需要 GraalVM native-image 工具。

您应该在本地安装它,并有正确设置 GRAALVM_HOME 环境变量,或者需要生成 Linux 原生的可执行文件 swig-mvapich,您可以使用 docker

使用本地安装 GraalVM

# Make sure $GRAALVM_HOME is set properly
$ echo $GRAALVM_HOME
/home/{user}/.sdkman/candidates/java/{major}.{minor}.r{java-version}-grl

# Produce the native executable
mvn package -Pnative

提示

Quarkus 对 GraalVM 版本非常自选。使用本地安装时,请务必使用 Quarkus 首选版本。为此,您可以打开 pom.xml 中导入的 quarkus-bom,并在其中搜索 graalvm。如果使用 Docker,Quarkus 会为您拉取正确的版本。

使用 docker

# Produce the native executable
mvn package -Pnative -Dquarkus.native.container-build=true

对于简单的应用程序,这可能需要一分钟的时间。

构建完成后,原生可执行文件应 在目标目录中 可用:

$ ls -l target
...
-rwxr-xr-x. 1 ppalaga ppalaga  71M Jan 11 22:42 quarkus-cxf-integration-test-server-1.8.0-SNAPSHOT-runner
...

正如您所见,它的大小只能为 71 MB,并且是可执行的。

您可以按照以下方式运行它:

$ target/*-runner
...
INFO  [io.quarkus] (main) quarkus-cxf-integration-test-server 1.8.0-SNAPSHOT native (powered by Quarkus
2.15.2.Final) started in 0.042s. Listening on: http://0.0.0.0:8080
...

同样,您可以使用 curl 发送一些 SOAP 请求,以确保原生可执行文件正常工作。

不要忘记比较内存用量、第一次请求和其他性能指标与您前面使用的堆栈进行比较,并共享结果!

5.1.5.3. 原生镜像:附加资源

您还可以参考以下链接,其中包含有关如何处理原生镜像的提示。

5.1.5.4. 创建容器镜像

请参阅 Quarkus 容器镜像 指南。

5.1.6. 日志记录

有关 Quarkus 上日志记录的基本信息,请参阅 Quarkus Logging 指南,例如

5.1.6.1. 有效负载日志记录

history

从 Quarkus CXF 2.6.0 开始,有效负载日志记录功能可以通过 io.quarkiverse.cxf:quarkus-cxf 扩展提供。在 2.6.0 之前,它可以通过单独的扩展 io.quarkiverse.cxf:quarkus-cxf-rt-features-logging 提供,它将在以后的版本中删除。

有效负载日志记录功能主要通过 org.apache.cxf.ext.logging.LoggingFeature 类实现。

您可以通过几种方法在客户端或服务端点上设置该功能。

5.1.6.2. 通过配置属性配置有效负载日志记录

5.1.6.2.1. 全局设置

自 Quarkus CXF 2.6.0 开始存在全局日志记录选项。它们需要使用 quarkus.cxf.logging.enabled-for 启用。有四个可能的值:

  • none (默认)- 没有为客户端和服务端点启用全局日志记录功能
  • 客户端 - 为应用程序中的所有客户端启用全局日志记录功能
  • 服务 - 为应用程序中的所有服务端点启用全局日志记录功能
  • 两者都 为应用程序中的所有客户端和服务端点启用全局日志记录功能

全局设置可以在 客户端或 服务端点级别上覆盖。

application.properties

# Global settings
quarkus.cxf.logging.enabled-for = both
quarkus.cxf.logging.pretty = true

所有日志记录配置选项都列在 quarkus-cxf 参考页面中。

提示

本页中提到的所有日志记录属性都是 运行时 配置选项。因此,您可以在启动应用程序时传递它们,而无需重新构建它。它可以通过在命令行中传递系统属性(例如,-Dquarkus.cxf.logging.enabled-for=both)或设置环境变量(例如,导出 QUARKUS_CXF_LOGGING_ENABLED_FOR=both)。

5.1.6.2.2. 每个客户端和每个服务端点设置

从 Quarkus CXF 2.5.0 开始,可以通过在 application.properties 中设置适当的选项来声明性地配置并附加到客户端或服务端点:

application.properties

# For a service:
quarkus.cxf.endpoint."/hello".logging.enabled = true
quarkus.cxf.endpoint."/hello".logging.pretty = true
# For a client:
quarkus.cxf.client.hello.logging.enabled = true
quarkus.cxf.client.hello.logging.pretty = true

所有日志记录配置选项都记录在 quarkus-cxf 参考页面中:

要使用默认设置附加实例,您可以执行以下操作之一:

  1. application.properties 中:

    # For a service:
    quarkus.cxf.endpoint."/hello".features = org.apache.cxf.ext.logging.LoggingFeature
    # For a client:
    quarkus.cxf.client."myClient".features = org.apache.cxf.ext.logging.LoggingFeature
    提示

    用户指南 的第一 SOAP Web 服务 章节中有一个示例。

    或者

  2. 使用 CXF 的 @Features 注释:

    @org.apache.cxf.feature.Features (features = {"org.apache.cxf.ext.logging.LoggingFeature"})
    @WebService(endpointInterface = "org.acme.SayHi", targetNamespace = "uri:org.acme")
    public class SayHiImplementation implements SayHi {
       public long sayHi(long arg) {
           return arg;
       }
       //...
    }
5.1.6.3.1. 生成自定义 LoggingFeature bean

如果您需要一些自定义逻辑来设置 LoggingFeature,您可以生成名为 LoggingFeature bean :

import org.apache.cxf.ext.logging.LoggingFeature;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;

class Producers {

    @Produces
    @ApplicationScoped
    @Named("limitedLoggingFeature") // "limitedLoggingFeature" is redundant if the name of the method is the same
    LoggingFeature limitedLoggingFeature() {
        LoggingFeature loggingFeature = new LoggingFeature();
        loggingFeature.setPrettyLogging(true);
        loggingFeature.setLimit(1024);
        return loggingFeature;
    }
}

然后,您可以使用其名称以 prefixed in application.properties 来引用它:

# For a service:
quarkus.cxf.endpoint."/hello".features = #limitedLoggingFeature
# For a client:
quarkus.cxf.client.hello.features = #limitedLoggingFeature

5.1.7. 使用 JAXB 复杂的 SOAP 有效负载

我们为 Quarkus SOAP 客户端和 SOAP 服务入门指南 处理仅有原语参数和返回值的服务,如整数和字符串。让我们来看一下传递和接收更复杂的对象。

例如,让我们创建用于管理有问题的应用程序。

注意

本节中使用的代码片段示例来自 Quarkus CXF 源树中的服务器 集成测试

由于我们的混淆的表示应该比较复杂,因此让我们将其作为具有几个属性的 Java Bean 进行建模:

package io.quarkiverse.cxf.it.server;

import java.util.Objects;

import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;

@XmlType(name = "Fruit")
@XmlRootElement
public class Fruit {

    private String name;

    private String description;

    public Fruit() {
    }

    public Fruit(String name, String description) {
        this.name = name;
        this.description = description;
    }

    public String getName() {
        return name;
    }

    @XmlElement
    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    @XmlElement
    public void setDescription(String description) {
        this.description = description;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Fruit)) {
            return false;
        }

        Fruit other = (Fruit) obj;

        return Objects.equals(other.getName(), this.getName());
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.getName());
    }
}

您可能已经注意到,我们使用了一些 JAXB 注释,如 @XmlElement@XmlRootElement@XmlType。这是为了控制 bean 从和 XML 序列化和反序列化。

5.1.7.1. 自动注册以反应

JAXB 是一个基于反射的序列化框架。当学习 GraalVM 原生镜像时,您通常会听到的第一件事是在构建时注册类、字段和方法来反映。使用普通 GraalVM,您必须手动通过 reflection-config.json 来执行此操作。至少,对于您编写的类,至少要注意。不要使用 Quarkus。quarkus-jaxb 扩展(其 quarkus-cxf 依赖)能够扫描应用的类路径以获取带有 JAXB 注解的类类路径,并自动注册它们,以自动反映。

因此,在 Quarkus 上使用复杂有效负载与库存 CXF 不同。JAXB serialization 和 deserialization 将开箱即用,无需任何额外的配置。

5.1.7.2. SEI 和实现

用于管理模糊的服务端点接口(SEI)可能类似如下:

package io.quarkiverse.cxf.it.server;

import java.util.Set;

import jakarta.jws.WebMethod;
import jakarta.jws.WebService;

@WebService
public interface FruitService {

    @WebMethod
    Set<Fruit> list();

    @WebMethod
    Set<Fruit> add(Fruit fruit);

    @WebMethod
    Set<Fruit> delete(Fruit fruit);
}

我们可以根据需要实施 SEI:

package io.quarkiverse.cxf.it.server;

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

import jakarta.jws.WebService;

@WebService(serviceName = "FruitService")
public class FruitServiceImpl implements FruitService {

    private Set<Fruit> fruits = Collections.synchronizedSet(new LinkedHashSet<>());

    public FruitServiceImpl() {
        fruits.add(new Fruit("Apple", "Winter fruit"));
        fruits.add(new Fruit("Pineapple", "Tropical fruit"));
    }

    @Override
    public Set<Fruit> list() {
        return fruits;
    }

    @Override
    public Set<Fruit> add(Fruit fruit) {
        fruits.add(fruit);
        return fruits;
    }

    @Override
    public Set<Fruit> delete(Fruit fruit) {
        fruits.remove(fruit);
        return fruits;
    }
}

5.1.7.3. application.properties

实现非常简单,您只需要使用 application.properties 定义端点。

quarkus.cxf.endpoint."/fruits".implementor = io.quarkiverse.cxf.it.server.FruitServiceImpl
quarkus.cxf.endpoint."/fruits".features = org.apache.cxf.ext.logging.LoggingFeature

5.1.7.4. 使用 Quarkus dev 模式和 curl测试

使用上述文件,您可以在 dev 模式中启动 Quarkus 工具:

$ mvn quarkus:dev
...
INFO  [io.quarkus] (Quarkus Main Thread) ... Listening on: http://localhost:8080

然后,通过调用其 list 操作来检查该服务是否正常工作:

$ curl -v -X POST -H "Content-Type: text/xml;charset=UTF-8" \
    -d \
      '<soapenv:Envelope
      xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
      xmlns:ns1="http://server.it.cxf.quarkiverse.io/">
        <soapenv:Body>
            <ns1:list/>
        </soapenv:Body>
    </soapenv:Envelope>' \
    http://localhost:8080/soap/fruits
...
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ns1:listResponse xmlns:ns1="http://server.it.cxf.quarkiverse.io/">
      <return xmlns:ns2="http://server.it.cxf.quarkiverse.io/">
        <description>Winter fruit</description>
        <name>Apple</name>
      </return>
      <return xmlns:ns2="http://server.it.cxf.quarkiverse.io/">
        <description>Tropical fruit</description>
        <name>Pineapple</name>
      </return>
    </ns1:listResponse>
  </soap:Body>
</soap:Envelope>

正如您所见,端点返回了默认情况下提供的两个 fruits ApplePineapple

现在,让我们再添加另一个问题,表示一个 Orange

$ curl -v -X POST -H "Content-Type: text/xml;charset=UTF-8" \
    -d \
     '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
        <soap:Body>
          <ns2:add xmlns:ns2="http://server.it.cxf.quarkiverse.io/">
            <arg0>
              <description>Mediterranean fruit</description>
              <name>Orange</name>
            </arg0>
          </ns2:add>
       </soap:Body></soap:Envelope>' \
    http://localhost:8080/soap/fruits
...
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ns1:addResponse xmlns:ns1="http://server.it.cxf.quarkiverse.io/">
      <return xmlns:ns2="http://server.it.cxf.quarkiverse.io/">
        <description>Winter fruit</description>
        <name>Apple</name>
      </return>
      <return xmlns:ns2="http://server.it.cxf.quarkiverse.io/">
        <description>Tropical fruit</description>
        <name>Pineapple</name>
      </return>
      <return xmlns:ns2="http://server.it.cxf.quarkiverse.io/">
        <description>Mediterranean fruit</description>
        <name>Orange</name>
      </return>
    </ns1:addResponse>
  </soap:Body>
</soap:Envelope>

我们可以看到 Orange 已如预期在返回的列表中添加。

5.1.7.5. 进一步的步骤

您可能希望继续打包应用程序,以便在 JVM 上运行或原生运行

5.1.8. 合同第一和代码第一种方法

Quarkus CXF 完全支持第一个和代码第一个开发模式。

5.1.8.1. 合同第一个客户端

WSDL 描述 SOAP 服务。它是定义操作的合同,其参数和返回值等。WSDL 足以生成完整客户端的代码。CXF 为其提供 wsdl2java 工具。

Quarkus CXF 将 wsdl2java 嵌套在 quarkus-cxf 扩展中,因此您不需要直接使用它。

按照用户指南 的 WSDL 部分中生成模型类,以了解更多有关如何使用它的详细信息。

您可能还想检查 CXF 开发一个 Consumer 作为一般简介。

5.1.8.2. 合同第一个服务

在实施服务时,来自 WSDL 的 Java 代码 也可能会很方便。wsdl2java 可以为您生成模型类(带有 JAXB 注释)和服务接口(带有 JAX-WS 注释)。然后,您的任务是为这些接口提供实施。

您可能需要检查 CXF 文档中的 WSDL 第一服务开发 部分,以更好地了解底层概念。

5.1.8.3. 代码第一个服务

您提出的另一个有效选项是使用 JAX-WS 和 JAXB 在 Java 中编写您的服务。然后,您有两个选择如何获取 WSDL 合同:

  1. 启动服务并将客户端指向 http://your-host/your-service?wsdl
  2. 在构建时从 Java 类生成 WSDL 文档
提示

如需更多信息,请参阅 CXF 文档的 Code first development 部分。

5.1.8.4. 从 WSDL 生成模型类

在 Quarkus 代码生成阶段,quarkus-cxf 扩展支持从 WSDL 生成 Java 类。

代码示例

本节中显示的代码片段来自 Quarkus CXF 的源树中的客户端 集成测试。您可能需要将其作为可执行示例进行检查。

您需要设置几个内容才能使 CXF 代码生成正常工作:

  • 在项目中具有 io.quarkiverse.cxf:quarkus-cxf 依赖项
  • 对于 Maven 项目,generate-code 目标需要在 quarkus-maven-plugin 配置中存在:

    pom.xml

                <plugin>
                    <groupId>io.quarkus</groupId>
                    <artifactId>quarkus-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <goals>
                                <goal>build</goal>
                                <goal>generate-code</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>

  • 对于 Gradle 项目,不需要额外的 io.quarkus 插件配置
  • 将您的 WSDL 文件置于 src/main/resourcessrc/test/resources 或其中的任何子目录下。
  • 您的 WSDL 文件名必须以 .wsdl结尾
  • quarkus.cxf.codegen.wsdl2java.includes配置属性设置为 与您要处理的 WSDL 文件匹配的模式。如果要处理 src/main/resources/wsdlsrc/test/resources/wsdl 下的所有 WSDL 文件,请按如下所示设置:

    application.properties

    quarkus.cxf.codegen.wsdl2java.includes = wsdl/*.wsdl

这将在 target/generated-sources/wsdl2javatarget/generated-test-sources/wsdl2java 目录中生成 Java 类。它们由编译器插件自动选择。因此,我们可以自由地引用应用程序或测试代码。

注意

请注意,quarkus-cxf 代码生成使用来自 CXF 的 wsdl2Java 工具(位于 hood 下)。wsdl2Java 通过包含和排除选择的每个 WSDL 文件单独调用。

通过 quarkus.cxf.codegen. wsdl2java.additional-params 配置参数可以将自定义参数传递给 wsdl2java。

如果需要为每个 WSDL 文件使用不同的 additional-params,您可能需要为它们分别定义一个单独的命名参数。下面是一个示例:

application.properties

# Parameters for foo.wsdl
quarkus.cxf.codegen.wsdl2java.foo-params.includes = wsdl/foo.wsdl
quarkus.cxf.codegen.wsdl2java.foo-params.wsdl-location = wsdl/foo.wsdl
# Parameters for bar.wsdl
quarkus.cxf.codegen.wsdl2java.bar-params.includes = wsdl/bar.wsdl
quarkus.cxf.codegen.wsdl2java.bar-params.wsdl-location = wsdl/bar.wsdl
quarkus.cxf.codegen.wsdl2java.bar-params.xjc = ts

提示

io.quarkiverse.cxf:quarkus-cxf-xjc-plugins 依赖项添加到项目,以便可以使用 -xjc-Xbg,-xjc-Xdv,-xjc-Xjavadoc,-xjc-Xproperty-listener,-xjc-Xts-xjc-Xwsdlextension wsdl2java.

5.1.8.4.1. 非 ASCII Characters

有时,由于代码中包含非 ASCII 字符,因此 wsdl2java 自动生成的 Java 类可能无法与 GraalVM 完全兼容。在原生镜像构建过程中可能会出现与以下类似的例外:

[quarkus-dalkia-ticket-loader-1.0.0-SNAPSHOT-runner:26]      compile: 161 459,15 ms,  8,54 GB
[quarkus-dalkia-ticket-loader-1.0.0-SNAPSHOT-runner:26]        image: 158 272,73 ms,  8,43 GB
[quarkus-dalkia-ticket-loader-1.0.0-SNAPSHOT-runner:26]        write:     205,82 ms,  8,43 GB
Fatal error:com.oracle.svm.core.util.VMError$HostedError: java.lang.RuntimeException: oops : expected ASCII string! com.oracle.svm.reflect.OperationOrderStatusType_CRÉÉ_f151156b0d42ecdbdfb919501d8a86dda8733012_1456.hashCode
    at com.oracle.svm.core.util.VMError.shouldNotReachHere(VMError.java:72)

以下是 Java 类中自动生成的非 ASCII 字符示例:

@XmlType(name = "OperationOrderStatusType")
@XmlEnum
public enum OperationOrderStatusType {

    @XmlEnumValue("Cr\u00e9\u00e9")
    CRÉÉ("Cr\u00e9\u00e9"),
    @XmlEnumValue("A communiquer")
    A_COMMUNIQUER("A communiquer"),
    @XmlEnumValue("En attente de r\u00e9ponse")
    EN_ATTENTE_DE_RÉPONSE("En attente de r\u00e9ponse"),
    @XmlEnumValue("Attribu\u00e9")
    ATTRIBUÉ("Attribu\u00e9"),
    @XmlEnumValue("Clotur\u00e9")
    CLOTURÉ("Clotur\u00e9"),
    @XmlEnumValue("Annul\u00e9")
    ANNULÉ("Annul\u00e9");
    private final String value;

    OperationOrderStatusType(String v) {
        value = v;
    }

    public String value() {
        return value;
    }

    public static OperationOrderStatusType fromValue(String v) {
        for (OperationOrderStatusType c: OperationOrderStatusType.values()) {
            if (c.value.equals(v)) {
                return c;
            }
        }
        throw new IllegalArgumentException(v);
    }
}

\u 开头的任何内容都将是一个问题。因此,需要以下重构:

@XmlType(name = "OperationOrderStatusType")
@XmlEnum
public enum OperationOrderStatusType {

    @XmlEnumValue("Créé")
    CREE("Créé"),
    @XmlEnumValue("A communiquer")
    A_COMMUNIQUER("A communiquer"),
    @XmlEnumValue("En attente de réponse")
    EN_ATTENTE_DE_REPONSE("En attente de réponse"),
    @XmlEnumValue("Attribué")
    ATTRIBUE("Attribué"),
    @XmlEnumValue("Cloturé")
    CLOTURE("Cloturé"),
    @XmlEnumValue("Annulé")
    ANNULE("Annulé");
    private final String value;

    OperationOrderStatusType(String v) {
        value = v;
    }

    public String value() {
        return value;
    }

    public static OperationOrderStatusType fromValue(String v) {
        for (OperationOrderStatusType c: OperationOrderStatusType.values()) {
            if (c.value.equals(v)) {
                return c;
            }
        }
        throw new IllegalArgumentException(v);
    }
}

5.1.8.5. 从 Java 生成 WSDL 文档

如果您的服务位于 http://your-host/your-service?wsdl 的 WSDL 服务不足,因为您想要将其分发为 Maven 构件,那么您可以在构建时使用 java2ws 生成 WSDL 文档。

您不需要直接调用 CXF 提供的 java2ws 工具,您不必使用 cxf-java2ws-plugin

quarkus-cxf wraps java2ws,您可以在 application.properties 中配置它作为应用程序的任何其他方面。

下面是一个示例:

注意

本节中使用的代码片段示例来自 Quarkus CXF 源树中的服务器 集成测试

application.properties

quarkus.cxf.java2ws.includes = io.quarkiverse.cxf.it.server.HelloServiceImpl,io.quarkiverse.cxf.it.server.FaultyHelloServiceImpl
quarkus.cxf.java2ws.wsdl-name-template = %TARGET_DIR%/Java2wsTest/%SIMPLE_CLASS_NAME%-from-java2ws.wsdl

在这里,我们已指示 java2ws 为两个服务类生成 WSDLs,即 HelloServiceImplFaultyHelloServiceImpl

注解

服务类必须使用 jakarta.xml.ws.WebService 注解,以进行 java2ws 处理。

这两个生成的 WSDL 文档将分别存储为 target/Java2wsTest/FaultyHelloServiceImpl-from-java2ws.wsdltarget/Java2wsTest/HelloServiceImpl-from-java2ws.wsdl

注意

与在 Quarkus 源生成阶段执行的 wsdl2java 不同,java2ws 是编译后发生的 Quarkus 扩展的一部分。java2ws 的输入是,所有 Java 类文件之后。因此,您不需要将 generate-code 添加到 java2wsquarkus-maven-plugin 中。

5.1.8.5.1. 另请参阅

5.1.9. CXF Interceptors 和 features, JAX-WS Handlers

查看以下章节以了解定制 SOAP 请求和响应处理的各种方法:

5.1.9.1. CXF Interceptors 和功能

CXF 拦截器CXF 功能 可以使用注解或 application.properties 配置添加到您的客户端或服务器。

虽然 CXF 提供了多个开箱即用的嵌入式拦截器和功能,但您也可以集成自定义开发的实现。

注解可用于服务接口或实施类。

@org.apache.cxf.feature.Features (features = {"org.apache.cxf.ext.logging.LoggingFeature"})
@org.apache.cxf.interceptor.InInterceptors (interceptors = {"org.acme.Test1Interceptor" })
@org.apache.cxf.interceptor.InFaultInterceptors (interceptors = {"org.acme.Test2Interceptor" })
@org.apache.cxf.interceptor.OutInterceptors (interceptors = {"org.acme.Test1Interceptor" })
@org.apache.cxf.interceptor.InFaultInterceptors (interceptors = {"org.acme.Test2Interceptor","org.acme.Test3Intercetpor" })
@WebService(endpointInterface = "org.acme.SayHi", targetNamespace = "uri:org.acme")
public class SayHiImplementation implements SayHi {
   public long sayHi(long arg) {
       return arg;
   }
   //...
}

您还可以在 application.properties 文件中定义配置。

quarkus.cxf.endpoint."/greeting-service".features=org.apache.cxf.ext.logging.LoggingFeature
quarkus.cxf.endpoint."/greeting-service".in-interceptors=org.acme.Test1Interceptor
quarkus.cxf.endpoint."/greeting-service".out-interceptors=org.acme.Test1Interceptor
quarkus.cxf.endpoint."/greeting-service".in-fault-interceptors=org.acme.Test2Interceptor,org.acme.Test3Intercetpor
quarkus.cxf.endpoint."/greeting-service".out-fault-interceptors=org.acme.Test1Intercetpor
类加载

功能和拦截器类都会首先通过 CDI 加载。它们可以通过完全限定类名称或 bean 名称来引用。

如果没有 CDI Bean 可用,则会调用没有参数的构造器来实例化每个类。

5.1.9.2. JAX-WS 处理程序

作为 @HandlerChain 注释的替代选择,JAX-WS 处理程序 可以通过 application.properties 添加至您的客户端或服务器:

application.properties

# A web service endpoint with multiple Handler classes
quarkus.cxf.endpoint."/greeting-service".handlers=org.acme.MySOAPHandler,org.acme.AnotherSOAPHandler

# A web service client with a single Handler
quarkus.cxf.client."greeting-client".handlers=org.acme.MySOAPHandler

其中 MySOAPHandler 可能类似如下:

import jakarta.xml.ws.handler.soap.SOAPHandler;
import jakarta.xml.ws.handler.soap.SOAPMessageContext;

public class MySOAPHandler implements SOAPHandler<SOAPMessageContext> {

    public boolean handleMessage(SOAPMessageContext messageContext) {
        SOAPMessage msg = messageContext.getMessage();
        return true;
    }
    // other methods
}
类加载

SOAPHandler 类首先通过 CDI 加载。

如果没有 CDI Bean 可用,则会调用没有参数的构造器来实例化每个类。

5.1.10. 高级服务主题

有关实现 SOAP 服务端点的更多详细信息,请查看以下章节:

5.1.10.1. 服务端点和路径

让我们来说明如何在特定 URL 路径下公开服务端点。

5.1.10.1.1. 通过 application.properties设置端点路径

First SOAP Web 服务 章节中,我们解释了如何使用 application.properties 来公开服务:

application.properties

# The context path under which all services will be available
quarkus.cxf.path = /soap

# Publish "HelloService" under the context path /${quarkus.cxf.path}/hello
quarkus.cxf.endpoint."/hello".implementor = io.quarkiverse.cxf.it.server.HelloServiceImpl
quarkus.cxf.endpoint."/hello".features = org.apache.cxf.ext.logging.LoggingFeature

有了此设置,可以在 http://localhost:8080/soap/hello 下访问 io.quarkiverse.cxf.it.server.HelloServiceImpl

这是自 Quarkus CXF 非常开始工作的传统方法。

5.1.10.1.2. 使用 @CXFEndpoint 注释设置端点路径

自 Quarkus CXF 3.11.0 起,有新的方法在特定路径下公开端点 :@io.quarkiverse.cxf.annotation.CXFEndpoint 注解。该路径通过其非可选 属性值 进行设置,它相对于 quarkus.cxf.path,就像通过 application.properties 执行时一样。

让我们来看一个示例。

注意

本节中显示的代码片段示例来自 Quarkus CXF 的源树中的客户端和服务器 集成测试。您可能需要将其用作可运行的示例。

PathAnnotationHelloServiceImpl.java

package io.quarkiverse.cxf.it.annotation.cxfendpoint;

import jakarta.jws.WebService;

import io.quarkiverse.cxf.annotation.CXFEndpoint;
import io.quarkiverse.cxf.it.HelloService;

@CXFEndpoint("/path-annotation") 
1

@WebService(serviceName = "HelloService", targetNamespace = HelloService.NS)
public class PathAnnotationHelloServiceImpl implements HelloService {
    @Override
    public String hello(String person) {
        return "Hello " + person + " from PathAnnotationHelloServiceImpl!";
    }
}

1
如果 application.properties 中的 quarkus.cxf.path 的值为 /soap,则该服务可在 http://localhost:8080/soap/path-annotation 下访问。

MyServiceImpl 类型的 @CxfEndpoint ("/my-path") 注释等同于 application.properties 中的 quarkus.cxf.endpoint."/my-path".implementor = org.acme.MyServiceImpl 行。因此,仅使用其中之一就足够了。

/my-path 端点的 application.properties 中设置的其他选项,将只与 @CXFEndpoint ("/my-path") 合并。

@CXFEndpoint 注释也可用于生成者方法。这特别适用于测试客户端,因为返回的实现可以是模拟的。

下面是一个示例:

MockedEndpointTest.java

package io.quarkiverse.cxf.it.annotation.cxfendpoint;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import io.quarkiverse.cxf.annotation.CXFClient;
import io.quarkiverse.cxf.annotation.CXFEndpoint;
import io.quarkiverse.cxf.it.HelloService;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class MockedEndpointTest {

    @CXFEndpoint("/helloMock") 
1

    HelloService helloMockService() {
        final HelloService result = Mockito.mock(HelloService.class);
        Mockito.when(result.hello("Mock")).thenReturn("Hello Mock!");
        return result;
    }

    @CXFClient("helloMock") 
2

    HelloService helloMockClient;

    @Test
    void helloMock() {
        Assertions.assertThat(helloMockClient.hello("Mock")).isEqualTo("Hello Mock!"); 
3

    }

}

1
在这里,我们对返回 HelloService 接口的模拟的方法使用 @CXFEndpoint 注释。不需要 @jakarta.enterprise.inject.Produces 注解,因为 Quarkus CXF 声明 @CXFEndpoint 作为定义注解的 bean。
2
客户端在 application.properties 中配置以连接到 http://localhost:8080/soap/helloMock
3
断言可确保服务实施可以按预期工作。

5.1.10.2. JAX-WS 提供商

JAX-WS 提供程序 受到全面支持,可以按照如下所示实施。

根据以下示例 供应商 实现:

import jakarta.xml.transform.stream.StreamSource;
import jakarta.xml.ws.BindingType;
import jakarta.xml.ws.Provider;
import jakarta.xml.ws.Service;
import jakarta.xml.ws.ServiceMode;
import jakarta.xml.ws.WebServiceProvider;
import java.io.StringReader;

@WebServiceProvider
@ServiceMode(value = Service.Mode.PAYLOAD)
public class StreamSourcePayloadProvider implements Provider<StreamSource> {

    public StreamSourcePayloadProvider() {
    }

    public StreamSource invoke(StreamSource request) {
        String payload = StaxUtils.toString(request);

        // Do some interesting things ...

        StreamSource response = new StreamSource(new StringReader(payload));
        return response;
    }
}

application.properties 可以配置如下。

# A web service endpoint with the Provider implementation class
quarkus.cxf.endpoint."/stream-source".implementor=org.acme.StreamSourcePayloadProvider
类加载

供应商 类首先通过 CDI 加载。

如果没有 CDI Bean 可用,则会调用没有参数的构造器来实例化每个类。

5.1.10.3. REST 和 SOAP 端点

有时,使用 Quarkus CXF 扩展的同一项目中可能需要 REST 端点。REST 端点路径必须与 SOAP 端点路径不同(为了避免在两个协议之间请求转发冲突)。

例如,如果在 WSDL 中声明了 WeatherWebService 接口,您可以先创建 org.acme.cxf.WeatherWebServiceImpl 类,如下所示:

package org.acme.cxf;

import ...

@Slf4j
@WebService(endpointInterface = "org.acme.cxf.WeatherWebService")
public class WeatherWebServiceImpl implements WeatherWebService {

    @Inject
    BackEndWeatherService backEndWeatherService;

    private Map<String, DailyTemperature> dailyTempByZipCode = Collections.synchronizedMap(new LinkedHashMap<>());

    public WeatherWebServiceImpl() {
        this.dailyTempByZipCode.addAll(
                this.backEndWeatherService.getDailyForecast(Instant.now()));
    }

    @Override
    public DailyTemperature estimationTemperatures(String zipCode) {
        log.info("Daily estimation temperatures forecast called with '{}' zip code paramter", zipCode);
        return this.dailyTempByZipCode.get(zipCode);
    }
}

之后,您需要为 CXF Web 服务指定根上下文,如 配置文档 中所示,以根据其根上下文路径分割 REST (例如 RESTEasy)和 SOAP 路由。

CXF 的 SOAP 属性:

quarkus.cxf.path=/soap
quarkus.cxf.endpoint."/weather".implementor=org.acme.cxf.WeatherWebServiceImpl

现在,请想象以下 RESTEasy 端点:

package org.acme.reasteasy;

import ...

@Slf4j
@Path("/healthcheck")
public class HealthCheckResource {

    @Inject
    BackEndWeatherService backEndWeatherService;

    @GET
    public Response doHealthCheck() {
        if(this.backEndWeatherService.isAvailable()) {
            return Response.ok().build();
        } else {
            return Response.status(Response.Status.SERVICE_UNAVAILABLE);
        }
    }
}

您可以通过配置 REASTEasy 路径来分隔 REST 端点:

quarkus.resteasy.path=/rest

现在,您应该能够向在单个项目中部署的 REST 和 SOAP 端点发送请求,该端点位于:

5.1.10.4. 在反向代理后运行

SOAP 请求旨在向 Quarkus 上运行的服务进行路由,该代理可以生成额外标头(如 X-Forwarded-Host),以保留来自面向客户端的代理服务器的信息,并在涉及时更改或丢失。在这些情况下,可将 Quarkus 配置为自动更新协议、主机、端口和 URI 等信息,反映这些标头中的值。

提示

如需更多详细信息,请参阅 Quarkus HTTP 参考

对各种 X-Forwarded 标头的 quarkus CXF 支持可以在 Quarkus 配置中正常工作。

重要

激活此功能会使服务器暴露几个安全问题(例如,信息欺骗)。只有在反向代理后运行时,请考虑将其激活。

以下是相关的 Quarkus 属性及其对 Quarkus CXF 的影响:

  • quarkus.http.proxy.proxy-address-forwarding - 主开关来启用请求目的地部分的重写。

    • 如果启用,在整个 CXF 服务器堆栈中,请求字段的重写将有效。
    • 如果启用,则使用通过 X-Forwarded-ProtoX-Forwarded-Port 标头传递的值来设置协议部分,以及 jakarta.servlet.http.HttpServletRequest.getRequestURL () 返回的 URL 的端口部分。
    • 如果启用,通过 X-Forwarded-For 传递的值将由 jakarta.servlet.ServletRequest.getRemoteAddr () 返回。
  • quarkus.http.proxy.enable-forwarded-host - 启用 jakarta.servlet.http.HttpServletRequest.getRequestURL () 返回的 URL 部分的重写。实际主机名从通过 quarkus.http.proxy.forwarded-host-header 配置的标头获取(默认为 X-Forwarded-Host)。
  • quarkus.http.proxy.enable-forwarded-prefix - 启用 jakarta.servlet.http.HttpServletRequest.getRequestURL () 返回的 URL 的路径部分的重写,以及 jakarta.servlet.http.HttpServletRequest.getRequestURI () 返回的 URI 部分。实际路径前缀从通过 quarkus.http.proxy.forwarded-prefix-header 配置的标头获取(默认为 X-Forwarded-Prefix)。

以下是复制到 application.properties 的最常见片段:

quarkus.http.proxy.proxy-address-forwarding = true
quarkus.http.proxy.enable-forwarded-host = true
quarkus.http.proxy.enable-forwarded-prefix = true

这些设置的可观察影响之一是更改在 http://localhost:8080/services/my-service?wsdl 上提供的 WSDL 中的位置值。例如,如果请求包含以下标头

X-Forwarded-Proto: https
X-Forwarded-Host: api.example.com
X-Forwarded-Port: 443
X-Forwarded-Prefix: /my-prefix

然后 http://localhost:8080/services/my-service?wsdl 上提供的 WSDL 将包含 以下位置

...
<soap:address location="https://api.example.com:443/my-prefix/services/my-service"/>
...

5.1.11. 高级 SOAP 客户端主题

有关实现 SOAP 客户端的更多详细信息,请查看以下章节:

5.1.11.1. client-endpoint-url 默认值

如果省略 application.properties 中的 client-endpoint-url 属性,则 CXF Quarkus 扩展将假定该服务在 http://localhost:8080/{service-path} 中发布,其中 {service-path} 派生自

  • 配置属性 quarkus.cxf.path (如果指定); 和
  • SEI 的类名称(小写)

给定 quarkus.cxf.path = /wsCalculatorService 的默认有效 client-endpoint-urlhttp://localhost:8080/ws/org.jboss.eap.quickstarts.wscalculator.calculator.calculatorservice

如果没有指定 quarkus.cxf.pathclient-endpoint-url 将只是 http://localhost:8080/org.jboss.eap.quickstarts.wscalculator.calculator.calculatorservice

5.1.11.2. 配置多个客户端

在上例中,我们只配置了一个名为 myCalculator 的单个客户端。当然,您可以使用多个标识符配置指向不同 URL 和/或实施不同 SEI 的多个客户端:

application.properties

cxf.it.calculator.baseUri = http://localhost:8082
quarkus.cxf.client.myCalculator.wsdl = ${cxf.it.calculator.baseUri}/calculator-ws/CalculatorService?wsdl
quarkus.cxf.client.myCalculator.client-endpoint-url = ${cxf.it.calculator.baseUri}/calculator-ws/CalculatorService
quarkus.cxf.client.myCalculator.service-interface = org.jboss.eap.quickstarts.wscalculator.calculator.CalculatorService

# another client
quarkus.cxf.client.anotherCalculator.wsdl = https://acme.com/ws/WeatherService?wsdl
quarkus.cxf.client.anotherCalculator.client-endpoint-url = https://acme.com/ws/WeatherService
quarkus.cxf.client.anotherCalculator.service-interface = org.jboss.eap.quickstarts.wscalculator.calculator.CalculatorService

5.1.11.3. 通过 @CXFClient注入的客户端的 CDI 范围

quarkus CXF 在默认的 @Dependent 范围中生成通过 @io.quarkiverse.cxf.annotation.CXFClient 注入的所有客户端。因此,注入的实例的实际范围取决于注入客户端的 bean 的 CDI 范围。

因此,如果客户端注入 @ApplicationScoped bean,则客户端实例也会变为 @ApplicationScoped。如果客户端注入 @RequestScoped bean,则客户端实例也会变为 @RequestScoped

如果您需要在应用程序启动后 动态配置客户端,则此行为会变得方便。

5.1.11.4. 启动时编程客户端配置

要在应用程序启动时配置所有客户端,您可以实施 HTTPConduitConfigurer,并使用 StartupEvent observer 方法在 CXF 总线上设置它。

在以下示例中,我们配置 HTTPClientPolicy 的一些方面。可以利用同样的方法来自定义授权策略 ,Proxy AuthorizationPolicy ,甚至您的客户端的 TLSClientParameters

import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.event.Observes;
import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transport.http.HTTPConduitConfigurer;
...

void onStart(@Observes StartupEvent ev) {

     HTTPConduitConfigurer httpConduitConfigurer = new HTTPConduitConfigurer() {
         public void configure(String name, String address, HTTPConduit conduit) {
             conduit.getClient().setAllowChunking(false);
             conduit.getClient().setAutoRedirect(true);
         }
     };

     final Bus bus = BusFactory.getDefaultBus();
     bus.setExtension(httpConduitConfigurer, HTTPConduitConfigurer.class);
}

5.1.11.5. 动态客户端配置

有时,您需要在应用 启动后重新配置客户端,甚至在 每个请求之前重新配置客户端。例如,这可能是每个请求发送到不同的远程 URL 的情况。

CXF 提供了一个 API,用于设置远程端点的 URL。但是,在可能从其他线程访问的客户端实例上使用该 API 可能会导致竞争条件。

5.1.11.5.1. 防止对 CXF 客户端的并发访问

如果您的客户端用作外部请求的一部分,您可以将客户端注入到 @RequestScoped bean 中。然后,每个请求都由一个全新的客户端实例提供,您可以安全地进行配置。

例如,如果您的客户端从 REST-handler 方法调用,或从提供外部请求的 @WebMethod 调用时,此解决方案适用。

package io.quarkiverse.cxf.client.it;

import java.util.Map;

import jakarta.enterprise.context.RequestScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.xml.ws.BindingProvider;

import org.jboss.eap.quickstarts.wscalculator.calculator.CalculatorService;

import io.quarkiverse.cxf.annotation.CXFClient;

/*
 * The @RequestScoped annotation causes that the REST resource is instantiated
 * anew for every call of the add() method. Therefore also a new client instance
 * is injected into the calculator field for every request served by add().
 */
@RequestScoped
@Path("/cxf/dynamic-client")
public class DynamicClientConfigRestResource {

    @CXFClient("requestScopedVertxHttpClient")
    CalculatorService calculator;

    @GET
    @Path("/add")
    @Produces(MediaType.TEXT_PLAIN)
    public int add(@QueryParam("a") int a, @QueryParam("b") int b, @QueryParam("baseUri") String baseUri) {
        Map<String, Object> ctx = ((BindingProvider) calculator).getRequestContext();
        /* We are setting the remote URL safely, because the client is associated exclusively with the current request */
        ctx.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, baseUri + "/calculator-ws/CalculatorService");
        return calculator.add(a, b);
    }

}

5.1.11.6. 纯客户端应用程序

Quarkus batch (如定期调度)或命令行应用程序,在没有 HTTP 服务器的情况下可以这样做。使用以下属性以防止在启动时启动 HTTP 服务器:

quarkus.http.host-enabled = false

5.1.11.7. 防止资源泄漏

CXF 客户端代理实现 java.io.Closeable。因此,在客户端不再需要释放所有关联的系统资源 (如线程)后,调用(关闭) proxy)。close () 非常重要。

quarkus CXF 在被 CDI 容器处理后立即自动关闭通过 @io.quarkiverse.cxf.annotation.CXFClient 注入的客户端。

对于手动创建的客户端代理,您需要调用 (关闭) proxy).close ()

import java.net.URL;
import javax.xml.namespace.QName;
import jakarta.xml.ws.Service;
import java.io.Closeable;

final URL serviceUrl = new URL("http://localhost/myService?wsdl");
final QName qName = new QName("http://acme.org/myNamespace", "MyService");
final Service service = jakarta.xml.ws.Service.create(serviceUrl, qName);
final MyService proxy = service.getPort(MyService.class);

try {
    proxy.doSomething();
} finally {
    ((Closeable) proxy).close();
}

5.1.12. Camel Integration

Camel Quarkus 支持 CXF 版本 2.12.0。在 hood 下,实施基于 Quarkus CXF。因此,Camel Quarkus 也提供 Quarkus CXF 中的所有功能。

详情请参考 Camel Quarkus CXF SOAP 扩展文档。

5.1.13. 例子

代码库的 integration-tests 文件夹 提供了各种示例,演示了如何广泛使用此扩展。

Red Hat logoGithubredditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。 了解我们当前的更新.

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

Theme

© 2026 Red Hat
返回顶部