Quarkus实践指南:构建新一代的Kubernetes原生Java微服务
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.2 Quarkus 开发基础

本节主要讲解Quarkus 的一些常见用法,包括其核心的CDI方式等。

2.2.1 Quarkus 的CDI应用

1.Quarkus 的CDI简介

CDI(Contexts and Dependency Injection for Java 2.0)即Java的容器、依赖和注入规范。关于规范的详细内容,可参阅与JSR 365规范相关的网址。

Quarkus 的CDI方案是基于Java上下文的依赖注入2.0标准,但该方案只实现了CDI的一部分功能,是一个不完全符合TCK的CDI实现。其实Quarkus CDI与Spring的依赖注入很相似,在 CDI 中,Bean 是定义应用程序状态和/或逻辑的上下文对象的源,如果 Bean 容器可以根据 CDI规范中定义的生命周期上下文模型来管理 Bean实例的生命周期,那么这些 Java EE组件就是Bean。

2.Bean发现

Bean 是一个容器管理对象,支持一组基本服务,如依赖项的注入、生命周期回调和拦截器。Quarkus 简化了 Bean 发现。Bean 是根据以下内容合成的:①application 类;②包含beans.xml 的依赖项;③包含 Jandex 索引的依赖项 META-INF/jandex.idx;④application.properties文件中定义的quarkus.index-dependency所用到的依赖;⑤Quarkus 集成代码。

一个简单的Bean示例如下:

@ApplicationScoped 是一个范围注解。该注解告诉容器与 Bean 实例关联的上下文。在这个特定的例子中,为应用程序创建一个Bean实例,并可被所有其他注入转换器的Bean使用。

@Inject 是一个现场注入点。该注解告诉容器转换器要依赖字典 Bean。如果没有匹配的Bean,则构建失败。

@Counted是一个拦截器绑定注解。在本例中,该注解来自MicroProfile度量规范。

Quarkus 不会发现没有注解Bean Defining Annotation的Bean类,这是由CDI定义的。但是,包含producer方法、字段和observer方法的类,即使未注解也会被发现,这与CDI中的定义稍微有所不同。实际上,注解了@Dependent的类表示可以被发现。另外,Quarkus 扩展组件可以声明其他发现规则。例如,即使声明类没有注解@Scheduled业务方法也会被注册。

3.原生可执行程序与私有成员

Quarkus 使用GraalVM构建原生可执行程序。GraalVM的限制之一就是反射的使用,其支持反射操作,但必须为所有相关成员进行显式注册以实现反射。这些注册会带来更大的原生可执行程序。

如果 Quarkus DI 需要访问私有成员,则必须使用反射。因此,Quarkus 鼓励用户不要在Bean 中使用私有成员。这涉及注入字段、构造函数和初始化程序、观察者方法、生产者方法和字段、处理程序和拦截器的方法。

如何避免使用私有成员?可以使用package-private修饰符:

以及package-private注入字段、package-private监听方法,或通过构造函数注入:

在package-private构造函数注入这种情况下,@Inject是可选的。

4.Quarkus 的依赖解析原理

在Quarkus 的CDI中,匹配Bean到注入点的过程点是类型安全的。首先,每个Bean都声明一组 Bean类型。然后,一个 Bean被分配给一个注入点,如果这个 Bean的类型和需要的类型相匹配,那就需要限定词(Qualifier)。

依赖解析中有一个规则,一个 Bean 只能被分配给一个注入点,否则将编译失败。如果有一个Bean没被分配,应用系统的编译也会失败,会抛出UnsatisfiedResolutionException异常。如果一个注入点被分配给多个 Bean,会抛出 AmbiguousResolutionException异常。这个特性导致CDI容器不能找到任何注入点的明确依赖,应用系统也会快速报错。

同时,可以使用 setter 和构造方法注入,但是在 CDI(Contexts and Dependency Injection for Java EE)中 setter 被更有效的初始化方法替代,初始化方法可以接收多个参数,而不必遵循JavaBean的命名约定。以下为一个示例:

