1. ScriptEngine
JavaSE6中自带了JavaScript语言的脚本引擎,基于Mozilla的Rhino实现,可以通过三种方式查找脚本引擎:
① 通过脚本名称获取:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
② 通过文件扩展名获取:
ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js");
③ 通过MIME类型来获取:
ScriptEngine engine = new ScriptEngineManager().getEngineByMimeType("text/javascript");
JavaScript脚本中的println是Rhino引擎额外提供的打印控制台方法
JSEngineUtil
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
public class JSEngineUtil {
/**
* 获取js引擎
*/
public static ScriptEngine getJavaScriptEngine() {
ScriptEngineManager manager = new ScriptEngineManager();
// ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js");
ScriptEngine engine = manager.getEngineByName("JavaScript");
return engine;
}
/**
* 执行表达式字符串脚本
* @param conditionExpression 表达式字符串脚本
* @param context 上下文容器
* @throws ScriptException
* @throws NoSuchMethodException
*/
public static boolean executeExpression(String conditionExpression, Map<String, Object> context) throws ScriptException, NoSuchMethodException {
// conditionExpression 变量是$retValue形式,函数是@Substring形式
Map<String, Object> inputVariables = new HashMap<String, Object>(); // 输入变量key-value包括变量和函数参数
//处理变量
List<String> variables = getVariables(conditionExpression);// 获取脚本中的变量名$retValue形式
for (String var : variables) {
Object value = context.get(var.substring(1)); // context中变量不是以$开头
inputVariables.put(var, value);
}
ScriptEngine engine = getJavaScriptEngine();
InputStream inputStream = JSEngineUtil.class.getResourceAsStream("function.js"); //暂时函数脚本统一在function.js内
String streamString = getStreamString(inputStream);
String removeChar = removeChar(conditionExpression, '@'); //表达式脚本中函数名去除@
// 在function.js字符串后拼接表达式脚本,一起编译,原理就是和js中一样,前面是函数,后面是表达式
Object result = engine.eval(streamString + removeChar, new SimpleBindings(inputVariables));
return (boolean) result;
}
/**
* 从表达式字符串中获取变量名 $retValue
* @param conditionExpression 表达式字符串
* @return
*/
public static List<String> getVariables(String conditionExpression){
List<String> list = new ArrayList<String>();
String regex = "\\$[A-Za-z_][A-Za-z0-9_]*";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(conditionExpression);
while (matcher.find()) {
list.add(matcher.group());
}
return list;
}
/**
* 从表达式字符串中获取函数名 @Substring
* @param conditionExpression 表达式字符串
* @return
*/
public static List<String> getFunctions(String conditionExpression){
List<String> list = new ArrayList<String>();
String regex = "@[A-Za-z][A-Za-z0-9_]*";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(conditionExpression);
while (matcher.find()) {
list.add(matcher.group());
}
return list;
}
/**
* 去除字符串中特殊字符,针对变量名 $retValue, 函数名@Substring
* @param conditionExpression
* @param ch 特殊字符
* @return
*/
public static String removeChar(String conditionExpression, char ch){
for (int i = 0; i < conditionExpression.length(); i++) {
if(conditionExpression.charAt(i) == ch && i+1 < conditionExpression.length()){
char charAt = conditionExpression.charAt(i+1); // 下一个字符是[A-Za-z0-9_],这样就是变量名或者函数名
if(Pattern.matches("[A-Za-z0-9_]", String.valueOf(charAt))){
//去除@
conditionExpression = conditionExpression.substring(0,i) + conditionExpression.substring(i+1);
}
}
}
return conditionExpression;
}
/**
* 从输入流中获取字符串
* @param in
* @return
*/
public static String getStreamString(InputStream in) {
if (in != null) {
try {
//通过InputStreamReader包装类把字节输入流转为字符输入流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
StringBuffer stringBuffer = new StringBuffer();
String temp = null;
while ((temp = bufferedReader.readLine()) != null) {
stringBuffer.append(temp);
stringBuffer.append("\r\n");
}
in.close();
return stringBuffer.toString();
} catch (Exception ex) {
ex.printStackTrace();
}
}
return null;
}
}
function.js
/**
* Created by bestm on 2019/11/8.
* java动态执行js函数
*/
function Add(number1, number2){
return number1 + number2;
}
function CompareTwo(number1, number2){
return number1>number2;
}
function Substring(str, startPosition, length){
return str.substring(startPosition, startPosition + length);
}
2. BeanShell
在jbpm3,4, Jmeter 中集成有beanshell
BeanShell是用Java写成的,一个小型的、免费的、可以下载、嵌入式的Java源代码解释器,具有对象脚本的特性。
BeanShell可以执行标准Java语句和表达式,以及另外自身的一些脚本命令和语法。
pom依赖
<dependency>
<groupId>bsh</groupId>
<artifactId>bsh</artifactId>
<version>2.0b4</version>
<type>pom</type>
</dependency>
function.bsh 脚本
//两个数相加,或者字符串拼接
Add(number1, number2){
return number1 + number2;
}
//比较两个数大小
CompareTwo(number1, number2){
return number1>number2;
}
//截取字符串
Substring(str, startPosition, length){
return str.substring(startPosition, startPosition + length);
}
//字符串转为数字
StringToInt(number){
return Integer.valueOf(number);
}
BeanShellUtil
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import bsh.EvalError;
import bsh.Interpreter;
public class BeanShellUtil {
private static final String FUNCTION_SCRIPT = "function.bsh";
/**
* 从表达式字符串中获取变量名 $retValue
* @param conditionExpression 表达式字符串
* @return
*/
public static List<String> getVariables(String conditionExpression) {
List<String> list = new ArrayList<String>();
String regex = "\\$[A-Za-z_][A-Za-z0-9_]*";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(conditionExpression);
while (matcher.find()) {
list.add(matcher.group());
}
return list;
}
/**
* 去除字符串中特殊字符,针对变量名 $retValue, 函数名@Substring
* @param conditionExpression
* @param ch 特殊字符
* @return
*/
public static String removeChar(String conditionExpression, char ch){
for (int i = 0; i < conditionExpression.length(); i++) {
if(conditionExpression.charAt(i) == ch && i+1 < conditionExpression.length()){
char charAt = conditionExpression.charAt(i+1); // 下一个字符是[A-Za-z0-9_],这样就是变量名或者函数名
if(Pattern.matches("[A-Za-z0-9_]", String.valueOf(charAt))){
//去除@
conditionExpression = conditionExpression.substring(0,i) + conditionExpression.substring(i+1);
}
}
}
return conditionExpression;
}
/**
* 执行表达式字符串脚本
* @param conditionExpression 表达式字符串脚本
* @param context 上下文容器
* @return
* @throws EvalError
* @throws IOException
* @throws FileNotFoundException
*/
public static boolean executeExpression(String conditionExpression, EsContext context) throws EvalError, FileNotFoundException, IOException {
Interpreter interpreter = new Interpreter(); // 创建一个解释器对象
// 处理变量
List<String> variables = getVariables(conditionExpression);// 获取脚本中的变量名$retValue形式
for (String var : variables) {
Object value = context.get(var.substring(1)); // context中变量不是以$开头
interpreter.set(var, value); // 设置变量值
}
String removeChar = removeChar(conditionExpression, '@'); //表达式脚本中函数名去除@
//导入并执行一个脚本
String path = BeanShellUtil.class.getResource(FUNCTION_SCRIPT).getPath();
interpreter.source(path);
Object result = interpreter.eval(removeChar); // 执行脚本
return (boolean) result;
}
public static void main(String[] args) throws Exception{
//调用
// String conditionExpression = "$v1>$v2 && @Substring($branchNo,0,4)==\"4900\"";
String conditionExpression = "@StringToInt($v1) > @StringToInt($v2) && @Substring($branchNo,0,4)==\"4900\"";
EsContext context = new EsContext();
context.set("v1", "3"); // 注意 不能为字符串,比较大小为数字
context.set("v2", "2");
context.set("branchNo", "4900");
boolean executeExpression = executeExpression(conditionExpression, context);
System.out.println(executeExpression);
// 总结,BeanShell 表达式中单引号要转为双引号,比较大小,字符串会报错,要转为数字
// 对比jsEngine 弱类型,特别适合脚本 由于BeanShell 的java强类型,虽然都是 动态脚本语言
}
}
EsContext
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
public class EsContext implements Serializable{
private static final long serialVersionUID = 4407682649837124658L;
private Map<String, Object> content;
public EsContext(){
this.content = new HashMap<String, Object>(); // 注意因为效率问题,不采用有序的LinkedHashMap
}
public void setContent(Map<String, Object> content) {
this.content = content;
}
public Map<String, Object> getContent(){
return this.content;
}
public Object get(String name){
if(this.content.containsKey(name)){
Object object = this.content.get(name);
return object;
}
return null;
}
public String getString(String name){
if(this.content.containsKey(name)){
Object object = this.content.get(name);
if(object instanceof String){
return (String)object;
}
throw new ClassCastException("it's a complex object type, not a string type");
}
return null;
}
/**
* 覆盖写入
* @param name
* @param value
*/
public void set(String name, Object value){
this.content.put(name, value);
}
/**
* 不覆盖写入,如果有,不写入返回false,如果没有,写入返回true
* @param name
* @param value
* @return
*/
public boolean add(String name, Object value){
if(this.content.containsKey(name)){
return false;
}
this.content.put(name, value);
return true;
}
@Override
public String toString() {
return "EsContext [content=" + content + "]";
}
}
3. Aviator
谷歌的Aviator是一个高性能、轻量级的java语言实现的表达式求值引擎,主要用于各种表达式的动态求值。
AviatorScript 5.x 支持脚本
pom依赖
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.1.2</version> <!-- jdk1.8 -->
</dependency>
hello.av
## com/yl/examples/hello.av
p("hello, AviatorScript! " + (a + b));
return "#{a * b + b} + #{a}";
测试
public static void test1() throws IOException {
Expression exp = AviatorEvaluator.getInstance().compileScript("com/yl/examples/hello.av");
Map<String, Object> newEnv = exp.newEnv("a", 2, "b", 6);
Object result = exp.execute(newEnv);
System.out.println(result);
}
//测试表达式
public static void test2() throws IOException {
Expression exp = AviatorEvaluator.getInstance().compile("a + b");
Map<String, Object> newEnv = exp.newEnv("a", 2, "b", 6);
Object result = exp.execute(newEnv);
System.out.println(result);
}