原生Java如何改变开发方式?何时切换及框架选择指南

作者 | Graeme Rocher

原生Java如何改变开发方式?何时切换及框架选择指南

译者 | 明知山

策划 | 丁晓昀

本文属于“Native Compilations Boosts Java”系列文章。你能够通过订阅 RSS 来获取更新通知。

Java 在企业级应用中占主导地位。然而在云计算领域,与它的一些竞争对手相比,采用 Java 的成本更高。原生编译对在云端采用 Java 的成本有降低作用:通过它创建的应用程序启动速度更快,并且使用的内存更少。

Java 用户产生了疑问:原生 Java 是怎样改变开发方式的呢?在哪些情况下应该切换到原生 Java 呢?在哪些情况下不应该切换呢?应该使用哪些框架呢?本系列文章将会对这些问题进行回答。

对服务器端 Java 认识的改变

2017 年,Java 服务器端存在一个认知方面的问题。随着发生向微服务以及轻量级容器化运行时的转变,开发人员开始留意到传统 Java 应用程序在不断膨胀。这些传统 Java 应用程序被打包后部署到了 Servlet 容器的共享 Java 虚拟机 (JVM) 上。Serverless 的出现使这种认知情况进一步加重了。

在这段时间,Object Computing 的一个团队开始重新思考设计 Java 框架的方式,要从一开始就进行设计。接着,Micronaut 框架诞生了,它是一个与众不同的 Java 框架,通过运用 Java 注释,把框架的组装计算工作转移到了编译阶段。这将传统 Java 框架所使用的反射、运行时生成代理以及复杂的动态类加载全部消除了。

以此来优化应用程序的启动性能并消除反射。

构建编译时具有明显的好处。如果在编译时计算更多的东西,那么在运行时就能以最优的方式执行。消除反射、动态类加载和运行时生成代理,这为我们提供了进一步的下游优化机会,其中包括 JIT 以及(关键的是)GraalVM 的原生镜像工具。这种方法被采用后,原生镜像无需额外的配置信息,就能够对 Micronaut 框架应用程序进行静态分析。

Micronaut 框架与 GraalVM 之间存在这种协同作用,所以 Micronaut 框架的联合创始人 Graeme Rocher 加入了 Oracle Labs。Oracle Labs 不但拥有 GraalVM,还为正在进行的 Micronaut 框架的开发作出了重大贡献。

Micronaut 框架介绍

人们普遍存在这样一种误解,即 Micronaut 框架是专门为微服务而设计的。然而实际上,Micronaut 框架为一系列不同的应用程序类型提供了一种架构,这种架构具有极限模块化的特点。

其特性包含以下方面:

用户构建 JavaFX 应用程序使用了 Micronaut 框架。

Micronaut 框架为众多模块生态系统奠定了基础,它能够帮助这些模块生态系统解决一系列问题。也正是因为这种灵活性,使得 Micronaut 框架在开发者群体中获得了极大的普及。以下是 Micronaut 的架构图:

基础层以 Java Annotation Processing (APT) 为基础,在编译时实现了依赖注入。它能够支持各种模块的构建,其中包括基于 Netty 的 HTTP 服务器。同时,它还涵盖了其他领域,像数据访问、安全性以及 JSON 序列化。

为什么要用 Micronaut 框架?

Micronaut 框架的目标在于将框架中所使用的 Java 反射、动态类加载以及运行时生成的代理和字节码等特性全部消除,目的是为了提供一种传统 Java 框架的轻量级替代方案。

消除传统框架对这些特性的依赖,这对提高性能有着深远的影响;消除传统框架对这些特性的依赖,这对降低内存消耗有着深远的影响;消除传统框架对这些特性的依赖,这对提升安全性有着深远的影响;消除传统框架对这些特性的依赖,这对增强健壮性有着深远的影响;消除传统框架对这些特性的依赖,这对提高调试的便捷性有着深远的影响;消除传统框架对这些特性的依赖,这对提高测试的便捷性有着深远的影响。与其他解决方案不同的是,Micronaut 框架应用程序在 JVM 中能够快速启动!