这是一个构造函数注入。实际上,这段代码在常规的 CDI 实现中不起作用,在这种实现中,具有普通作用域的Bean必须始终声明一个无参构造函数,并且该Bean的构造函数必须用@Inject进行注解。然而,在 Quarkus 中,如果检测到没有参数的构造函数,那么就会直接在字节码中“添加”构造函数。如果只存在一个构造函数,也不是必须添加@Inject注解的。

5.关于Qualifier的含义

@Qualifier注解的限定符用于帮助容器区分实现了相同类型的 Bean。如果一个 Bean具有所需的所有限定符,那么只能被分配给一个注入点。但是,如果注入点未声明限定符,那么就使用@Default限定符。

限定符类型是被定义为@Retention (RUNTIME)的 Java 注解,并使用@javax.inject.Qualifier元注解进行注解。例如:

被限定的 Bean的声明是通过注解 Bean类、生产方法或者限定类型的类的属性实现的,例如:

@Superior是一个限定符注解。

解释:该 Bean 可被分配给@Inject@Superior Translator 和@Inject@Superior SuperiorTranslator,但不能被分配给@Inject Translator,原因是在类型安全解析期间,@Inject转换器会自动转换为@Inject@Default Translator,而且由于 SuperiorTranslator 不声明@Default,所以只能分配原始的Translator Bean。

6.Bean的范围

Bean 的范围(Scope)决定了其实例化的生命周期,即何时何地被实例化创建和销毁,每一个 Bean 都有一个准确的范围,内置的所有范围都能使用,除了 javax.enterprise.context.ConversationScoped。内置范围注解介绍如下。

@javax.enterprise.context.ApplicationScoped是该应用程序的单个Bean实例,并在所有注入点之间共享。实例是延迟创建的,即在客户端代理上调用方法后。

@javax.inject.Singleton 就像@ApplicationScoped 一样,只是不使用任何客户端代理。在注入注解为@Singleton Bean的注入点时创建该实例化对象。

@javax.enterprise.context.RequestScoped Bean实例与当前请求(通常是 HTTP请求)相关联。

@javax.enterprise.context.Dependent 是一个伪作用域,其含义是由此形成的实例不能被共享,并且每个注入点都会生成一个新的依赖 Bean 实例。Dependent Bean 的生命周期与注入它的Bean绑定,同时将与注入它的Bean一起创建和销毁。

@javax.enterprise.context.SessionScoped 范围由 javax.servlet.http.HttpSession 对象支持,仅在使用quarkus-undertow扩展名时才可用。

提示:Quarkus 扩展组件可以提供其自定义的范围。例如,quarkus-narayana-jta 就提供了javax.transaction.TransactionScoped的自定义范围。

7.客户端代理概念

客户端代理(Client Proxy)原则上是一个将所有方法调用委托给目标 Bean 实例的对象,也就是一个由 Bean 容器构造的对象。客户端代理实现 io.quarkus.arc.ClientProxy 并继承 Bean类。客户端代理仅限于方法调用的委托,故不能读取或写入普通作用域 Bean 字段,否则将使用非上下文环境或过时的数据。示例如下:

Translator_ClientProxy实例总是被注入,而不是直接引用Translator Bean的上下文实例。

客户端代理允许的操作:①延迟实例化,在代理上调用方法后才创建实例化对象;②可以将作用域“更窄”的 Bean 注入作用域“更宽”的 Bean (例如,可以将@RequestScoped Bean 注入@ApplicationScoped Bean);③依赖关系图中的循环依赖关系,具有循环依赖关系则通常表明这不是一个好的设计,应考虑进行重新设计,但有时循环依赖关系很难避免;④可以手动销毁Bean,直接注入的引用将导致过时的Bean实例化对象。

8.Bean的类型

首先,Bean 分为 Class Bean、Producer 方法、Producer 字段、Synthetics (复合类型) Beans等。Producer方法与字段主要用来对Bean实例化对象加以控制,此外,在集成第三方库时,既不能控制源码,也不能添加其他注解,这时 Producer 方法与字段就非常有用了。Producers示例如下:

提示:可以声明限定符,将依赖项注入Producer方法参数中。

9.Bean的生命周期回调

Bean类可以声明生命周期@PostConstruct和@PreDestroy回调。生命周期示例如下:

提示:最好在回调函数中保持逻辑“无副作用”,即应该避免在回调函数中调用其他Bean。

10.拦截器的定义

拦截器用于将跨领域关注点与业务逻辑分开。有一个单独的规范Java Interceptors,该规范定义了基本的编程模型和语义。示例如下:

提示:拦截器实例化对象是拦截的Bean实例的相关对象,即为每个拦截到的Bean都创建一个新的拦截器实例化对象。

11.事件与观察者(Event and Observer)

Bean 可以实现生产事件和消费事件在完全解耦的方式下交互,任何 Java 对象都可以充当事件的有效负载。可选的限定符充当主题选择器。示例如下:

Quarkus 中也使用依赖注入和面向切面的基本方法和技巧。

12.基于Quarkus 框架的CDI程序的实现

下面是一个基于Quarkus 框架的CDI程序的简单实现,主要包括两个类:一个是资源类,一个是服务类。服务类会注入资源类,通过调用资源类来实现相关功能。

(1)导入工程项目

导入Maven工程项目,可以从GitHub上克隆预先准备好的示例代码:

该程序位于“011-quarkus-hello-cdi”目录中,是一个Maven工程项目。

(2)程序说明

该程序由HelloResource类和HelloService类组成。

其中一个是资源类,打开com.iiit.quarkus.sample.hello.HelloResource类文件。该Bean负责暴露/hello服务,其代码如下:

程序说明:

① HelloResource类有两个方法。当通过 HTTP访问时,答复是 hello world;当带着参数访问时,答复是hello加上参数。

②@Inject是一个现场注入点。它告诉容器本Bean依赖于HelloService Bean。如果没有匹配的Bean,则构建失败。

另一个Bean是服务类,打开com.iiit.quarkus.sample.hello.HelloService类文件,其代码如下:

程序说明:

@ApplicationScoped 是一个范围注解,它告诉容器与 Bean 实例关联的上下文。在这个特定的例子中,为应用程序创建一个Bean实例,并由所有其他注入的Bean一起使用。

程序实现过程为,当外部调用/hello 时,转到 HelloResource 的调用方法 getHello,而HelloResource的getHello方法最终调用的是HelloService的getHello方法。

(3)在开发模式下启动应用

在当前目录下,打开命令行窗口并执行命令mvnw compile quarkus:dev。

应用启动完毕后,可在任意位置打开一个命令行窗口来执行命令 curl http://localhost:8080/hello,以此验证应用是否正常运行。也可以通过浏览器 URL(http://localhost:8080)访问服务。结果输出都是hello world。

2.2.2 Quarkus 命令模式

1.Quarkus 命令模式简介

(1)Quarkus 启动命令模式的方式

Quarkus 有两种不同的方法来实现运行并退出应用程序。第 1 种方法是实现Quarkus Application,并让 Quarkus 自动运行此方法。第 2种方法是实现 Quarkus Application和Java的main方法,并使用Java的main方法启动Quarkus。

Quarkus Application 实例被称为应用程序主实例,具有 Java main 方法的类被称为 Java main。可以访问Quarkus API的最简单的命令模式应用程序如下所示:

@Quarkus Main注解告诉Quarkus 这是主入口点。

一旦Quarkus 启动,就会调用run方法,而应用程序在完成时停止。

(2)Quarkus 启动命令的main方法

如果我们想使用Java的main方法来运行应用程序main,其代码如下:

这实际上与直接运行 HelloWorldMain应用程序中的 main方法相同,但是该方法的优点是可以从IDE运行,这样便于调试和监控。

如果实现Quarkus Application并具有JavaMain类,则Java的main方法将运行。建议Java的 main 方法只执行很少的逻辑,只需启动应用程序的 main 方法。在开发模式下,Java 的main方法在主应用程序不同的类加载器中运行,因此其行为可能不像预期的那样。

