Java21 SpringBoot3.2.x GraalVM Docker 初探

前言

Java21 正式引入虚拟线程特性,SpringBoot 3.2.x 也支持了该特性,今天来测试一下,顺便复习一下 GraalVM 和 Docker 镜像构建

工程搭建

Idea Spring 项目初始化

  • 父项目
1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
  • 项目属性
1
2
3
4
<properties>
<java.version>21</java.version>
<skipTests>true</skipTests>
</properties>
  • 项目依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
  • 构建插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.2.1</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
  • 构建插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.9.28</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<phase>package</phase>
<goals>
<goal>compile-no-fork</goal>
</goals>
</execution>
<execution>
<id>test-native</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
<configuration>
<buildArgs>
<!--<mainClass>org.example.demo02.Demo02Application</mainClass>
<imageName>${project.artifactId}</imageName>
<arg>-H:+ReportExceptionStackTraces</arg>-->
<arg>-H:+UnlockExperimentalVMOptions</arg>
<arg>-H:-CheckToolchain</arg>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
  • 配置文件
1
2
3
4
spring:
threads:
virtual:
enabled: true

项目代码

  • 启动类
1
2
3
4
5
6
7
8
@EnableAsync
@EnableScheduling
@SpringBootApplication
public class Demo02Application {
public static void main(String[] args) {
SpringApplication.run(Demo02Application.class, args);
}
}
  • 控制层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/test")
public class TestController {
private final AsyncTaskExecutorService taskExecutorService;
@GetMapping("/hi")
public void hi() {
log.info("Rest controller method has been called {}", Thread.currentThread());
taskExecutorService.run();
}
}

@Slf4j
@Component
public class AsyncTaskExecutorService {
@Async
public void run() {
log.info("Async task method has been called {}", Thread.currentThread());
}
}

@Slf4j
@Component
public class SchedulerService {
@Scheduled(fixedDelayString = "15000")
public void run() {
log.info("Scheduled method has been called {}", Thread.currentThread());
}
}

启动项目

浏览器访问 接口 ,控制台打印信息如下,可以看到 VirtualThread 虚拟线程开启成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.1)

2023-12-29T16:46:05.999+08:00 INFO 44632 --- [ main] org.example.demo02.Demo02Application : Starting Demo02Application using Java 21.0.1 with PID 44632 (D:\Project\GraalVM\demo02\target\classes started by esafenet in D:\Project\GraalVM\demo02)
2023-12-29T16:46:06.001+08:00 INFO 44632 --- [ main] org.example.demo02.Demo02Application : No active profile set, falling back to 1 default profile: "default"
2023-12-29T16:46:06.766+08:00 INFO 44632 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2023-12-29T16:46:06.778+08:00 INFO 44632 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-12-29T16:46:06.778+08:00 INFO 44632 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.17]
2023-12-29T16:46:06.827+08:00 INFO 44632 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-12-29T16:46:06.828+08:00 INFO 44632 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 789 ms
2023-12-29T16:46:07.189+08:00 INFO 44632 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path ''
2023-12-29T16:46:07.198+08:00 INFO 44632 --- [ main] org.example.demo02.Demo02Application : Started Demo02Application in 1.537 seconds (process running for 2.27)
2023-12-29T16:46:07.205+08:00 INFO 44632 --- [ scheduling-1] o.example.demo02.task.SchedulerService : Scheduled method has been called VirtualThread[#43,scheduling-1]/runnable@ForkJoinPool-1-worker-1
2023-12-29T16:46:21.132+08:00 INFO 44632 --- [omcat-handler-0] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-12-29T16:46:21.132+08:00 INFO 44632 --- [omcat-handler-0] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2023-12-29T16:46:21.133+08:00 INFO 44632 --- [omcat-handler-0] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
2023-12-29T16:46:22.211+08:00 INFO 44632 --- [ scheduling-1] o.example.demo02.task.SchedulerService : Scheduled method has been called VirtualThread[#43,scheduling-1]/runnable@ForkJoinPool-1-worker-1
2023-12-29T16:46:25.503+08:00 INFO 44632 --- [omcat-handler-2] org.example.demo02.web.TestController : Rest controller method has been called VirtualThread[#53,tomcat-handler-2]/runnable@ForkJoinPool-1-worker-1
2023-12-29T16:46:25.510+08:00 INFO 44632 --- [ task-1] o.e.d.service.AsyncTaskExecutorService : Async task method has been called VirtualThread[#54,task-1]/runnable@ForkJoinPool-1-worker-3

GraalVM 项目构建

执行下面的命令

1
mvn -Pnative -DskipTests -DskipNativeTests clean package

项目根目录 target 下则出现了 demo02.exe,直接双击启动,一闪而过的话大概率是端口被占用,启动速度相当快 Started Demo02Application in 0.15 seconds

1
2
3
4
5
6
7
8
9
10
Produced artifacts:
D:\Project\GraalVM\demo02\target\demo02.exe (executable)
========================================================================================================================
Finished generating 'demo02' in 1m 39s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:52 min
[INFO] Finished at: 2023-12-29T16:55:31+08:00
[INFO] ------------------------------------------------------------------------

GraalVM 构建 Docker 镜像

执行下面的命令

1
mvn -Pnative -DskipTests -DskipNativeTests clean spring-boot:build-image

如果构建慢,可以提前拉取这两个镜像

  • paketobuildpacks/run-jammy-tiny:latest
  • paketobuildpacks/builder-jammy-tiny:latest

参考链接

graalvm guides
graalvm maven plugin
visualstudio2022
spring boot native image
spring doc 中文 java-graalvm-docker-image
spring doc 中文 spring-native-intro
image build三种对比
spring boot build image plugin