java小脚本循环_Java脚本API运行脚本程序防止脚本死循环

前提概要

当我们使用java脚本API运行脚本的时候,在一些我们并不知道脚本的程序逻辑

并且无法修改脚本的特殊的场景下,如果脚本中存在死循环(endless loop)

或者高资源消耗的耗时循环语句,程序运行将会占用大量的系统资源,比如说CPU、

磁盘IO等。如果脚本程序是死循环并且程序同步地执行脚本的话,那么程序将会一直阻塞下去。

解决办法

由于在这些场景下,我们无法控制脚本的程序逻辑,无法改动脚本的代码,

所以有必要对脚本的执行进行控制。在这里我们可以通过异步调用的方式,

防止脚本执行阻塞对主程序带来的负面影响。并且通过添加超时机制,

对脚本执行超时的线程进行强制关闭,避免有死循环嫌疑的恶意脚本对系统资源的恶意消耗。

程序示例

1.编写脚本执行线程1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18/**

* 脚本执行线程

*/

private static abstract class ScriptThread extends Thread {

private boolean done = false;

boolean isDone() {

return done;

}

@Override

public void run() {

execute();

this.done = true;

}

public abstract void execute();

}

说明:线程中添加变量 done , 用来标志脚本执行正常结束的情况。

run方法中调用execute()方法,execute执行完成将done标志位置为true。

通过isDOne()方法判断线程是否正常结束。

创建ScriptThread对象需要实现execute()方法,方法内部添加执行脚本的逻辑代码。

2.定义脚本执行超时异常类1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24/**

* 脚本执行超时异常

*/

public static class ScriptTimeoutException extends Exception {

private static final long serialVersionUID = 1L;

private int timeout;

public ScriptTimeoutException() {

super("Script execute timeout.");

}

public ScriptTimeoutException(int timeout) {

super("Script execute timeout.");

this.timeout = timeout;

}

public int getTimeout() {

return timeout;

}

public void setTimeout(int timeout) {

this.timeout = timeout;

}

}

说明:脚本执行超时后抛出ScriptTimeoutException对象,用来区分异常类型。

3.编写脚本执行阻塞方法1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35/**

* 阻塞等待脚本执行结束,或者到达超时时间

*

*

 
 

* 脚本执行超过等待时间,强制停止脚本线程

*

*

* @param task 脚本执行线程

* @return 1:脚本正常执行结束 2:脚本强制退出执行 0:其他

*/

@SuppressWarnings("deprecation")

private static int waitScriptRunning(ScriptThread task) {

int result = 0;

long start = System.currentTimeMillis();

while (true) {

if (task.isDone()) {//如果脚本执行已经结束

result = 1;

break;

}

long current = System.currentTimeMillis();

if (current - start >= waitTime) {//超过脚本执行等待时间还未结束,取消执行,强制关闭线程

if (!task.isDone()) {

result = 2;

task.stop();

}

break;

}

try {

Thread.sleep(1000);

} catch (Exception e) {

logger.warn(e.getMessage(), e);

}

}

return result;

}

说明:

调用该方法将阻塞等待,直到线程执行完毕或者线程超时,如果超时,则强制关闭线程。

(JDK Thread#stop()方法不推荐使用,在当前的特殊场景下,我们无法修改脚本逻辑,

无法在脚本内部控制线程的中断,因此需要使用stop方法对线程进行强制退出。)

4.编写脚本执行方法1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41/**

* 执行脚本中的方法

*

* @param scriptLang 脚本语言

* @param script 需要执行的脚本文本

* @param functionName 执行的方法名

* @param args 执行脚本方法传入的参数

* @return 脚本返回值

* @throws Exception exception

*/

public static Object invokeScriptFunction(String scriptLang, String script, String functionName, Object... args)

throws Exception {

final Map map = new HashMap<>();

ScriptThread scriptThread = new ScriptThread() {

@Override

public void execute() {

try {

ScriptEngine engine = getEngine(scriptLang);

if (engine == null)

throw new Exception(String.format("Script engine not get! No support for script [%s].", scriptLang));

engine.eval(script);

map.put("value", ((Invocable) engine).invokeFunction(functionName, args));

} catch (Exception e) {

map.put("exception", e);

}

}

};

scriptThread.start();

int result = waitScriptRunning(scriptThread);

if (result == 2) {

throw new ScriptTimeoutException(waitTime);

}

Object o = map.get("exception");

if (o != null) {

throw (Exception) o;

}

return map.get("value");

}

说明:方法逻辑中首先新建脚本执行线程并提交线程的执行,接着调用等待方法阻塞等待脚本的执行,如果返回结果说明脚本超时,则抛出超时异常。

脚本执行线程execute方法体中定义执行脚本的逻辑,将执行脚本正常情况下的返回值、异常情况下的异常对象储存到map中。

如果脚本执行正常结束没有超时,则拿到map中的内容,若异常对象不为空则抛出异常对象;若异常对象为空则将脚本返回值返回。

附上Java脚本执行工具项目地址

ScriptExecuter

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值