Quarkus Main 也支持多种主要方法。一个应用程序中可以有多个主方法,并在构建时在它们之间进行选择。@Quarkus Main 注解采用可选的 name 参数,在生成 quarkus.package.main-class时配置选项来定义要选择的 name参数。如果不想使用注解,也可以使用该 name参数来指定主类的完全限定符。

默认情况下,将使用没有名称的@Quarkus Main (即空字符串),如果它不存在或quarkus.package.main-class未指定,则Quarkus 将自动生成一个只运行应用程序的主类。

@Quarkus Main 的名称必须唯一(包括空字符串的默认名称)。如果应用程序中有多个@Quarkus Main注解,而且名称不唯一,则程序运行将失败,编译通不过。

(3)命令模式程序的基本生命周期

运行命令模式程序时,基本生命周期环节包括:① 启动 Quarkus;② 运行Quarkus Application主方法;③在main方法返回后关闭Quarkus 并退出JVM。

应用程序总是由主线程返回,如果希望在启动时运行一些逻辑,然后像普通应用程序一样运行(即不退出),那么需要在主线程上调用 Quarkus.waitForExit(非命令模式应用程序本质上只运行了一个只调用 waitForExit 的应用程序)。如果希望关闭正在运行的应用程序,而不在主线程中,那么应该调用Quarkus.asyncExit,以解锁主线程并启用关闭进程。

2.Quarkus 命令模式程序的实现

下面编写一个 Quarkus 命令模式程序的简单实现。在上面的程序中再增加一个类,即命令的启动类。

(1)导入工程项目

导入Maven工程项目,可以从GitHub上克隆预先准备好的示例代码:

该程序位于“012-quarkus-hello-command-mode”目录中,是一个Maven工程项目。

(2)程序说明

在该程序中,HelloMain 类是核心。com.iiit.quarkus.sample.hello.HelloMain 类负责启动程序,其代码如下:

程序说明:

①@Quarkus Main注解告诉 Quarkus 使用当前类作为主方法,除非它在配置中被重写。这个主类将引导 Quarkus 并运行它,直到停止。这与自动生成的主类没有什么不同,但是优点是开发者可以直接从IDE启动它,而不需要运行Maven或Gradle命令。

② 如果希望在启动时实际执行业务逻辑(或者编写完成任务后会退出的应用程序),需要给run方法提供一个io.quarkus.runtime.Quarkus Application类。在Quarkus 启动后,将调用应用程序的run方法。当此方法返回时,Quarkus 应用程序将退出。

③ 如果希望在启动时执行逻辑,你应该调用 Quarkus.waitForExit 方法,该方法将一直等待请求关闭(来自外部信号,如按Ctrl+C组合键时或线程已调用Quarkus.asyncExit方法时)。(3)在命令模式下启动程序

可以直接在IDE工具上运行程序。如果IDE工具是Eclipse,选择一级菜单Run中的Run命令即可启动程序,其界面如图2-7所示。

图2-7 Eclipse启动命令行界面

如果 IDE工具是 IntelliJ IDEA,选择一级菜单 Run中的 Run“HelloMain”命令即可启动程序,其界面如图2-8所示。

图2-8 IntelliJ IDEA启动命令行界面

由于输入了Quarkus.waitForExit(),因此程序保持原有状态,没有退出。

在程序启动完毕后,可在其他任何位置打开一个新命令行窗口并执行命令 curl http://localhost:8080来验证程序是否正常运行,也可以通过浏览器URL(http://localhost:8080)来访问服务。

2.2.3 Quarkus 应用程序生命周期

1.Quarkus 应用程序生命周期简介

Quarkus 应用程序具有生命周期,包括启动、运行、终止等过程。本节主要讲述 Quarkus 应用程序在启动时执行自定义操作,并在应用程序停止时清理所有内容。

Quarkus 应用程序生命周期涉及的事件包括使用main方法编写Quarkus 应用程序、编写运行任务后退出的命令模式程序,以及应用程序启动时或应用程序停止时的通知。

