返回

使用 Native Image Maven 外掛程式加入可達性元資料

您可以使用 Maven 從 Java 應用程式建置原生可執行檔。為此,請使用作為 Native Build Tools 專案一部分提供的 GraalVM Native Image Maven 外掛程式。

一個「真實世界」的 Java 應用程式可能需要一些 Java 反射物件,或者它會呼叫一些原生程式碼,或者存取類別路徑上的資源 - native-image 工具在建置時必須知道的動態功能,並以 元資料 的形式提供。(Native Image 在建置時動態載入類別,而不是在執行時載入。)

根據您的應用程式相依性,有三種方法可以提供元資料

  1. 使用 GraalVM 可達性元資料儲存庫
  2. 使用追蹤代理程式
  3. 自動偵測(如果所需的資源直接位於類別路徑上的 src/main/resources/ 目錄中)

本指南示範如何使用 GraalVM 可達性元資料儲存庫追蹤代理程式來建置原生可執行檔。本指南的目標是說明這兩種方法的差異,並示範如何使用可達性元資料可以簡化您的開發任務。

我們建議您按照說明逐步建立應用程式。或者,您可以直接前往完成的範例

準備示範應用程式

先決條件

請確定您已安裝 GraalVM JDK。最簡單的開始方式是使用 SDKMAN!。如需其他安裝選項,請瀏覽下載區段

  1. 在您最愛的 IDE 中或從命令列使用 Maven 建立一個名為「H2Example」的新 Java 專案,在 org.graalvm.example 套件中。

  2. 開啟主類別檔案,src/main/java/org/graalvm/example/H2Example.java,並將其內容替換為以下內容
     package org.graalvm.example;
    
     import java.sql.Connection;
     import java.sql.DriverManager;
     import java.sql.PreparedStatement;
     import java.sql.ResultSet;
     import java.sql.SQLException;
     import java.util.ArrayList;
     import java.util.Comparator;
     import java.util.HashSet;
     import java.util.List;
     import java.util.Set;
    
     public class H2Example {
    
         public static final String JDBC_CONNECTION_URL = "jdbc:h2:./data/test";
    
         public static void main(String[] args) throws Exception {
             // Cleanup
             withConnection(JDBC_CONNECTION_URL, connection -> {
                 connection.prepareStatement("DROP TABLE IF EXISTS customers").execute();
                 connection.commit();
             });
    
             Set<String> customers = Set.of("Lord Archimonde", "Arthur", "Gilbert", "Grug");
    
             System.out.println("=== Inserting the following customers in the database: ");
             printCustomers(customers);
    
             // Insert data
             withConnection(JDBC_CONNECTION_URL, connection -> {
                 connection.prepareStatement("CREATE TABLE customers(id INTEGER AUTO_INCREMENT, name VARCHAR)").execute();
                 PreparedStatement statement = connection.prepareStatement("INSERT INTO customers(name) VALUES (?)");
                 for (String customer : customers) {
                     statement.setString(1, customer);
                     statement.executeUpdate();
                 }
                 connection.commit();
             });
    
             System.out.println("");
             System.out.println("=== Reading customers from the database.");
             System.out.println("");
    
             Set<String> savedCustomers = new HashSet<>();
             // Read data
             withConnection(JDBC_CONNECTION_URL, connection -> {
                 try (ResultSet resultSet = connection.prepareStatement("SELECT * FROM customers").executeQuery()) {
                     while (resultSet.next()) {
                         savedCustomers.add(resultSet.getObject(2, String.class));
                     }
                 }
             });
    
             System.out.println("=== Customers in the database: ");
             printCustomers(savedCustomers);
         }
    
         private static void printCustomers(Set<String> customers) {
             List<String> customerList = new ArrayList<>(customers);
             customerList.sort(Comparator.naturalOrder());
             int i = 0;
             for (String customer : customerList) {
                 System.out.println((i + 1) + ". " + customer);
                 i++;
             }
         }
    
         private static void withConnection(String url, ConnectionCallback callback) throws SQLException {
             try (Connection connection = DriverManager.getConnection(url)) {
                 connection.setAutoCommit(false);
                 callback.run(connection);
             }
         }
    
         private interface ConnectionCallback {
             void run(Connection connection) throws SQLException;
         }
     }
    
  3. 刪除 H2Example/src/test/java/ 目錄(如果存在)。

  4. 開啟專案組態檔案,pom.xml,並將其內容替換為以下內容
     <?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
             http://maven.apache.org/xsd/maven-4.0.0.xsd">
         <modelVersion>4.0.0</modelVersion>
    
         <groupId>org.graalvm.buildtools.examples</groupId>
         <artifactId>maven</artifactId>
         <version>1.0.0-SNAPSHOT</version>
    
         <properties>
             <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
             <h2.version>2.2.220</h2.version>
             <!-- Replace with your Java version -->
             <java.version>22</java.version>
             <imageName>h2example</imageName>
             <mainClass>org.graalvm.example.H2Example</mainClass>
         </properties>
    
         <dependencies>
             <!-- 1. H2 Database dependency -->
             <dependency>
                 <groupId>com.h2database</groupId>
                 <artifactId>h2</artifactId>
                 <version>${h2.version}</version>
             </dependency>
         </dependencies>
         <!-- 2. Native Image Maven plugin within a Maven profile -->
         <profiles>
             <profile>
                 <id>native</id>
                 <build>
                     <plugins>
                         <plugin>
                             <groupId>org.graalvm.buildtools</groupId>
                             <artifactId>native-maven-plugin</artifactId>
                             <version>0.10.1</version>
                             <extensions>true</extensions>
                             <executions>
                                 <execution>
                                     <id>build-native</id>
                                     <goals>
                                         <goal>compile-no-fork</goal>
                                     </goals>
                                     <phase>package</phase>
                                 </execution>
                             </executions>
                             <configuration>
                                 <buildArgs>
                                     <!-- 3. Quick build mode -->
                                     <buildArg>-Ob</buildArg>
                                 </buildArgs>
                             </configuration>
                         </plugin>
                     </plugins>
                 </build>
             </profile>
         </profiles>
         <build>
             <finalName>${project.artifactId}</finalName>
             <plugins>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-surefire-plugin</artifactId>
                     <version>3.0.0-M5</version>
                 </plugin>
    
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-compiler-plugin</artifactId>
                     <version>3.11.0</version>
                     <configuration>
                         <source>${java.version}</source>
                         <target>22</target>
                     </configuration>
                 </plugin>
    
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-jar-plugin</artifactId>
                     <version>3.3.0</version>
                     <configuration>
                         <archive>
                             <manifest>
                                 <addClasspath>true</addClasspath>
                                 <mainClass>${mainClass}</mainClass>
                             </manifest>
                         </archive>
                     </configuration>
                 </plugin>
    
                 <plugin>
                     <groupId>org.codehaus.mojo</groupId>
                     <artifactId>exec-maven-plugin</artifactId>
                     <version>3.1.1</version>
                     <executions>
                         <execution>
                             <id>java</id>
                             <goals>
                                 <goal>java</goal>
                             </goals>
                             <configuration>
                                 <mainClass>${mainClass}</mainClass>
                             </configuration>
                         </execution>
                         <execution>
                             <id>native</id>
                             <goals>
                                 <goal>exec</goal>
                             </goals>
                             <configuration>
                                 <executable>${project.build.directory}/${imageName}</executable>
                                 <workingDirectory>${project.build.directory}</workingDirectory>
                             </configuration>
                         </execution>
                     </executions>
                 </plugin>
             </plugins>
         </build>
    
     </project>
    

    1 加入 H2 資料庫的相依性,這是一個適用於 Java 的開放原始碼 SQL 資料庫。應用程式透過 JDBC 驅動程式與此資料庫互動。

    2 在附加到 package 階段的 Maven 設定檔中啟用 Native Image Maven 外掛程式。(您將使用 Maven 設定檔建置原生可執行檔。)Maven 設定檔可讓您決定是要只建置 JAR 檔案,還是要建置原生可執行檔。外掛程式會探索它需要傳遞給 native-image 的 JAR 檔案,以及可執行檔的主類別應該是什麼。

    3 您可以使用 <buildArgs> 區段將參數傳遞給底層的 native-image 建置工具。在個別的 <buildArg> 標籤中,您可以傳遞參數的方式與從命令列完全相同。-Ob 選項可啟用快速建置模式(建議僅在開發期間使用),作為範例。從外掛程式的文件中瞭解其他組態選項。

  5. (選用)建置應用程式。從儲存庫的根目錄執行下列命令
    mvn clean package
    

    這會產生一個「可執行」的 JAR 檔案,其中包含應用程式的所有相依性,以及正確設定的 MANIFEST 檔案。

