我们经常听到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 nullSystem.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());// 3return 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 {@Beanpublic 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;}@Beanpublic RedisTemplate<String, Object> luaRedisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);// 设置 key 和 value 的序列化方式为 StringStringRedisSerializer 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 {@Autowiredprivate LuaRedisTemplateluaRedisTemplate;@Autowiredprivate RedisScript<String> script;@GetMappingpublic 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配置 **/@Autowiredprivate LuaConfig luaConfig;@GetMappingpublic String test() {// jstry (Context context = Context.create()) {Value result = context.eval("js", "var a = 333; var b = 333; a + b;");System.out.println("js: " + result.asInt());}// luaLuaValue luaValue = luaConfig.executeLuaScript("local res = 666 return tostring(res)");System.out.println("lua: " + luaValue.toString());// groovyScript groovy = new GroovyShell().parse(" def groovy = \"csdn的 孟秋与你 是世界上最好的博主 以及groovy是世界上最好的语言.class\"");System.out.println("groovy: " + groovy.run());// pythonPythonInterpreter 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的脚本语言