2.Quarkus 应用程序生命周期程序的实现

下面编写一个 Quarkus 应用程序生命周期程序的简单实现。在上面的命令模式程序上增加两个类。

导入Maven工程项目,可以从GitHub上克隆预先准备好的示例代码:

该程序位于“013-quarkus-hello-lifecycle”目录中,是一个Maven工程项目。

在该程序中打开com.iiit.quarkus.sample.hello.AppLifecycleBean类文件,其代码如下:

程序注入了一个 AppRuntimeStatusBean 对象,这样就能调用 AppRuntimeStatusBean 对象的方法。当启动 StartupEvent事件时,调用 AppRuntimeStatusBean对象的 startupStatus方法;当启动ShutdownEvent事件时,调用AppRuntimeStatusBean对象的terminationStatus方法。

打开com.iiit.quarkus.sample.hello.AppRuntimeStatusBean类文件,其代码如下:

我们在命令模式下启动程序,可以直接在 IDE 工具上运行程序,选择一级菜单 Run 中的Run命令即可启动程序。运行程序后的界面如图2-9所示。

图2-9 运行程序后的界面

在程序启动后,接着运行,直至退出,其日志如下所示:

通过日志可以看到,Quarkus Main Thread 首先启动的是 StartupEvent 事件,之后才开启JVM并启动监听端口,然后是处理配置文件,接着开始安装Quarkus 的外部扩展组件,并进入运行模式,监听外部信息。最后结束流程。

虚拟机模式和原生模式稍微有一点区别,也就是在虚拟机模式下,StartupEvent 事件总是在(ApplicationScoped.class)的@Initialized 之后被触发,而关闭事件在@destroy 之前被触发(ApplicationScoped.class)。但是,在原生模式下,可执行程序@Initialized(ApplicationScoped.class)在原生模式的构建过程中被触发,而StartupEvent事件在生成原生模式镜像时被触发。

在 CDI 应用程序中,带有限定符@Initialized 的事件(ApplicationScoped.class)在初始化应用程序上下文时被触发。

2.2.4 Quarkus 配置文件

1.Quarkus 配置文件简介

(1)Quarkus 配置属性的文件和程序访问

默认情况下,Quarkus会读取application.properties配置文件。Quarkus遵循MicroProfile配置规范在应用程序中注入配置,注入使用@ConfigProperty 注解。当以编程方式访问配置文件application.properties时,可以通过访问配置方法org.eclipse.microprofile.config.ConfigProvider.getConfig来实现。

Quarkus 常用配置信息只在创建应用程序时才有效,在应用程序运行时有可能会被覆盖。

(2)Quarkus 配置属性列表

Quarkus 的配置属性非常多,可参阅官网上的说明。该网站中列出了大部分 Quarkus 配置属性,Quarkus 第三方扩展组件基本上都有自己的配置属性,所以 Quarkus 的配置参数也基本上是按照 Quarkus 扩展组件来分类的。这些类别包括但不限于 AWS Lambda、Agroal Database connection pool、Amazon DynamoDB Client、Amazon IAM、Amazon KMS、Amazon S3、Amazon SES、Amazon SNS、Amazon SQS、Apache Kafka、Apache Tika、ArC、Artemis Core、Cache、Consul Config、Container Image、Datasource configuration、Eclipse Vert.x、Elasticsearch REST Client、Elytron Security、Flyway、Funqy、Google Cloud Functions、Hibernate、Infinispan Client、Jaeger、Keycloak Authorization、Kubernetes、Liquibase、Logging、Mailer、Micrometer Metrics、MongoDB Client、Narayana JTA、Neo4j Client、OpenID Connect、Picocli、Quarkus Core、Console Logging、Quarkus Extension for Spring Cloud Config Client、Quartz、Qute Templating、RESTEasy JAX-RS、Reactive DB2 Client、Redis Client、Scheduler、SmallRye、Swagger UI、Undertow、Vault、gRPC等。