启动速度有了极大提升,所以无需再对集成测试和单元测试代码进行区分,这使得从编码到测试之间的时间大幅缩短。以往,由于应用程序启动过慢,我们经常不得不减少集成测试。而 Micronaut 框架消除了这种状况,因此该框架中没有配备大量 HTTP 层的模拟工具。许多其他框架提供大量模拟工具。其目的是降低启动应用程序的成本。

消除反射使得堆栈跟踪信息的数量减少了。在传统框架里,堆栈跟踪信息通常是非常繁杂的。

通过生成编译错误,Micronaut 框架提升了整体开发者体验。

Micronaut 框架入门

本节将介绍怎样利用 Micronaut 框架去构建云原生的 Java 微服务。

使用 Micronaut 框架存在几种不同的途径。你必须拥有 Java SE 8 或者版本更高的 JDK。倘若要运用原生镜像特性,那么你就需要 Java 11 或者版本更高的 GraalVM JDK。

要创建一个 Micronaut 应用程序,你能够利用已集成到 IDE 中的向导。比如,在 IntelliJ IDEA Ultimate 中可以使用该向导,或者在 GraalVM Tools 的 VSCode Micronaut 扩展中也能使用。

另外,创建一个新的 Micronaut 应用程序通过 Micronaut Launch 很容易。它是一个项目创建向导,你能选择要构建的应用程序类型以及包含的特性。接着,它会生成一个包含应用程序的 ZIP 文件,你可以去下载它,或者把代码推送到你的 Github 存储库。

如果你对命令行较为熟悉,那么能够通过一些常见的方法(其中包含 SDKMAN 和 Homebrew)来安装 Micronaut CLI,以便创建应用程序。在完成安装之后,创建一个新的应用程序是很容易的:

mn create-app demo –build gradle

如果你不喜欢去安装额外的 CLI,那么你可以借助 curl 来直接对 Micronaut Launch API 进行调用。

curl通过以下步骤进行操作:先访问 https://start.micronaut.io/demo.zip?build=gradle 并将其保存为 demo.zip;然后对 demo.zip 进行解压操作;最后进入解压后的 demo 目录。

上面的命令借助 Gradle 构建工具构建出了一个应用程序。你能够把“gradle”换成“maven”。

Micronaut 框架生成的项目结构具有和其他 Java 项目相同的特点:

package demo; importMicronaut 是 io.micronaut.runtime 中的一部分。 public class Application {    public static void main(String[] args) {Micronaut 运行了 Application 类并传入了 args 参数。   }}

调用 Micronaut.run(..) 会触发框架的启动过程。

第二个类处于 src/test/java 目录里,它的作用是验证应用程序能够成功启动,同时不存在任何错误。

package demo; import io.micronaut.runtime.EmbeddedApplication;import io.micronaut.test.extensions.junit5.annotation.MicronautTest;import org.junit.jupiter.api.Test;import org.junit.jupiter.api.Assertions; import jakarta.inject.Inject; @MicronautTestclass DemoTest {    @Inject   EmbeddedApplication application;    @Test   void testItWorks() {       Assertions.assertTrue(application.isRunning());   }}

这个 JUnit 5 测试用例被 @MicronautTest 进行了注解。@MicronautTest 是一个 JUnit 5 扩展。它用于将组件注入到测试中。在这个例子中,会为运行中的应用程序注入 EmbeddedApplication。

为 Micronaut 开发准备 IDE

一般而言,Micronaut 框架基于 Java Annotation Processing(APT)的一个优势在于,使用该框架时无需其他特殊的构建工具。所有流行的 IDE 都对 APT 提供支持,不过有些 IDE(例如 Eclipse)需要明确地将其启用。

Micronaut 框架日益流行,IDE 厂商已开始提供对其的支持。JetBrain 的 IntelliJ Ultimate 为该框架的用户提供了诸多优秀工具,其中有项目向导,还有配置自动完成以及 Micronaut 数据支持等。

