Quarkus Extension for Spring DI API

虽然鼓励用户使用 CDI 注释进行注入,但是 Quarkus 以 spring-di 扩展的形式为 Spring 依赖注入提供了一个兼容性层。 本指南解释 Quarkus 应用程序如何利用 Spring 框架中包含的知名依赖注入注释。

Prerequisites

如要完成本指南,您需要:

  • Roughly 15 minutes

  • An IDE

  • 安装了 JDK 17+,已正确配置 JAVA_HOME

  • Apache Maven ${proposed-maven-version}

  • 如果你想使用 Quarkus CLI, 则可以选择使用

  • 如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately

Solution

我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。

克隆 Git 存储库: git clone $${quickstarts-base-url}.git,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。

该解决方案位于 spring-di-quickstart directory 中。

Creating the Maven project

首先,我们需要一个新项目。使用以下命令创建一个新项目:

CLI
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
    --no-code
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 --gradle--gradle-kotlin-dsl 选项。 有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。

Maven
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
    -DprojectGroupId={create-app-group-id} \
    -DprojectArtifactId={create-app-artifact-id} \
    -DnoCode
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

适用于 Windows 用户:

  • 如果使用 cmd,(不要使用反斜杠 \ ,并将所有内容放在同一行上)

  • 如果使用 Powershell,将 -D 参数用双引号引起来,例如 "-DprojectArtifactId={create-app-artifact-id}"

此命令生成一个导入 spring-di 扩展的项目。

如果你已配置好 Quarkus 项目,那么你可以通过在项目基本目录中运行以下命令,向你的项目添加 spring-di 扩展:

CLI
quarkus extension add {add-extension-extensions}
Maven
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
Gradle
./gradlew addExtension --extensions='{add-extension-extensions}'

这会将以下内容添加到构建文件中:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-spring-di</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-spring-di")

Add beans using Spring annotations

让我们使用各种 Spring 注解创建一些 bean。

首先,我们将创建一个 StringFunction 接口,其中一些 bean 会实现该接口,稍后会将该接口注入另一个 bean 中。创建 src/main/java/org/acme/spring/di/StringFunction.java 文件,并设置以下内容:

package org.acme.spring.di;

import java.util.function.Function;

public interface StringFunction extends Function<String, String> {

}

有了接口后,我们将添加一个 AppConfiguration 类,该类将使用 Spring 的 Java 配置样式定义一个 bean。它将用于创建一个 StringFunction bean,该 bean 将把作为参数传递的文本大写。创建 src/main/java/org/acme/spring/di/AppConfiguration.java 文件,内容如下:

package org.acme.spring.di;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfiguration {

    @Bean(name = "capitalizeFunction")
    public StringFunction capitalizer() {
        return String::toUpperCase;
    }
}

作为一个 Spring 开发人员,你可能会倾向于添加 @ComponentScan 注解,以定义扫描其他 bean 的特定包。请注意,@ComponentScan 是完全不必要的,因为 Quarkus 只在 annotated 模式下使用 bean discovery,没有可见性边界。此外,请注意,Quarkus 中的 bean 发现发生在构建时。同样,Quarkus 不支持 Spring @Import 注解。

现在我们定义另一个 bean,它将使用 Spring 的构造型注解样式实现 StringFunction。这个 bean 实际上是一个无操作 bean,它仅仅按原样返回输入。创建一个 src/main/java/org/acme/spring/di/NoOpSingleStringFunction.java 文件,并设置以下内容:

package org.acme.spring.di;

import org.springframework.stereotype.Component;

@Component("noopFunction")
public class NoOpSingleStringFunction implements StringFunction {

    @Override
    public String apply(String s) {
        return s;
    }
}

Quarkus 还提供对使用 Spring 的 @Value 注解注入配置值的 supports。要查看实际情况,首先使用以下内容编辑 src/main/resources/application.properties

# Your configuration properties
greeting.message = hello

接下来,使用以下内容在 src/main/java/org/acme/spring/di/MessageProducer.java 中创建一个新的 Spring bean:

package org.acme.spring.di;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class MessageProducer {

    @Value("${greeting.message}")
    String message;

    public String getPrefix() {
        return message;
    }
}

我们将创建的最后一个 bean 将所有前面的 bean 联系在一起。创建一个 src/main/java/org/acme/spring/di/GreeterBean.java 文件,并复制以下内容:

package org.acme.spring.di;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class GreeterBean {

    private final MessageProducer messageProducer;

    @Autowired
    @Qualifier("noopFunction")
    StringFunction noopStringFunction;

    @Autowired
    @Qualifier("capitalizeFunction")
    StringFunction capitalizerStringFunction;

    @Value("${greeting.suffix:!}")
    String suffix;

    public GreeterBean(MessageProducer messageProducer) {
        this.messageProducer = messageProducer;
    }

    public String greet(String name) {
        final String initialValue = messageProducer.getPrefix() + " " + name + suffix;
        return noopStringFunction.andThen(capitalizerStringFunction).apply(initialValue);
    }
}

在上面的代码中,我们可以看到正在使用字段注入和构造函数注入(请注意,构造函数注入不需要 @Autowired 注解,因为只有一个构造函数)。此外,suffix 上的 @Value 注解也定义了一个默认值,在这种情况下,由于我们尚未在 application.properties 中定义 greeting.suffix,因此将使用该默认值。

Create the Jakarta REST resource

使用以下内容创建一个 src/main/java/org/acme/spring/di/GreeterResource.java 文件:

package org.acme.spring.di;

import org.springframework.beans.factory.annotation.Autowired;

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

@Path("/greeting")
public class GreeterResource {

    @Autowired
    GreeterBean greeterBean;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return greeterBean.greet("world");
    }
}

Update the test

我们还需要更新功能测试,以反映对端点的更改。编辑 src/test/java/org/acme/spring/di/GreetingResourceTest.java 文件,并将 testHelloEndpoint 方法的内容更改为:

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
public class GreetingResourceTest {

    @Test
    public void testHelloEndpoint() {
        given()
            .when().get("/greeting")
            .then()
                .statusCode(200)
                .body(is("HELLO WORLD!"));
    }

}

Package and run the application

使用以下内容运行应用程序:

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

用浏览器打开 [role="bare"][role="bare"]http://localhost:8080/greeting.

结果应该是: HELLO WORLD!

Run the application as a native

你当然可以使用类似于 this 指南中的说明来创建一个本机映像。

Important Technical Note

请注意,Quarkus 中的 Spring 支持不会启动 Spring 应用程序上下文,也不会运行任何 Spring 基础架构类。Spring 类和注释仅用于读取元数据和/或用作用户代码方法返回类型或参数类型。这对最终用户来说意味着添加任意的 Spring 库没有任何效果。此外,不会执行 Spring 基础架构类(例如 org.springframework.beans.factory.config.BeanPostProcessororg.springframework.context.ApplicationContext)。特别是对于依赖项注入,Quarkus 使用基于 Jakarta Contexts and Dependency Injection 4.1 规范的依赖项注入机制(称为 ArC)。如果你想了解更多信息,我们建议你阅读 Quarkus introduction to CDICDI reference guide。Quarkus 不支持各种 Spring Boot 测试功能。对于测试目的,请查看 Quarkus testing guide

一些已知的限制:

  • 如果出现歧义,Spring 将使用 bean 名称与注入点字段名称或参数名称进行后备匹配。不支持此操作,因此需要使用 @Named 注解来明确解决任何歧义。

  • 仅将某种特定类型的 bean 全部注入到 List&lt;Bean&gt; 有限。不支持 Set&lt;Bean&gt;Map&lt;String, Bean&gt; 的注入。

  • 不支持使用 @Autowired(required=false) 的可选注入。使用 javax.enterprise.inject.Instance,然后测试 Instance.isResolvable()

  • 忽略 @Conditional,因为在构建时会解决依赖项注入。一种替代方案是使用 conditional build profiles

Conversion Table

下表显示了 Spring DI 注解可以如何转换为 CDI 和/或 MicroProfile 注解。

Spring CDI / MicroProfile Comments

@Autowired

@Inject

如果类型为 java.util.List,则会添加 `io.quarkus.arc.All`限定符。

@Qualifier

@Named

@Value

@ConfigProperty

@ConfigProperty 不支持表达式语言,但可以处理典型用例,使其更加容易处理

@Component

@Singleton

默认情况下,Spring 陈规定型注解是单例 bean

@Service

@Singleton

默认情况下,Spring 陈规定型注解是单例 bean

@Repository

@Singleton

默认情况下,Spring 陈规定型注解是单例 bean

@Configuration

@ApplicationScoped

在 CDI 中,生产程序 bean 不仅限于应用程序作用域,还可以是 @Singleton 或 @Dependent

@Bean

@Produces

@Scope

没有到 CDI 注解的一对一映射。根据 @Scope 的值,可以使用 @Singleton、@ApplicationScoped、@SessionScoped、@RequestScoped、@Dependent

@ComponentScan

没有到 CDI 注解的一对一映射。它不会在 Quarkus 中使用,因为 Quarkus 在构建时执行所有类路径扫描。

@Import

没有到 CDI 注解的一对一映射。

Spring DI Configuration Reference

Unresolved include directive in modules/ROOT/pages/spring-di.adoc - include::../../../target/quarkus-generated-doc/config/quarkus-spring-di.adoc[]