gvm安装要多少时间(GraalVM安装配置与简单使用)
gvm安装要多少时间(GraalVM安装配置与简单使用)其包含JVM,GraalVM编译器,JavaScript Runtime,LLVM Runtime。选择基于java17的最新版22.0.02。“graalvm-ce-java17-darwin-amd64-22.0.0.2.tar.gz”。下载并解压缩。下载GraalVMGraalVM社区版下载地址:https://github.com/graalvm/graalvm-ce-builds/releases
说明JDK自带一个Nashorn脚本引擎来执行JS,但随着时间的推移,也逐渐暴露出不足:
- 支持的EMACScript版本过低。Nashorn支持“ECMAScript -262 Edition 5.1”,而非更流行更强大的ES6,也即ECMAScript 2015,更不用说更新的版本了,很多新特性无法使用。对现在更熟悉ES6标准的JS开发者来说也造成了麻烦。
- Oracle官方不再支持Nashorn。Oracle官方宣布Nashorn将在JDK11弃用,现在JDK17已经正式移除了Nashorn。
其替代品就是GraalVM。
本文内容主要描述了:
- 如何将GraalVM部署在服务器(Docker方式),运行JS、Java程序(以JAR的方式)、
- 作为开发人员,如何在本地基于GRAALVM进行开发。
本地开发
笔者使用的Mac系统,Eclipse。
下载GraalVM
GraalVM社区版下载地址:
https://github.com/graalvm/graalvm-ce-builds/releases
选择基于java17的最新版22.0.02。“graalvm-ce-java17-darwin-amd64-22.0.0.2.tar.gz”。下载并解压缩。
其包含JVM,GraalVM编译器,JavaScript Runtime,LLVM Runtime。
版本如下:
% java -version
openjdk version "17.0.2" 2022-01-18
OpenJDK Runtime Environment GraalVM CE 22.0.0.2 (build 17.0.2 8-jvmci-22.0-b05)
OpenJDK 64-Bit Server VM GraalVM CE 22.0.0.2 (build 17.0.2 8-jvmci-22.0-b05 mixed mode sharing)
% js -version
GraalVM JavaScript (GraalVM CE Native 22.0.0.2)
% lli --version
LLVM 12.0.1 (GraalVM CE Native 22.0.0.2)
设置环境变量
设置GraalVM的环境变量与JVM是一样的,只是将JAVA_HOME指向GraalVM所在目录即可。
笔者设置的是~/.zshrc文件,也有设置在.bashrc的。
#这是原JDK8的目录
#JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home
#现在指向了GraalVM的目录
JAVA_HOME=/绝对路径/graalvm-ce-java17-22.0.0.2/Contents/Home
PATH=$JAVA_HOME/bin:$PATH:.
CLASSPATH=$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:.
export JAVA_HOME
export CLASSPATH
修改后【source ~/.zshrc】即可生效。在终端命令行处输入上一节的三条命令,就可以看到相关的版本号,证明环境变量设置成功。
设置开发环境Eclipse按照GraalVM
【Preferences】-【Java 】-【Installed JREs】-【Add】
选择GraalVM目录,并命名:
在要使用GraalVM的项目中指定GraalVM:
【项目右键】-【Build Path】-【Configure Build Path】
在原JDK处选择GraalVM:
设置工程编译版本
在工程的【pom.xml】文件中,一般要指定编译的版本号,通常是:
<plugin>
<groupId>org.apache.Maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
在我们使用的GraalVM中,内置的JDK是17,此处也要修改如下:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
完整的pom.xml文件,会在后文提供。
设置maven编译版本
通常都是JDK8,现在也要做更改。
打开Maven的settings.xml,修改如下:
<proFile>
<id>jdk-17</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>17</jdk>
</activation>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.compilerVersion>17</maven.compiler.compilerVersion>
</properties>
</profile>
至此,开发人员本机的系统环境、开发环境、Maven编译环境设置完毕。
代码本节内容主要介绍如何在GraalVM进行开发、打包,涉及JS、Java代码的运行,相互调用等。
之前基于Nashorn进行开发的代码,都是使用“javax.script.ScriptEngine”,GraalVM也是兼容的,但是官方明确指出这种兼容只是出于遗留原因,官方强烈鼓励使用“org.graalvm.polyglot.Context”,所以本文的代码采用官方推荐的Context进行编码,而非ScriptEngine。
创建一个简单的Maven工程,pom.xml如下:
<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>org.leo</groupId>
<artifactId>testgraalvm</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
</dependencies>
<build>
<finalName>testgraalvm</finalName> <!-- 指定打包生成的文件名 -->
<plugins>
<!-- 设置编译版本为17 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 因为引入了一些第三方JAR,本项目打包后为了顺利执行,将第三方JAR也一同打包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass></mainClass>
</manifest>
</archive>
<!--完整包的后缀名-->
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- 指定在打包节点执行jar包合并操作 -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
相关Java类:
User类,目的是Java与JS交互传递实体类数据用。
注意:通常的写法,声明变量用的是“private”,但是在此处必须用“public”,原因后述。
public class User implements Serializable {
private static final Long serialVersionUID = 7439853194483038885L;
public String name;//必须用public
public Integer age;
public User() {
super();
}
public User(String name Integer age) {
super();
this.name = name;
this.age = age;
}
//省略GET、SET
@Override
public String toString() {
return "User [name=" name " age=" age "]";
}
}
TestUtil类,JS调用Java方法用。
public class TestUtil {
public static Integer add(Integer a Integer b) {
return a b;
}
}
JS文件,Java调用JS用。
// JS使用JAVA方法
var ju=Java.type('TestUtil');
function test3(a b){
return ju.add(a b);
}
// JS将传入的参数整合为JSON,并返回
function test4(name age){
var json={};
json.name=name;
json.age=age;
return json;
}
TestGraalVM类。
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.TypeLiteral;
import org.graalvm.polyglot.Value;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
public class TestGraalVM {
public static void main(String[] args) throws IOException {
Gson gson = new GsonBuilder().create();
// 必须设置allowAllAccess(true),否则JS无法使用JAVA方法
Context context = Context.newBuilder().allowAllAccess(true).build();
// 获取JS数组的某个值
Value array = context.eval("js" "[1 2 42 4]");
int result = array.getArrayElement(2).asInt();
System.out.println("JS数组第三个数值:" result);
// 执行JS Function 无返回结果
System.out.println("=======执行JS Function 无返回结果=======");
String jsFunWithoutResult = "function test(a b c){var result= a b c;console.log('JS执行结果:' result);}";
context.eval(Source.create("js" jsFunWithoutResult));
context.getBindings("js").getMember("test").execute(1 2 3);
System.out.println("======执行JS内置函数 有返回结果=====");
Value vMath = context.getBindings("js");
vMath.putMember("a" 1);
vMath.putMember("b" 2);
System.out.println("执行JS内置函数:" context.eval(Source.create("js" "Math.max(a b)")).asInt());
// 执行JS Function,并返回结果
System.out.println("=======执行JS Function 有返回结果=======");
String jsFun = "function add(x y){var result=x y;console.log('JS返回结果:' result);return result;}";
context.eval(Source.create("js" jsFun));
Long re = context.getBindings("js").getMember("add").execute(10 20).asLong();
System.out.println("执行JS Function,并返回结果: " re);
// 获取JS定义的对象、数组。
String jsObj = "var intarr=[1 2 3];var msg = 'hello';var json={'name':'张三' 'age':18};var users=[{'name':'张三' 'age':18} {'name':'李四' 'age':22}]";
context.eval("js" jsObj);
System.out.println("======Int数组=====");
Value v = context.getBindings("js").getMember("intarr");
TypeLiteral<List<Integer>> INT_LIST = new TypeLiteral<List<Integer>>() {
};
List<Integer> ll = v.as(INT_LIST);
for (Integer i : ll) {
System.out.println("JS定义的INT数组:" i);
}
System.out.println("JS定义的字符串:" context.getBindings("js").getMember("msg").asString());
System.out.println("=======JS定义的 JSON 实体类========");
User userPojo = gson.fromJson(gson.toJson(context.getBindings("js").getMember("json").as(Map.class))
new TypeToken<User>() {
}.getType());
System.out.println(userPojo.toString());
System.out.println("======JS定义的 JSON 实体类列表=====");
// JS的JSONArr转为JAVA实体类的写法一
Value uv = context.getBindings("js").getMember("users");
System.out.println("列表总数:" uv.getArraySize());
for (int i = 0; i < uv.getArraySize(); i ) {
System.out.println(new User(uv.getArrayElement(i).getMember("name").asString()
uv.getArrayElement(i).getMember("age").asInt()).toString());
}
// JS的JSONArr转为JAVA实体类的写法二
System.out.println("--------------------");
String jsonList = gson.toJson(context.getBindings("js").getMember("users").as(List.class));
List<User> us = gson.fromJson(jsonList new TypeToken<List<User>>() {
}.getType());
for (User user : us) {
System.out.println(user.toString());
}
// GraalVM官方是很不推荐使用JS访问Java类或文件系统的,所以默认采用了安全的方法
// 但是在我们之前使用Nashorn的时候,因为其支持的ECMAScript版本较低,很多基本特性无法使用,例如没有Map、Set等。为了适应业务需要,有大量JS调用Java对象的代码,此处也写了相关的Demo。
// 不过还是不推荐这么做,GraalVM已经支持ES6,相关特性应该足以替代Java对象。
// 即便是JS做不到的功能,也推荐采用HTTP接口的方式进行调用。
System.out.println("=======JS Function 执行JAVA类方法=======");
String myJavaFun = "var ju=Java.type('TestUtil');function test2(a b){return ju.add(a b)}";
context.eval(Source.create("js" myJavaFun));
Integer i2 = context.getBindings("js").getMember("test2").execute(1 2).asInt();
System.out.println(i2);
System.out.println("========JS 接收对象============");
User param = new User("姓名" 44);
//User类的属性在这里必须设置成public,只有这样,JS里面才能通过user.name的形式获取到值
String jsPojoParam = "function test5(user){console.log(user);console.log(user.name);console.log(user.age)}";
context.eval(Source.create("js" jsPojoParam));
context.getBindings("js").getMember("test5").execute(param);
System.out.println("=========JS文件===========");
//注意此处test.js的地址,在本机或服务器运行时,要根据实际路径进行修改
String jsFile = FileUtils.readFileToString(new File("test.js") "utf-8");
context.eval(Source.create("js" jsFile));
Integer i3 = context.getBindings("js").getMember("test3").execute(1 2).asInt();
System.out.println("执行Function:" i3);
User user2 = gson.fromJson(
gson.toJson(context.getBindings("js").getMember("test4").execute("张三" 66).as(Map.class))
new TypeToken<User>() {
}.getType());
System.out.println("获取返回JSON:" user2.toString());
System.out.println("=========ES 6===========");
context.eval(Source.create("js"
"var map=new Map();map.set('name' '张三');map.set('age' 55);map.set('name' '李四');for(var [k v] of map){console.log(k '=' v)}"));
context.eval(Source.create("js"
"let arr=['苹果' '桔子' '梨'];console.log(...arr);let breakfast=arr.map(fruit => {return '吃' fruit;});console.log(breakfast);"));
}
}
使用Maven打包后生成的【testgraalvm-jar-with-dependencies.jar】就是包含了所有第三方JAR,可以直接运行的JAR包。
服务器运行本文的运行环境基于Docker,仅做演示用,如果应用到实际生产环境,还需专业的运维人员进行处理。
本文不描写如何在系统中安装Docker环境,笔者使用的是Docker Desktop。
Image下载
Docker Hub里没有GraalVM的Official Image,社区版放在了https://github.com/graalvm/container。
拉取:
docker pull ghcr.io/graalvm/graalvm-ce:latest
Container配置
运行:
-- 容器命名为 mygraalvm
docker run --name mygraalvm -it ghcr.io/graalvm/graalvm-ce:latest bash
启动&停止:
docker start mygraalvm
docker stop mygraalvm
复制之前编译好的JAR包,JS文件到Docker:
-- 复制到Docker根目录下,改名为tg.jar
docker cp /绝对地址/testgraalvm-jar-with-dependencies.jar 容器ID或name:/tg.jar
docker cp /绝对地址/test.js 容器ID或name:/test.js
在运行的容器中执行命令:
docker exec -it mygraalvm bash
此时执行ls,应该能看到前面上传的tg.jar。
修改Docker时区、中文字符集:
在本文中,仅是临时修改,供开发人员暂时测试使用。
-- 进入容器后执行
export TZ=CST-8
export LANG=C.UTF-8
JAR运行
执行JAR:
-- 前文的代码,没有设置包名
java -cp tg.jar 实际包名.TestGraalVM
运行结果: