从Springboot迁移到Micronaut

2023-10-14 16:07:37 浏览数 (1)

设置上下文

JVM是一项伟大的技术。现代版本将运行的字节码编译为本机代码,具体取决于现有的工作负载。出于这个原因,JVM 应用程序在运行时性能方面与本机可执行文件不相上下,甚至胜过本机可执行文件。

JVM 应用程序有一个预热时间,在此期间它们性能不佳。在运行时加载类没有帮助。Spring 和 Jakarta EE 等框架一直在使用类路径扫描和反射,这使得启动时间更长。对于长时间运行的进程,例如传统的应用程序服务器,这不是问题。

在容器的上下文中,它是。因为人们把容器当作牛而不是宠物来处理,所以 Kubernetes等平台会定期杀死 pod 并安排新的 pod。启动时间越长,JVM 的相关性就越低。在需要快速自动缩放 pod 数量的无服务器环境中,情况会变得更糟。

为了赶上潮流,Oracle 提供了SubstrateVM。GraalVM 的子组件 SubstrateVM 允许将 JVM 字节码转换为本机可执行文件。为此,SubstrateVM 编译字节码AOT。出于这个原因,您需要在运行时显式地向它提供 JVM 上可用的信息。例如反射的情况。请注意,某些 JVM 功能未移植到 GraalVM。此外,AOT编译是一个耗时的过程。

结果是,一方面,我们拥有 JVM 及其框架所利用的所有功能;另一方面,我们有本机可执行文件,需要精细的手动配置和大量的构建时间。

新一代的框架已经产生,旨在找到一个中间地带 ,即Micronaut 和 Quarkus。它们都旨在生成字节码 AOT。请注意,此 AOT 与上面提到的不同。两个框架都没有在运行时使用昂贵的反射,而是在构建时生成额外的类。这也使我们能够避免在启动时进行类路径扫描。简而言之,这个想法是关于在构建时提供尽可能多的代码。

示例应用程序

我希望迁移的示例应用程序足够简单,这样我就可以自己迁移它,但又不至于变得微不足道。它由以下部分组成:

  • Spring MVC实现的控制器层
  • 由 Spring Data JPA 实现的存储库层
  • 一个 JPA 实体
  • 通过 Spring Boot 在启动时生成模式和数据插入
  • Spring Boot 执行器,启用了healthbeans端点,无需身份验证即可访问

该应用程序是用 Kotlin 编写的。我将使用 H2 作为数据库来简化整个设置。

常见变化

第一个变化是替换父 POM。

代码语言:javascript复制
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.3.5.RELEASE</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

<parent>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-parent</artifactId>
    <version>2.1.3</version>
</parent>

因为 Micronaut 在构建时生成字节码,所以我们需要在编译期间添加注释处理器。因此,最后的第二步是在 POM 中配置它。

代码语言:javascript复制
<plugin>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-maven-plugin</artifactId>
  <version>${kotlin.version}</version>
  ...
  <executions>
    <execution>
      <id>kapt</id>
      <goals>
        <goal>kapt</goal>
      </goals>
      <configuration>
        <annotationProcessorPaths>
          <annotationProcessorPath>
            <groupId>io.micronaut</groupId>
            <artifactId>micronaut-inject-java</artifactId>        <!-- 1 -->
            <version>${micronaut.version}</version>
          </annotationProcessorPath>
          <annotationProcessorPath>
            <groupId>io.micronaut.data</groupId>
            <artifactId>micronaut-data-processor</artifactId>     <!-- 2 -->
            <version>${micronaut.data.version}</version>
          </annotationProcessorPath>
        </annotationProcessorPaths>
      </configuration>
    </execution>
    ...
  </executions>
  ...
</plugin>
  1. 处理依赖注入
  2. 处理持久化相关的类

您可以通过查看target/classes文件夹来检查那些额外的类。例如,示例应用程序显示以下内容:

代码语言:javascript复制
$Person$Introspection$$0.class                     PersonRepository$Intercepted$$proxy0.class
$Person$Introspection$$1.class                     PersonRepository$Intercepted$$proxy1.class
$Person$Introspection$$2.class                     PersonRepository$Intercepted$$proxy10.clas
$Person$Introspection$$3.class                     PersonRepository$Intercepted$$proxy2.class
$Person$Introspection.class                        PersonRepository$Intercepted$$proxy3.class
$Person$IntrospectionRef.class                     PersonRepository$Intercepted$$proxy4.class
$PersonControllerDefinition$$exec1.class           PersonRepository$Intercepted$$proxy5.class
$PersonControllerDefinition$$exec2.class           PersonRepository$Intercepted$$proxy6.class
$PersonControllerDefinition.class                  PersonRepository$Intercepted$$proxy7.class
$PersonControllerDefinitionClass.class             PersonRepository$Intercepted$$proxy8.class
$PersonRepository$InterceptedDefinition.class      PersonRepository$Intercepted$$proxy9.class
$PersonRepository$InterceptedDefinitionClass.class PersonRepository$Intercepted.class
Person.class                                       PersonRepository.class
PersonController.class                             SpringToMicronautApplicationKt.class

Micronaut 创建包含IntrospectionIntercepted通过kapt.

为了启动应用程序,Spring Boot 引用了一个类。

代码语言:javascript复制
@SpringBootApplication
class SpringToMicronautApplication

fun main(args: Array<String>) {
  runApplication<SpringToMicronautApplication>(*args)
}

