简介
Micronaut是一个创新性的、现代的、基于JVM的全堆栈云原生Java开发框架,旨在构建模块化、易于测试的JVM应用程序。它支持Java、Kotlin和Groovy语言,并具有卓越的性能和低延迟。
Micronaut通过提前进行依赖注入、AOT(Ahead of Time)编译和减少反射使用等技术手段,显著减少了应用程序的启动时间和内存占用。它还提供了用于构建和部署微服务的工具和功能,包括服务发现、负载均衡、分布式配置和监控等。
2018 年 4 月,Micronaut 框架首次公开发布,Micronaut框架的诞生源于对传统Java框架使用反射、运行时生成代理和复杂的动态类加载等做法的不满。Micronaut通过使用Java注释将框架的组装计算工作转移到了编译阶段,从而完全消除了这些做法。
开发工具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 项目,一切就都设置好了,你就准备就绪了。
Micronaut 框架入门
本节将介绍如何使用 Micronaut 框架来构建云原生 Java 微服务。
使用 Micronaut 框架有几种不同的方法。你至少需要 Java SE 8 或更高版本的 JDK。如果要使用原生镜像特性,你需要 Java 11 或更高版本的 GraalVM JDK。
要创建一个 Micronaut 应用程序,你可以使用已经集成到 IDE(例如,IntelliJ IDEA Ultimate或GraalVM Tools的 VSCode Micronaut 扩展)中的向导。
另外,通过Micronaut Launch创建一个新的 Micronaut 应用程序也非常容易。它是一个项目创建向导,你可以选择想要构建的应用程序类型和要包含的特性。然后,它会生成一个包含应用程序的 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 -o demo.zip && unzip demo.zip && cd demo
上面的命令使用 Gradle 构建工具创建了一个应用程序。你也可以将“gradle”替换成“maven”。
Micronaut 框架生成的项目结构与其他 Java 项目一样:
- 一个 Gradle 或 Maven 构建文件(尽管也可以配置其他的构建工具,如 Bazel)。
- 默认配置文件是 src/main/resources/application.yml。但是,如果你不喜欢 YAML,可以使用 Java 属性、JSON、HOCON 或 TOML 作为替代。
- 默认的日志记录器是 SLF4J+Logback 的组合,配置文件为 src/main/resources/logback.xml。你也可以将 SLF4J 换成其他日志记录系统。
- 单元测试是 JUnit 5,但也支持其他测试框架,如 Spock 和 Kotest for Kotlin 等。一个新创建的项目提供了一些 Java 源代码来帮助你入门。第一个是位于 src/main/java 中的 Application.java 类,它包含了 Micronaut 应用程序的主入口点:
package demo;
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
Micronaut.run(Application.class, 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;
@MicronautTest
class DemoTest {
@Inject
EmbeddedApplication> application;
@Test
void testItWorks() {
Assertions.assertTrue(application.isRunning());
}
}
这个 JUnit 5 测试用例用 @MicronautTest 进行了注解。这个注解是一个 JUnit 5 扩展,用于将组件注入到测试中。在本例中,将为运行中的应用程序注入 EmbeddedApplication。
开发 REST API
Micronaut 框架支持广泛的服务器端工作负载,包括 REST、gRPC、GraphQL 和基于 Kafka、RabbitMQ、JMS 和 MQTT 消息驱动的微服务。本文将重点介绍使用默认的基于 Netty 的 HTTP 服务器构建 REST 应用程序。
Micronaut 应用程序中的每个 HTTP 路由都通过一个带 @Controller 注解的 Java 类来定义。注解的名称来源于 Model View Controller(MVC)模式。带 @Controller 注解的类可以包含一个或多个映射到特定 HTTP 动词和 URI 的方法。
“Hello World”示例可以通过 Micronaut 控制器来实现,如下所示:
package demo;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
@Controller("/hello")
public class HelloController {
@Get(uri="/{name}", produces=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 -i http://localhost:8080/hello/John
HTTP/1.1 200 OK
date: Mon, 28 Mar 2022 13:08:54 GMT
Content-Type: text/plain
content-length: 10
connection: keep-alive
Hello John
既然 Micronaut 框架非常注重测试,那么还有什么比单元测试更好的方法来测试 API 呢?下面是 HelloController 示例的一个简单的 JUnit 5 测试:
package demo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
@MicronautTest
public class HelloControllerTest {
@Test
void testHello(@Client("/") HttpClient client) {
var message = client
.toBlocking()
.retrieve("/hello/John");
assertEquals("Hello John", message);
}
}
上面的测试注入了一个 Micronaut 的 HTTP Client,向/hello/John URI 发送了一个 GET 请求,并断言结果是正确的。
Micronaut 框架的一个巨大好处是测试执行得非常快,可以与常规单元测试相媲美。即使 @MicronautTest 注解启动了 Micronaut 服务器,并运行了完整的 HTTP 请求响应周期,执行速度也不会受到影响。这样就没有必要再去学习大量用于模拟 HTTP 服务器的 API 了!开发人员因此可以编写更多的集成测试,提高代码可维护性和质量。
访问数据库
访问数据库是服务器端应用程序的一种非常常见的活动,因此许多框架都为此提供了简化,以提高开发人员在这方面的生产力。Micronaut 框架也不例外。
Micronaut Data是一个具有特殊功能的数据库访问工具包:通过与 Micronaut 编译器的集成,Micronaut Data 增加了数据库查询的编译时检查和构建时计算,从而提高了运行时效率。
与 Spring Data JPA 非常相似,Micronaut Data 允许你使用 Repository 模式定义 Java 接口,它会在编译时自动为你实现数据库查询。
Micronaut Data 对 Repository 接口的方法签名进行编译时分析,并在可能的情况下实现接口,否则将发生编译错误。
Micronaut Data 支持多种不同的数据库和查询格式,包括:
- Hibernate和JPA——你可以使用 JPA 和 Hibernate,并且 Micronaut Data JPA 会在编译时计算 JPA 查询(如上所述)。
- JDBC和SQL——对于那些更喜欢原始 SQL 和简单的数据映射而不是对象关系映射(ORM)的人来说,Micronaut Data JDBC 提供了一个更简单的解决方案,可以用它向关系数据库写入或读取 Java 17+的记录类对象和 POJO。
- MongoDB——作为最新添加的功能,Micronaut Data MongoDB 直接与 MongoDB 驱动程序集成,Micronaut序列化以完全无反射的方式在 BSON 之间编解码对象。
- R2DBC——Micronaut 框架提供了一个基于 Netty 的反应式非阻塞核心。结合使用 Micronaut Netty 服务器和响应式数据库连接(Reactive Database Connectivity,R2DBC)规范及数据库实现,你可以开发出无端到端阻塞的 SQL 应用程序。
- Oracle Coherence——一个大规模分布式数据网格,Coherence 的特点是专门与 Micronaut Data 集成,可以轻松实现由 Coherence 集群提供支持的 Repository。关于 Micronaut 框架的所有不同的数据库访问选项可以单独写成一系列文章。不过好在已经有一些优秀的指南可参考:“使用Micronaut Data JDBC访问数据库”或“使用Micronaut Data Hibernate/JPA访问数据库”。
我个人喜欢 Micronaut Data JDBC,它是一个简单的 JDBC 数据映射器。它是基于编译时 Bean 自省,完全消除了持久化层的反射。
如果你在Gradle或Maven构建文件中配置了Micronaut Data JDBC,就可以创建映射到数据库表、视图或查询结果的 Java 17 记录对象。这与 JPA 不同,JPA 中的 Java 类和表之间是一对一的映射,并通过关联对模式进行建模。这些关联引入了延迟加载等概念,而延迟加载往往会导致性能问题(比如臭名昭著的 N+1 查询问题)。下面是一个记录类定义示例:
package demo;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.data.annotation.GeneratedValue;
import io.micronaut.data.annotation.Id;
import io.micronaut.data.annotation.MappedEntity;
@MappedEntity
public record Person(
@Id
@GeneratedValue
@Nullable Long id,
String firstName,
String lastName,
int age
) {}
然后,你可以为实现了大多数应用程序逻辑的接口定义 Repository 逻辑。例如:
package demo;
import java.util.List;
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.repository.CrudRepository;
@JdbcRepository(dialect=Dialect.H2)
public interface PersonRepository extends CrudRepository {
Person findByAgeGreaterThan(int age);
List findByLastNameStartsWith(String str);
}
上面的示例通过 CrudRepository 接口提供了完整的创建、读取、更新和删除操作(CRUD)。它还使用查询表达式定义了自定义查询。
如果你有更高级的用例,可以编写自定义查询、标准查询,或者直接编写 JDBC 逻辑来绑定结果。Micronaut Data JDBC 在完全不需要反射和运行时生成代理的情况下让这些变得轻而易举,没有 JPA 中的那种状态和会话同步概念,有助于保持应用程序的轻量级以及构建成 GraalVM 原生镜像之后的出色性能。
此外,每个 Repository 接口的检查都发生在编译时。这样可以防止 Repository 方法查询不存在的属性或使用不支持的返回类型,这在支持强大的动态特性的同时,维护了 Java 的类型安全。
构建原生可执行文件
Micronaut 框架的第一个版本是在 GraalVM 之前发布的。然后,这两项伟大的技术之间产生了自然而然的协同作用,主要是因为 GraalVM 的原生镜像组件可以很容易地将一个 Micronaut 应用程序转换为一个原生可执行文件。
GraalVM 原生镜像可以很好地支持 Java 反射、运行时代理和动态类加载。开发人员需要为原生镜像提供必要的配置,说明在何时何地可以使用它们。但对于 Micronaut 框架就不需要提供这些声明,因为 Micronaut 应用程序没有在框架级别使用这些技术!这使得 GraalVM 原生镜像的提前编译(AOT)分析变得更加简单。
当然,如果你使用了依赖反射的第三方库,则需要声明。但是,你所使用的框架中的大多数东西都是无反射的。
Micronaut Gradle和Micronaut Maven插件利用了 Oracle Labs 提供的GraalVM原生构建工具来简化原生可执行文件的构建。因此,用 Gradle 构建一个原生可执行文件就是这么简单:
./gradlew nativeCompile
使用 Maven 是这样:
./mvnw package -Dpackaging=native-image
这两个命令都将在工具的构建目录中为目标平台生成原生可执行文件。