前记
Java 在主导着企业级应用。但是在云中,采用 Java 的成本要比其竞争者更高。使用 GraalVM 进行原生编译降低了在云中 Java 的成本:它所创建的应用启动更快,使用的内存也更少。不过由于没有了JIT,性能并没有使用虚拟机的时候高。
环境
这里我构建了俩个项目, 一个是普通的 maven 项目, 一个是 Springboot 的 rest web 项目, 并且都支持 mvn/gradle 编译未 二进制可执行程序
JavaDemo
构建一个空的 Java 项目, 输出 Helloworld
public class Demo {
public static void main(String[] args) {
System.out.println("hello");
}
}
mvn pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>java-native</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>java-native-demo</name>
<description>Demo project for JAVA Native</description>
<properties>
<java.version>17</java.version>
<!-- the version same as ../graalvm-ce-java17-22.3.0/Contents/Home/bin/native-image -->
<!-- https://docs.oracle.com/en/graalvm/enterprise/20/docs/reference-manual/native-image/NativeImageMavenPlugin/-->
<graal-sdk.version>22.3.0</graal-sdk.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>${graal-sdk.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- Run independently of the jar -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>Demo</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.graalvm.nativeimage</groupId>
<artifactId>native-image-maven-plugin</artifactId>
<version>21.2.0</version>
<executions>
<execution>
<goals>
<goal>native-image</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<skip>false</skip>
<imageName>java-native</imageName>
<mainClass>Demo</mainClass>
<buildArgs>
--no-fallback
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
mvn build run
mvn package
./target/java-native
gradle cfg
settings.gradle
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
rootProject.name = 'java-native'
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
rootProject.name = 'java-native'
build.gradle
plugins {
id 'java'
id 'org.graalvm.buildtools.native' version '0.9.18'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_17
repositories {
mavenCentral()
}
dependencies {
// implementation 'org.springframework.boot:spring-boot-starter-web'
// testImplementation('org.springframework.boot:spring-boot-starter-test')
}
graalvmNative {
binaries {
main {
// https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html
mainClass = 'Demo'
imageName = 'java-native'
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(17)
vendor = JvmVendorSpec.matching("GraalVM Community")
}
}
}
}
gradle build run
gradle -x test clean build nativeCompile
./build/native/nativeCompile/java-native
native-image build
native-image -cp ./target/java-native-0.0.1-SNAPSHOT.jar -H:Name=helloworld -H:Class=Demo -H:+ReportUnsupportedElementsAtRuntime --allow-incomplete-classpath
Springboot
构建一个空的 Springboot 项目, 写个HelloController
package com.example.restservice;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
mvn pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-native-rest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-native-rest-demo</name>
<description>Demo project for Spring Boot Native</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>0.12.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>0.12.1</version>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
<execution>
<id>test-generate</id>
<goals>
<goal>test-generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<dependencies>
<!-- Required with Maven Surefire 2.x -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.9.13</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>build</goal>
</goals>
<phase>package</phase>
</execution>
<execution>
<id>test-native</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
<configuration>
<!-- ... -->
</configuration>
</plugin>
<!-- Avoid a clash between Spring Boot repackaging and native-maven-plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
<!-- ... -->
<repository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
</repository>
</repositories>
<pluginRepositories>
<!-- ... -->
<pluginRepository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
</pluginRepository>
</pluginRepositories>
</project>
mvn build run
mvn clean -Pnative -DskipTests package
./target/spring-native-rest
gradle cfg
settings.gradle
pluginManagement {
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/release' }
}
}
rootProject.name = 'spring-native-rest'
build.gradle
plugins {
id 'org.springframework.boot' version '2.7.1'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'org.springframework.experimental.aot' version '0.12.1'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_17
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/release' }
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation('org.springframework.boot:spring-boot-starter-test')
}
test {
useJUnitPlatform()
}
gradle build run
gradle -x test clean build nativeCompile
./build/native/nativeCompile/spring-native-rest
测试运行
源码:
后记
普通的maven 项目编译后大概10mb , SpringBoot 项目 编译后大概 50mb。
可以不依赖 JDK的情况下 还是比较能接受的, 只是编译的速度略慢 !!
目前Graavm 本地化翻译还是有很多不兼容的地方。还是有很长的路要走的。
不过 至少迈出的一大步。