基本上每个 Quarkus 扩展组件都有其对应的配置信息,这些配置信息都统一在 application.properties文件中进行定义。

(3)Quarkus 支持多配置文件

Quarkus 允许同一个文件中存在多个配置,并通过配置文件名在它们之间进行选择。语法是%{profile}.config.key=value.,示例如下:

其含义是:Quarkus HTTP 端口为 9090,但当 dev 配置文件处于活动状态时,Quarkus HTTP端口为8181。

尽管可以使用任意多个配置文件,可是在默认情况下,Quarkus 只有如下3个配置文件。

■ 开发阶段:在开发模式下激活(即quarkus:dev)。

■ 测试阶段:在运行测试时激活(即quarkus:test)。

■ prod阶段:不在开发或测试模式下运行时的默认配置文件。

有两种方法可以设置自定义配置文件,即通过 quarkus.profile 文件系统属性或QUARKUS_PROFILE 环境变量。如果两者都已设置,则系统属性优先。不需要在任何地方定义这些配置文件的名称,只需使用配置文件名称创建一个配置属性,然后将当前配置文件设置为该名称。例如,如果想要一个具有不同 HTTP 端口的 staging profile 文件,可以将以下内容添加到application.properties文件中:

2.Quarkus 配置文件程序的实现

下面编写一个Quarkus 配置文件程序的简单实现。

(1)导入工程项目

导入Maven工程项目,可以从GitHub上克隆预先准备好的示例代码:

该程序位于“014-quarkus-hello-config”目录中,是一个Maven工程项目。

(2)程序说明

在该程序目录下,有一个 application.properties 配置文件,还有一个 application.properties配置文件标准样例(该文件可通过mvnw quarkus:generate-config来生成)。

打开application.properties配置文件:

关于 HTTP 端口,在正常环境条件下,程序会监听 8080 端口。在开发环境下(即配置文件的%dev.quarkus.http.port=8081),程序会监听 8081 端口。在测试环境下(即配置文件的%test.quarkus.http.port=8082),程序会监听8082端口。

除去配置信息的内容,后续有很多已经注解掉的配置信息,是通过下面的命令来实现的:

开发者可根据具体工程的配置需求,对配置信息进行增加、修改和删除。

打开com.iiit.quarkus.sample.hello.HelloResource类文件,其代码如下:

程序说明:HelloResource类是一个资源类,通过getConfigProvider方法,暴露/config外部服务。

在HelloResource类中注入一个HelloService对象,其代码如下:

程序说明:

①对于message和helloName两个属性,分别使用@ConfigProperty注解注入。

②对于 getConfigProvider 方法,通过访问配置方法 org.eclipse.microprofile.config.ConfigProvider.getConfig来实现。

(3)在命令模式下启动程序

可以直接在 IDE工具上运行 HelloMain程序,即选择一级菜单 Run下的 run命令启动程序。

在程序启动后,可以分别执行下列命令,并观察获取到的不同结果:

注意:这里的监听端口是8081,因为程序是在开发模式下启动的。

3.Quarkus 组件常用配置信息及其说明

Quarkus 组件采用了统一配置方式,即所有扩展组件的配置信息都放在统一的application.properties文件中,这样的配置属性有几千个,表 2-1中列出的是 Quarkus 常用配置信息及其简介。

表2-1 Quarkus 常用配置信息及其简介

2.2.5 Quarkus 日志配置

1.Quarkus 日志配置简介

(1)Quarkus 支持的日志组件

Quarkus 支持的日志组件有 JDK java.util.logging、JBoss Logging、SLF4J、Apache Commons Logging等。其内部默认使用 JBoss日志记录,可供开发者在应用程序中直接使用,无须为日志添加其他依赖项。如果开发者使用JBoss日志记录,但是其中一个Java库使用了不同的日志API,则需要配置日志适配器。

(2)Quarkus 日志级别

以下是Quarkus 使用的日志级别。

■ OFF(关闭日志):关闭日志记录的特殊级别。

■ FATAL(致命日志):严重的服务故障/完全无法处理任何类型的请求。