此外,Visual Studio Code 拥有免费的 GraalVM 扩展包。该扩展包包含一个 Micronaut 项目创建向导,还具备配置自动完成的功能,同时也有 Micronaut 应用程序的原生镜像功能。

如果你安装了这些 IDE 中的某一个,那么在该 IDE 中打开 Gradle 项目或者 Maven 项目,这样一切就都设置好了,并且你也准备就绪了。

开发 REST API

Micronaut 框架能够支持众多的服务器端工作负载,其中包含 REST 相关的内容、gRPC 相关的内容、GraphQL 相关的内容,以及基于 Kafka 消息驱动的微服务、基于 RabbitMQ 消息驱动的微服务、基于 JMS 消息驱动的微服务和基于 MQTT 消息驱动的微服务。本文主要会把重点放在利用默认的基于 Netty 的 HTTP 服务器来构建 REST 应用程序这一方面。

Micronaut 应用程序中的每个 HTTP 路由是通过一个带特定注解的 Java 类来定义的。这个注解就是 @Controller 注解。注解的名称源自 Model View Controller(MVC)模式。带 @Controller 注解的类能够包含一个或者多个方法,这些方法会被映射到特定的 HTTP 动词和 URI 上。

“Hello World”示例能够通过 Micronaut 控制器得以实现,具体如下:

package demo;
导入 io.micronaut.http.MediaType 。引入 io.micronaut.http.annotation.Controller ;将 io.micronaut.http.annotation.Controller 进行引入;导入 io.micronaut.http.annotation.Controller 。使用 io.micronaut.http.annotation.Get 进行注解;该注解用于特定的 HTTP 请求处理;通过这种方式可以定义 HTTP 方法的处理逻辑;在 Micronaut 框架中广泛应用;常用于处理 GET 请求。
@Controller("/hello")通过特定的路由和映射来响应这些请求。
获取的地址为"/{name}",并且产生的媒体类型为MediaType.TEXT_PLAIN。 String hello(String name) { return "Hello " + name; }}

控制器被映射到了 /hello 这个路径。带有 @Get 注解的方法承担着处理 HTTP GET 请求的任务。并且该方法利用 RFC 5741 URI 模板,将方法的 name 参数进行了绑定。你能够在 IDE 里运行 Application 类的 main 方法,也可以通过./gradlew run 或者./mvnw mn:run 来启动服务器。接着,你可以向 Micronaut HTTP 服务器的默认 8080 端口发送 curl 请求,以此来测试端点。

使用 curl 命令向本地的 8080 端口发送请求,请求的路径是 /hello/John ,并显示响应的头部信息。HTTP/1.1 200 OK日期是 2022 年 3 月 28 日 13 时 08 分 54 秒,属于格林威治标准时间(GMT)。Content-Type: text/plaincontent-length: 10connection: keep-alive
Hello John

因为 Micronaut 框架对测试极为注重,所以在测试 API 方面,难道还有比单元测试更优的方法吗?以下是 HelloController 示例的一个简易的 JUnit 5 测试:

package demo;
要导入静态的 org.junit.jupiter.api.Assertions 类中的 assertEquals 方法。在测试类中使用 @Test 注解进行测试相关的操作。 可以通过该注解来标识需要进行测试的方法。 它有助于组织和执行单元测试。 能够方便地对代码的各个部分进行单独测试。 以确保代码的正确性和稳定性。
引入了 io.micronaut.http.client.HttpClient ;引入了 io.micronaut.http.client.annotation 包中的 Client 类。引入 io.micronaut.test.extensions.junit5.annotation 包中的 MicronautTest 类。
@MicronautTest它是一个公共类。
@Test在测试 Hello 函数时,需要使用带有 @Client("/") 注解的 HttpClient 客户端,即传入一个带有该注解的 HttpClient 对象作为参数。 var message = client .toBlocking() .retrieve("/hello/John");message 等于 "Hello John" 。 }}

上面的测试注入了一个 Micronaut 的 HTTP Client 。它向 /hello/John URI 发送了一个 GET 请求。并且断言了结果是正确的。

