AOT (GraalVM Native Image) application development of Spring Boot 3

GraalVM Native Images is a compiling tool that uses AOT (Ahead-of-Time) technology to directly compile java programs into executable programs. The compiled programs no longer depend on JRE at runtime, and at the same time start up quickly and consume less resources. It is a great advantage for traditional java programs. At the same time, for cloud-native applications, the program compiled and generated by GraalVM Native Images is very small in size, which is very suitable for cloud-native environments. At present, it is often criticized because the image generated by traditional java programs needs to include a large JRE or JDK.
Spring Boot supports AOT technology from version 3.0.
For specific code, refer to the sample project https://github.com/qihaiyan/springcamp/tree/master/spring-native

I. Overview

Spring Boot 3.0 still supports the traditional development method, which compiles and generates jar packages and executes them through JRE. On this basis, by adjusting the compilation method, it can compile and generate executable programs that can be run directly. The differences between Spring AOT and traditional applications include:

  1. Resources that are dynamically adjusted when the program is running cannot be used directly, such as reflection, dynamic proxy, etc., and need to be specified for the compiler through Hint in the code
  2. The classpath of the application is fixed after compilation and cannot be adjusted dynamically
  3. Classes will not be lazy loaded (lazy loading), one-time loading is completed when the application starts
  4. Some java aspect (AOP) technologies do not support

2. Add dependencies to the project

Add dependencies in the project's gradle.

Gradle:

plugins {
    id 'org.springframework.boot' version '3.0.0'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'org.graalvm.buildtools.native' version '0.9.18'
    id 'java'
}

group = 'cn.springcamp'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
    testCompileOnly {
        extendsFrom testAnnotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}

Compared with the traditional Spring Boot application, the plugin org.graalvm.buildtools.native is added to the gradle file, and the others are the same.

Since the plugin org.graalvm.buildtools.native has not been released to the Gradle Plugin Portal, refer to https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html , so it needs to be specified in settings.gradle Warehouse Address:

settings.gradle

pluginManagement {
    
    
    repositories {
    
    
        mavenCentral()
        gradlePluginPortal()
    }
}

3. Main program code

The sample program provides a rest interface which reads data from the database. For demonstration purposes, the H2 database is used.

Application program code:

@RestController
@SpringBootApplication
public class Application {
    
    
    @Autowired
    private DbService dbService;

    @RequestMapping("/hello")
    public DemoData hello() {
    
    
        return dbService.hello();
    }

    public static void main(String[] args) {
    
    
        SpringApplication.run(Application.class, args);
    }
}

DbService code:

@Component
public class DbService {
    
    
    @Autowired
    private TestDataRepository testDataRepository;

    public DemoData hello() {
    
    
        DemoData demoData = new DemoData();
        demoData = testDataRepository.save(demoData);
        return demoData;
    }
}

Since no reflection is used in the program, the code is no different from a traditional program.

3. Compile Native Image

Spring Boot supports 2 ways to compile Native Image, one is to compile through Docker, and Docker needs to be installed locally. The other is to compile with the local compilation environment, which requires Visual Studio to be installed.
Since the first method is relatively simple and there are no complicated operations except installing Docker, this article only introduces the second method.

3.1 Install the compilation environment

Two compilation tools, GraalVM and Visual Studio, need to be installed.
GraalVM can be downloaded and installed directly, the download address can also be installed through Scoop .
Visual Studio needs to be downloaded and installed. Since Visual Studio is relatively large, you can also only install Visual Studio Build Tools

3.2 Execute compilation command

Because the windows command line tool has a command length limit, the compilation command cannot be directly executed in the windows command line tool (including powershell and cmd), and needs to be installed in the installed Visual Studio command line tool (x64 Native Tools Command Prompt for VS 2022) implement.
Excuting an order:

gradle nativeCompile

In build\native\nativeCompilethe directory .
Run the file directly, and you can experience that the startup speed of the java program can be so fast.
Traditional application startup speed:

cn.springcamp.springnative.Application   : Started Application in 2.927 seconds (process running for 3.642)

Native application startup speed:

cn.springcamp.springnative.Application   : Started Application in 0.134 seconds (process running for 0.141)

Startup speed increased from 3.642 to 0.141 seconds.

Although the startup speed is faster, it takes a lot more time to compile, which is a disadvantage.

4. Unit testing

Traditional Spring Boot unit testing techniques can still be used. Spring Boot unit testing technology is specially introduced in this article.
It should be noted that Spring Native does not support JUnit4 and needs to use JUnit5.

Unit test code:

@Slf4j
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ApplicationTest {
    
    
    @Autowired
    private TestRestTemplate testRestTemplate;

    @Test
    public void testHello() {
    
    
        String resp = testRestTemplate.getForObject("/hello", String.class);
        log.info("hello result : {}" + resp);
        assertThat(resp, is("{\"id\":1}"));
    }
}

The business logic of the code can be verified through the traditional unit testing technology. As for whether the program can still run normally after being compiled into a Native Image, the traditional unit testing technology cannot guarantee that the Native Image unit test needs to be further used.

Native Image unit tests are executed with the following command:

gradle nativeTest

This command will first compile the application into a Native Image executable program, and then run the unit test case. Since Native Image compilation takes much longer than traditional applications, first use the traditional Spring Boot unit test technology to ensure that the code business logic is normal, and then use Native Image unit test commands to reduce the time-consuming of the entire development process.

Guess you like

Origin blog.csdn.net/haiyan_qi/article/details/128057967