■ ERROR(错误日志):请求中的严重中断或无法为请求提供服务。

■ WARN(警告日志):不需要立即纠正的非关键服务错误或问题。

■ INFO(信息日志):服务生命周期事件或重要的相关极低频信息。服务生命周期事件或重要性相当低的信息。

■ DEBUG(调试日志):传递有关生命周期或非请求绑定事件的额外信息的消息,这些信息可能有助于调试。

■ TRACE(跟踪日志):传递额外的每个请求调试信息的消息,这些消息的出现频率可能非常高。

■ ALL(所有日志):所有消息的特殊级别,包括自定义级别。

此外,可以为运行的应用程序和库配置以下级别的java.util.logging文件。

■ SEVERE(严重日志):与错误日志相同。

■ WARNING(警示日志):与警告日志相同。

■ CONFIG(配置日志):服务配置信息。

■ FINE(正常日志):与调试日志相同。

■ FINER(复杂日志):与跟踪(TRACE)日志相同。

■ FINEST(精细日志):该日志比跟踪日志含更多的调试信息,可能出现的频率更高。

(3)Quarkus 日志运行时配置

日志记录是按类别配置的。每个类别都可以独立配置,应用在某一个类别的配置也将应用于该类别的所有子类别,除非定义了更具体的子类别配置。对于每个类别,都应用console/file/syslog 配置的相同设置,也可以通过将一个或多个命名处理程序附加到类别来重写这些处理程序。

根记录器类别是单独处理的,并通过相关属性来进行配置。如果给定记录器类别不存在级别配置,则检查封闭(父)类别。如果没有配置包含相关类别的类别,则使用根记录器配置。(4)Quarkus 日志格式

默认情况下,Quarkus 使用日志格式化程序来生成可读的文本日志。

可以通过专用属性为每个日志处理程序配置格式,例如对于控制台处理程序,属性为quarkus.log.console.format。

可以更改控制台日志的输出格式,由外部环境服务捕获 Quarkus 应用程序日志输出功能将非常有用,例如,可以处理和存储日志信息以供以后分析。

(5)日志处理程序

日志处理程序是一个日志组件,负责向接收者发送日志事件。Quarkus 有 3 种不同的日志处理程序:控制台、文件和系统日志。

■ 控制台日志处理程序:默认情况下,将启用控制台日志处理程序。日志处理程序将所有日志事件输出到应用程序的控制台。

■ 文件日志处理程序:默认情况下,文件日志处理程序处于禁用状态。日志处理程序将所有日志事件输出到应用程序主机上的一个文件中。它支持日志文件旋转。

■ 系统日志处理程序:Syslog是一种使用 RFC5424定义的协议,是在类 UNIX系统上发送日志消息的协议。Syslog 处理程序将所有日志事件发送到 Syslog 服务器。默认情况下,该功能处于禁用状态。

2.Quarkus 日志配置程序的实现

下面编写一个Quarkus 日志配置程序的简单实现。

(1)导入工程项目

导入Maven工程项目,可以从GitHub上克隆预先准备好的示例代码:

该程序位于“015-quarkus-hello-logging”目录中,是一个Maven工程项目。

(2)程序说明

在该程序中,打开com.iiit.quarkus.sample.hello.LoggingFilter类文件,其代码如下:

程序说明:

①@Provider注解表明自定义类,说明LoggingFilter类是实现了ContainerRequestFilter的自定义类,然后实现具体的filter方法。

②filter方法可以实现在日志上显示外部调用Request方法的名称、访问路径和IP地址。

(3)在命令模式下启动程序

可以直接在IDE工具上运行HelloMain程序,即运行一级菜单Run中的Run命令启动程序。

在程序启动后,可以分别运行下列命令,并观察日志的记录信息:

2.2.6 缓存系统数据

1.Quarkus 内部缓存简介

本节将介绍如何在Quarkus 应用程序的任何CDI管理的Bean中启用应用程序数据缓存。

Quarkus 会对缓存进行注解,即Quarkus 提供了一组可以在CDI管理的Bean中使用的注解来启用缓存功能。这些注解分别介绍如下。