Micronaut 框架有一个很大的好处,那就是测试执行速度很快,能够与常规单元测试相媲美。当 @MicronautTest 注解启动了 Micronaut 服务器,并且运行了完整的 HTTP 请求响应周期时,执行速度不会受到影响。所以就不需要再去学习大量用于模拟 HTTP 服务器的 API 了。开发人员能够编写更多的集成测试,这使得代码的可维护性得以提高,同时也提升了代码的质量。

访问数据库

服务器端应用程序经常会进行访问数据库的活动,因为此活动很常见,所以许多框架都为其提供了简化措施,目的是提高开发人员在这方面的生产力。Micronaut 框架也具备这样的特点。

Micronaut Data 是一个数据库访问工具包,它具有特殊功能。它通过与 Micronaut 编译器进行集成,增加了数据库查询的编译时检查和构建时计算,进而提高了运行时效率。

Micronaut Data 与 Spring Data JPA 很相似,它能让你通过使用 Repository 模式来定义 Java 接口,并且在编译时会自动为你实现数据库查询。

Micronaut Data 会对 Repository 接口的方法签名进行编译时的分析。如果在分析后有可能实现该接口,就会进行实现操作。而如果无法实现,就会出现编译错误。

Micronaut Data 可以支持多种不同的数据库,也能够支持多种查询格式,这些数据库和查询格式包括:

我个人喜爱 Micronaut Data JDBC,它是一种较为简单的 JDBC 数据映射工具。它是以编译时的 Bean 自省为基础,将持久化层的反射完全消除掉了。

这与 JPA 存在差异,JPA 中的 Java 类和表是一对一映射的,并且通过关联来对模式进行建模。这些关联带来了延迟加载之类的概念,而延迟加载通常会引发性能方面的问题,例如那臭名昭著的 N+1 查询问题。以下是一个记录类的定义示例:

package demo;
该注解名为 Nullable;引入 io.micronaut.data.annotation.GeneratedValue ;将 io.micronaut.data.annotation.GeneratedValue 进行引入;导入 io.micronaut.data.annotation.GeneratedValue 。引入了 io.micronaut.data.annotation 包中的 Id 注解。引入 io.micronaut.data.annotation.MappedEntity;
@MappedEntitypublic record Person( @Id @GeneratedValue @Nullable Long id, String firstName, String lastName, int age) {}

接着,你能够为那些实现了大部分应用程序逻辑的接口去定义 Repository 逻辑。比如说:

package demo;
import java.util.List;引入 io.micronaut.data.jdbc.annotation 包中的 JdbcRepository 类。引入 io.micronaut.data.model.query.builder.sql 包中的 Dialect 类。导入 io.micronaut.data.repository 包下的 CrudRepository 类。
@JdbcRepository,其中的 dialect 为 Dialect.H2。公共接口 PersonRepository 继承了 CrudRepository {Person 查找年龄大于指定年龄的记录;Person 查找年龄比某个值大的记录;Person 查找年龄超出给定年龄的记录;Person 查找年龄大于某一特定年龄的记录。
List通过姓氏以指定字符串开头来查找;根据姓氏以某字符串开始进行查找;利用姓氏以特定字符串起始来进行查找;依据姓氏以给定字符串开头去查找;凭借姓氏以某特定字符串开端来查找。}

上面的示例为我们提供了通过 CrudRepository 接口实现的完整的创建操作、读取操作、更新操作和删除操作(CRUD)。并且,它还利用查询表达式定义了一些自定义的查询。

如果你有更高级的用例,那么你可以直接编写 JDBC 逻辑来绑定结果。Micronaut Data JDBC 能让这些轻而易举,且完全无需反射和运行时生成代理。它没有 JPA 中的那种状态和会话同步概念,这有助于保持应用程序的轻量级,也有助于构建成 GraalVM 原生镜像之后拥有出色性能。

此外,每个 Repository 接口的检查都在编译时进行。这样能够防止 Repository 方法去查询不存在的属性,也能防止其使用不支持的返回类型。它在支持强大的动态特性的同时,维护了 Java 的类型安全。