使用 GraalVM 可達性元資料儲存庫建置原生可執行檔

Native Image Maven 外掛程式支援 GraalVM 可達性元資料儲存庫。此儲存庫提供預設不支援 GraalVM Native Image 的程式庫的 GraalVM 組態。其中之一是此應用程式所依賴的 H2 資料庫。需要明確啟用支援。

  1. 開啟 pom.xml,並將下列內容加入 native 設定檔的 <configuration> 元素中,以啟用 GraalVM 可達性元資料儲存庫
     <metadataRepository>
         <enabled>true</enabled>
     </metadataRepository>
    

    組態區塊應如下所示

     <configuration>
         <buildArgs>
             <buildArg>-Ob</buildArg>
         </buildArgs>
         <metadataRepository>
             <enabled>true</enabled>
         </metadataRepository>
     </configuration>
    

    外掛程式會自動從儲存庫下載元資料。

  2. 現在使用設定檔建置原生可執行檔(請注意,設定檔名稱是以 -P 旗標指定)
     mvn package -Pnative
    

    這會在 target/ 目錄中產生一個用於平台的原生可執行檔,名為 h2example

  3. 從原生可執行檔執行應用程式

     ./target/h2example 
    

    應用程式會傳回儲存在 H2 資料庫中的客戶清單。