Micronaut 允许我们只使用标准main函数。

代码语言:javascript复制
fun main(args: Array<String>) {
  build()
    .args(*args)
    .packages("ch.frankel.springtomicronaut")
    .start()
}

Spring Boot 插件可以main“自动”找到函数。在 Micronaut 中,当前版本要求您在 POM 中显式设置它:

代码语言:javascript复制
<properties>
  ...
  <exec.mainClass>ch.frankel.s2m.SpringToMicronautApplicationKt</exec.mainClass>
</properties>

迁移 web 层

迁移到 web 层需要:

  1. 用相关的 Micronaut 依赖项替换 Spring Boot 启动器
  2. 用 Micronaut 的注释替换 Spring Boot 的注释

为了使应用程序成为 Web 应用程序,Micronaut 要求添加嵌入式服务器依赖项。Tomcat、Jetty 和 Undertow 可用。由于Spring Boot默认是Tomcat,所以我们使用Tomcat:

代码语言:javascript复制
<dependency>
  <groupId>io.micronaut.servlet</groupId>
  <artifactId>micronaut-http-server-tomcat</artifactId>
  <scope>runtime</scope>
</dependency>

Spring 和 Micronaut 的注解几乎是一一对应的。使用 Micronaut 只是使用一个包的注释而不是另一个包的注释的问题。Controller不同之处在于 Spring 提供了使用专门的注解将序列化为 JSON 的能力, @RestController. Micronaut 不需要也不需要在Controller注解上设置属性。

春天

微航

o.s.w.b.a.RestController

i.m.h.a.Controller(produces = [TEXT_JSON])

o.s.w.b.a.GetMapping

i.m.h.a.Get

o.s.w.b.a.PathVariable

i.m.h.a.PathVariable

  • o.s.w.b.a=org.springframework.web.bind.annotation
  • i.m.h.a=io.micronaut.http.annotation

迁移数据访问层

要迁移到数据访问层,必须:

  1. 使用 Micronaut 的依赖项而不是 Spring Boot 的
  2. 将 Micronaut 的 Spring Boot 替换Repository为 Micronaut 的
  3. 使用 Micronaut 创建模式并加载初始数据

要创建数据源和连接池,Spring Boot 需要一个 Spring Data starter 和相关的驱动程序。Micronaut 需要三个不同的部分:

  1. 数据访问依赖
  2. 驱动程序依赖
  3. 连接池依赖
代码语言:javascript复制
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
  <groupId>io.micronaut.data</groupId>
  <artifactId>micronaut-data-hibernate-jpa</artifactId>
  <version>${micronaut.data.version}</version>
</dependency>
<dependency>
  <groupId>io.micronaut.sql</groupId>
  <artifactId>micronaut-jdbc-hikari</artifactId>
</dependency>

请注意,如果您忘记了连接池,您将在运行时遇到此错误:

代码语言:javascript复制
No backing RepositoryOperations configured for repository. Check your configuration and try again

Spring Data JPA 在运行时生成存储库的实现。Micronaut Data 在构建时生成它们。对于开发人员来说,主要区别在于存储库接口必须使用 Micronaut 的@Repository.

代码语言:javascript复制
@Repository
interface PersonRepository : CrudRepository<Person, Long>

需要配置 Micronaut 以扫描存储库和实体:

代码语言:javascript复制
jpa.default:
  packages-to-scan:
    - 'ch.frankel.springtomicronaut'

要创建模式,您可以通过两种不同的方式配置 Spring Boot:依赖 Hibernate 的模式创建或create.sql在类路径的根目录中提供一个文件。同样,要插入初始数据,您可以添加一个data.sql.

Micronaut 不提供开箱即用的机制来插入数据。但它提供了与 Flyway 的集成。放置 Flyway 的迁移的默认位置是db/migration,就像 Spring Boot 一样。

代码语言:javascript复制
<dependency>
  <groupId>io.micronaut.flyway</groupId>
  <artifactId>micronaut-flyway</artifactId>
  <version>2.1.1</version>
</dependency>

警告:我没有使用最新版本,因为父引用了一个在 Maven Central 中找不到的工件。

代码语言:javascript复制
jpa.default:
  properties.hibernate:
    hbm2ddl.auto: none                         # 1
    show_sql: true                             # 2

flyway.datasources.default: enabled            # 3
  1. 禁用 Hibernate 的模式创建
  2. 记录 SQL 语句
  3. 启用 Flyway 迁移

H2 驱动程序依赖性保持不变。虽然 Spring Boot 使用默认参数创建连接,但 Micronaut 需要显式配置它:

代码语言:javascript复制
datasources.default:
  url: jdbc:h2:mem:test
  driverClassName: org.h2.Driver
  username: sa
  dialect: H2

迁移执行器

Micronaut 还提供管理端点。它与 Spring Boot 的基本相同。

需要替换依赖项:

代码语言:javascript复制
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
  <groupId>io.micronaut</groupId>
  <artifactId>micronaut-management</artifactId>
</dependency>

与 Spring Boot 最大的区别是开发人员需要单独配置端点:

代码语言:javascript复制
endpoints:
  all.path: /actuator        # 1
  beans:
    enabled: true
    sensitive: false
  health:
    enabled: true
    sensitive: false
  flyway:
    enabled: true
    sensitive: false

这样 一个Springboot项目迁移到Micronaut项目就完成啦!

0 人点赞