构建原生可执行文件

Micronaut 框架的首个版本先于 GraalVM 发布。之后,这两项伟大的技术之间形成了自然的协同作用,这主要是由于 GraalVM 的原生镜像组件能够较为轻松地把一个 Micronaut 应用程序转化为一个原生可执行文件。

GraalVM 原生镜像能够对 Java 反射、运行时代理以及动态类加载提供良好的支持。开发人员需为原生镜像给予必需的配置,以表明在何种情况下以及何处可以使用这些技术。然而,对于 Micronaut 框架而言,无需提供这些声明,因为 Micronaut 应用程序并未在框架层面使用这些技术。这让 GraalVM 原生镜像的提前编译(AOT)分析变得更为简便。

当然,若你使用了依赖反射的第三方库,那就需要进行声明。然而,你所使用的框架里的大部分东西都是没有反射的。

所以,用 Gradle 构建一个原生可执行文件是很简单的。

./gradlew nativeCompile

使用 Maven 是这样:

使用 mvnw 进行打包操作,并且指定打包类型为 native-image 。

这两个命令会在工具的构建目录中给目标平台生成原生的可执行文件。

运行原生可执行文件:

./demo请提供具体需要改写的句子呀,没有看到横线下的内容呢。语言风格需保持原文风格,去掉最前面的序号,将长句拆分成多个小分句,分句越短越好。语言风格需保持原文风格,去掉最前面的序号,将长句拆分成多个小分句,分句越短越好。请提供具体需要改写的句子呀,你只给了这样一个奇怪的符号组合呢。|_|  有一些符号,分别是 |_|_|\___|_|  、 \___/|_| 、 |_|\__,_|\__,_|\__|  。  Micronaut (v3.4.0)

启动时间缩短至几毫秒(如 23 毫秒),内存消耗也明显降低。由于有了这样的巨大改进,所以能够将 Micronaut 应用程序部署到内存受限或启动速度极为关键的环境中(例如无服务器工作负载)。

需要注意的是,目前正在进一步研究通过 Micronaut AOT 项目来实现更重大的改进,该项目由 Oracle Labs 开发。它会在构建原生可执行文件之前,对字节码进行额外的静态分析,以此来优化和消除死代码路径。并且它还能将 YAML 转换为 Java,避免在运行时使用 YAML 解析器等。

为云而构建

Micronaut 框架除了支持原生镜像之外,还支持许多不同的打包格式和部署目标,这些包括:

总 结

Micronaut 框架给服务器端 Java 工作负载带来了新的气息。它具备创新的编译时方式与特性,从而成为构建现代云原生 Java 应用程序的最优选择。

GraalVM 原生镜像与我们有紧密集成,并且我们与 Oracle Labs GraalVM 团队存在工作关系,这意味着像 Micronaut AOT 以及 Micronaut 序列化(Jackson Databind 的无反射替代方案)等项目将会继续带来重大创新。

Micronaut 框架拥有一个充满活力的社区。这个社区提供了许多模块,这些模块能够提高开发人员的生产力。其中包括与数据库技术集成的 Micronaut Data。

社区的反馈会持续推动框架的发展。所以,要是你有任何反馈的话,就不要迟疑,通过 Micronaut 社区把关于新功能和改进的想法分享出来。

作者简介

Graeme Rocher 创建了几个流行的开源项目,其中包括 Grails 和 Micronaut。他还是《Grails 权威指南》的合著者。目前,Graeme 是 Oracle 的架构师。Graeme 是 Java Champions 成员。他在 2018 年因在开源方面作出重要贡献而荣获由 Oracle 颁发的 Groundbreaker 奖项。

原文链接:

你可能想看:
« 上一篇
下一篇 »

相关推荐

java中什么是冒泡,冒泡算法

2026年09月06日

27阅读

java中冒号,java双冒号

2026年09月06日

26阅读

java中的->,java中的是什么意思

2026年09月06日

24阅读

java中的super用法,Java中super的说明

2026年09月06日

28阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

25阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

25阅读