使用追蹤代理程式建置原生可執行檔

提供 native-image 的元資料組態的第二種方法是在編譯時注入追蹤代理程式(稍後稱為「代理程式」)。預設會停用代理程式,但可以在您的 pom.xml 檔案中或透過命令列啟用。

代理程式可以在三種模式下執行

  • 標準:收集沒有條件的元資料。如果您要建置原生可執行檔,建議使用此選項。(預設)
  • 條件式:收集具有條件的元資料。如果您要為打算進一步使用的原生共用程式庫建立條件式元資料,建議使用此選項。
  • 直接:僅適用於進階使用者。此模式允許直接控制傳遞至代理程式的命令列。

請參閱以下如何使用追蹤代理程式收集元資料,以及建置套用所提供組態的原生可執行檔。

  1. native 設定檔的 <configuration> 元素中加入下列內容來啟用代理程式
     <agent>
         <enabled>true</enabled>
     </agent>
    

    組態區塊應如下所示

     <configuration>
         <agent>
             <enabled>true</enabled>
         </agent>
         <buildArgs>
             <buildArg>-Ob</buildArg>
         </buildArgs>
         <metadataRepository>
             <enabled>true</enabled>
         </metadataRepository>
     </configuration>
    
  2. 使用已啟用的代理程式執行應用程式會比較複雜,而且需要您設定個別的 MOJO 執行,以允許分支 Java 程序。在 native Maven 設定檔區段中,加入 exec-maven-plugin 外掛程式
     <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>exec-maven-plugin</artifactId>
         <version>3.1.1</version>
         <executions>
             <execution>
                 <id>java-agent</id>
                 <goals>
                     <goal>exec</goal>
                 </goals>
                 <phase>test</phase>
                 <configuration>
                     <executable>java</executable>
                     <workingDirectory>${project.build.directory}</workingDirectory>
                     <arguments>
                         <argument>-classpath</argument>
                         <classpath/>
                         <argument>${mainClass}</argument>
                     </arguments>
                 </configuration>
             </execution>
         </executions>
     </plugin>
    
  3. 在 JVM 上使用已啟用的代理程式執行您的應用程式
     mvn -Pnative -Dagent=true -DskipTests -DskipNativeBuild=true package exec:exec@java-agent
    

    代理程式會擷取並記錄對 H2 資料庫的呼叫,以及在測試執行期間遇到的所有動態功能,並將其記錄到 target/native/agent-output/main/ 目錄中的多個 *-config.json 檔案中。

  4. 使用代理程式收集的組態建置原生可執行檔
     mvn -Pnative -Dagent=true -DskipTests package exec:exec@native
    

    它會在 target/ 目錄中產生一個用於平台的原生可執行檔,名為 h2example

  5. 從原生可執行檔執行應用程式
     ./target/h2example 
    
  6. (選用)若要清除專案,請執行 mvn clean,並刪除目錄 META-INF/ 及其內容。

摘要

本指南示範如何使用 GraalVM 可達性元資料儲存庫和追蹤代理程式建置原生可執行檔。目標是顯示差異,並證明使用可達性元資料如何簡化工作。

請注意,如果您的應用程式在執行時沒有呼叫任何動態功能,則啟用 GraalVM 可達性元資料儲存庫是不必要的。在這種情況下,您的工作流程將會是

mvn package -Pnative

與我們聯繫