■@CacheResult,尽可能不执行方法体,从缓存加载方法结果。

当使用@CacheResult 注解的方法被调用时,Quarkus 将计算一个缓存键并使用它检查缓存中是否已经调用了该方法。如果该方法有一个或多个参数,则从所有方法参数或用@CacheKey注解的所有参数来计算键。作为键一部分的每个非基元方法参数,必须正确实现equals方法和hashCode 方法,只有这样缓存才能按预期工作。该注解也可以用于没有参数的方法,在这种情况下,将使用从缓存名称派生的默认键。如果在缓存中找到一个值,则返回该值,而带注解的方法不会实际执行。如果找不到值,则调用带注解的方法,并使用计算出来的键将返回的值存储到缓存中。

使用@CacheResult 注解的方法受缓存锁定未命中机制的保护。如果多个并发调用尝试从同一个丢失的键中检索缓存值,则该方法将只被调用一次。第一个并发调用将触发方法调用,而随后的并发调用将等待方法调用结束后才能获取缓存的结果。lockTimeout 参数可用于给定延迟后的中断锁定。默认情况下,锁定超时是禁用的,这意味着锁定不会中断。该注解不能用于返回void的方法,但Quarkus 能够缓存空值。

■@CacheInvalidate,从缓存中移除项。

当使用@CacheInvalidate 注解的方法被调用时,Quarkus 将计算一个缓存键并使用它尝试从缓存中删除现有项。如果该方法有一个或多个参数,则从所有方法参数或使用@CacheKey注解的所有参数来计算键。该注解也可以用于没有参数的方法,在这种情况下,将使用从缓存名称派生的默认键。如果该键没有标识任何缓存项,则不会发生任何事情。

■@CacheInvalidateAll,当使用@CacheInvalidateAll 注解的方法被调用时,Quarkus 将删除缓存中的所有条目。

■@CacheKey,当方法参数使用@CacheKey 注解时,在调用由@CacheResult 或@CacheInvalidate 注解的方法时,@CacheKey 才会被标识为缓存键的一部分。该注解是可选的,仅当某些方法参数不是缓存键的一部分时才应使用。

复合缓存密钥生成逻辑是,如果一个缓存键是由多个方法参数共同构建的,那么不管它们是否用@CacheKey 显式标识,构建逻辑都取决于这些参数在方法签名中出现的顺序。另一方面,参数名根本不会被使用,因此对缓存键没有任何影响。

2.Quarkus 内部缓存程序的实现

下面编写一个Quarkus 内部缓存程序的简单实现。

(1)导入工程项目

导入Maven工程项目,可以从GitHub上克隆预先准备好的示例代码:

该程序位于“016-quarkus-hello-cache”目录中,是一个Maven工程项目。

(2)程序说明

在该程序中,打开com.iiit.quarkus.sample.hello.HelloResource类文件,其代码如下:

程序说明:

①HelloResource类注入了带有缓存处理的HelloService对象。

②HelloResource类通过两次调用HelloService对象方法所用的时间计算出时间差,这样可以了解两次调用之间的区别。

打开程序中的com.iiit.quarkus.sample.hello.HelloService类文件,其代码如下:

程序说明:

①@CacheResult(cacheName="hello-cache")定义了一个名为hello-cache的缓存值。

② 当第一次从外部调用 getHello 方法时,会沉睡 2000μs,然后返回调用时间。当第二次及以后调用该方法时,由于从缓存中获取数据,故调用时间非常短。

(3)在命令模式下启动程序

可以直接在 IDE工具上运行 HelloMain程序,即选择一级菜单 Run中的 run命令启动程序。

在程序启动后,可以反复执行命令curl http://localhost:8080/hello并观察反馈信息。这时的开发工具控制台反馈信息如下:

可以看到,第一次获取数据的时间最长,其后获取数据的时间都为0。

2.2.7 基础开发案例

Quarkus 基础开发案例及其简介如表2-2所示。

表2-2 Quarkus 基础开发案例及其简介