【script】java武魂技展示:在java中使用不同的脚本语言 一文体现java生态的强大
我们经常听到java强大在于它的生态,对于生态的理解我们一般可能想到的是spring家族、微服务那一套中间件;其实java生态的强大也体现在它能使用各种脚本语言,博主最近在项目中考虑使用脚本语言以达到动态效果,因此顺带例举了常用的脚本语言方式
文章目录
- java中使用js
- java中使用python
- java中使用lua
- java中使用groovy
- 混合使用效果展示
java中使用js
在旧版本的jdk(8-14)中 默认是带有js引擎的,使用较为通用的方式即可:
// 该方式在jdk15已经不可用 被移除了 Nashorn , 在jdk15之前 默认是通过Nashorn来作为JavaScriptEngine的
ScriptEngineManager MANAGER = new ScriptEngineManager();
// JavaScriptEngine 获取一个JavaScript引擎 (脚本语言本质是实现ScriptEngine接口)
ScriptEngine engine = MANAGER.getEngineByName("js");
// 定义JavaScript代码
script = "var a = 1; var b = 2; a + b;";
try {
// 执行JavaScript代码
Object result = engine.eval(script);
// jdk >=15 engine is null
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
本文不再介绍低版本jdk使用方式,以下所有代码皆基于jdk21环境举例:
// 使用js方式
try (Context context = Context.create()) {
Value result = context.eval("js", "var a = 1; var b = 2; a + b;");
System.out.println(result.asInt());
// 3
return result.toString();
}
<!-- js 支持-->
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>21.0.0</version>
</dependency>
java中使用python
public String test(String script) {
// Jython 只支持 Python 2 语法,且无法调用用 C 扩展编写的 Python 模块(例如一些涉及原生代码的库)。
// 其它方式不受python版本限制的方式: 1.ProcessBuilder 需要安装了python 并配置环境变量
// 2. GraalVM 虚拟机(支持多语言) 这种方式需要你使用 GraalVM 作为运行环境,并安装 Python 语言支持
PythonInterpreter interpreter = new PythonInterpreter();
interpreter.exec("print('原创作者csdn:孟秋与你')");
// 执行带参数的 Python 脚本
interpreter.set("javaVar", new PyInteger(42));
interpreter.exec("pythonVar = javaVar * 2");
PyObject result = interpreter.get("pythonVar");
return result.toString();
}
<!-- python 2支持-->
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
<version>2.7.2</version>
</dependency>
java中使用lua
我们经常在redis中使用lua脚本 达到分布式锁的效果 例如redisson组件也是通过lua脚本写的
- redis使用lua脚本
/**
* @author csdn:孟秋与你 /github:qiuhuanhen
*/
@Configuration
public class LuaScriptConfig {
@Bean
public RedisScript<String> script() {
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
// resource目录下创建的scripts文件夹
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("scripts/xxx.lua")));
redisScript.setResultType(String.class);
return redisScript;
}
@Bean
public RedisTemplate<String, Object> luaRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 设置 key 和 value 的序列化方式为 String
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(stringRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
@RequestMapping("/redis")
@RestController
public class RedisIdController {
@Autowired
private LuaRedisTemplateluaRedisTemplate;
@Autowired
private RedisScript<String> script;
@GetMapping
public String test() {
// 取决你的脚本需要几个传参
return luaRedisTemplate.execute(script, java.util.List.of("param1", "param2"....));
}
}
lua示例
-- 获取自增ID KEYS[1]对应上文param1
local increment = redis.call("INCR", KEYS[1])
<!-- Spring Data Redis version和springboot版本一致 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 纯java中使用lua脚本
使用lua脚本相比其它脚本语言 有一个优势在于权限可控,可以通过控制load的模块,极大的限制lua脚本能做的事情;换句话说,当我们把脚本能力开放给维护人员或内部系统接口时,风险也是可控的,不至于被删库跑路。
一般标准下是使用JsePlatform.standardGlobals(); 这个权限还是很危险的,所以我们可以选择基础功能load即可,具体看下面注释:
本文原创作者:csdn 孟秋与你
import org.luaj.vm2.Globals;
import org.luaj.vm2.LoadState;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.compiler.LuaC;
import org.luaj.vm2.lib.Bit32Lib;
import org.luaj.vm2.lib.CoroutineLib;
import org.luaj.vm2.lib.PackageLib;
import org.luaj.vm2.lib.StringLib;
import org.luaj.vm2.lib.TableLib;
import org.luaj.vm2.lib.jse.JseBaseLib;
import org.luaj.vm2.lib.jse.JseIoLib;
import org.luaj.vm2.lib.jse.JseMathLib;
import org.luaj.vm2.lib.jse.JseOsLib;
import org.luaj.vm2.lib.jse.JsePlatform;
import org.luaj.vm2.lib.jse.LuajavaLib;
import org.springframework.stereotype.Component;
@Component
public class LuaConfig {
private Globals globals;
public LuaConfig() {
// 使用标准 JSE 环境创建 Lua 环境 (包含标准库,不注入自定义的Java 对象)
// this.globals = JsePlatform.standardGlobals();
// 从标准库的源码中,筛选最最基本的功能(降低风险)
globals = new Globals();
// 基本函数 print() error()等
globals.load(new JseBaseLib());
// 管理 Lua 模块和包,除了JseBaseLib 其它基础模块要用到。允许通过该require()函数加载外部 Lua 模块。
globals.load(new PackageLib());
// 提供用于操作整数的按位运算。
globals.load(new Bit32Lib());
// 提供操作Lua表(数组、字典)的函数。
globals.load(new TableLib());
// 提供字符串操作功能。
globals.load(new StringLib());
// 允许使用协同程序(轻量级线程)
// globals.load(new CoroutineLib());
// 提供常见的数学函数,如、、sin()等。cos()random()
globals.load(new JseMathLib());
// 提供文件输入/输出(I/O)操作的功能。
// globals.load(new JseIoLib());
// 提供与操作系统相关的功能,如获取环境变量、执行shell命令等。
// globals.load(new JseOsLib());
// 提供从 Lua 脚本与 Java 对象交互的能力 (LuajavaLib 允许 Lua 脚本直接访问 Java 对象、类、甚至 Java 运行时环境)
// globals.load(new LuajavaLib());
// 禁用path cpath (加载外部脚本)
// globals.get("package").set("path", LuaValue.valueOf(""));
// globals.get("package").set("cpath", LuaValue.valueOf(""));
// 禁用 require 函数 将外部脚本作为模块导入 (这是个辅助功能,require导入的脚本 依然不能使用globals没load的模块功能 但可能导入外部复杂的脚本)
// globals.set("require", LuaValue.NIL);
LoadState.install(globals);
LuaC.install(globals);
// 手动注入java对象方式
// LuaValue controller = CoerceJavaToLua.coerce(new LuaController());
// this.globals.set("controller", controller);
// this.globals.set("key", "原创作者 csdn:孟秋与你");
}
public LuaValue executeLuaScript(String script) {
// 加载并执行 Lua 脚本
LuaValue chunk = globals.load(script);
return chunk.call();
}
public Globals getGlobals() {
return globals;
}
}
<!--lua支持-->
<dependency>
<groupId>org.luaj</groupId>
<artifactId>luaj-jse</artifactId>
<version>3.0.1</version>
</dependency>
java中使用groovy
public String test(Long id) {
GroovyShell shell = new GroovyShell();
// 语言特性:自动返回最后一个值 即使是个固定值也会返回
Script script1 = shell.parse("def temp = binding.variables.get(\"id\") as Long \n \"一个固定值-本文原创作者:csdn孟秋与你\"\n");
// 一个固定值
return String.valueOf(script1.run());
}
<!-- groovy 支持-->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>3.0.17</version>
</dependency>
混合使用效果展示
如果将上述脚本混合使用,将会看到一个java的武魂组合技:
/**
* 不同脚本语言混合演示
*/
@RequestMapping("/test/script")
@RestController
public class ScriptController {
/** 这是上文lua部分的LuaConfig配置 **/
@Autowired
private LuaConfig luaConfig;
@GetMapping
public String test() {
// js
try (Context context = Context.create()) {
Value result = context.eval("js", "var a = 333; var b = 333; a + b;");
System.out.println("js: " + result.asInt());
}
// lua
LuaValue luaValue = luaConfig.executeLuaScript("local res = 666 return tostring(res)");
System.out.println("lua: " + luaValue.toString());
// groovy
Script groovy = new GroovyShell().parse(" def groovy = \"csdn的 孟秋与你 是世界上最好的博主 以及groovy是世界上最好的语言.class\"");
System.out.println("groovy: " + groovy.run());
// python
PythonInterpreter interpreter = new PythonInterpreter();
interpreter.exec("res = \"** python\"");
PyObject result = interpreter.get("res");
System.out.println("python: " + result.toString());
return "java";
}
}
运行展示:
tips: 这回真的 groovy是世界上最好的语言.class,不是玩梗 groovy是生成字节码 运行在jvm的脚本语言