java如何运行动态脚本

内容

  • 需求说明
  • java如何运行动态脚本
  • ScriptEngine详解

需求说明

最近自己在用spring boot开发一个东西,遇到需要执行用户编写的动态语言脚本,如python,js,groovy,ruby等动态语言.

java如何运动动态脚本

就拿groovy来举例子吧

在查阅的资料中,java运行groovy语言主要通过三种方式

  • GroovyShell
  • GroovyClassLoader
  • ScriptEngine:JSR-223是推荐的一种使用策略.规范化,简便的运行动态语言脚本
GroovyShell

创建GroovyShell实例

通过evaluate方法执行groovy代码片段

我用的是gradle,如果你想运行关于本文中关于GroovyShell的demo,需要添加以下依赖项

compile group: 'org.codehaus.groovy', name: 'groovy-jsr223', version: '3.0.0-alpha-4'
import groovy.lang.GroovyShell;

public class RunGroovyShell {
    public static void main(String [] args) {
        GroovyShell groovyShell = new GroovyShell();
        groovyShell.evaluate("println 'Hello World'");
    }
}

让我看一下GroovyShell里面有什么东西

/**
 * Represents a groovy shell capable of running arbitrary groovy scripts
 *
 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
 * @author Guillaume Laforge
 * @author Paul King
 */

在GroovyShell类头上有这么一段注释,意思就是GroovyShell类可以运行任何Groovy脚本

public GroovyShell() {
    this(null, new Binding());
}

public GroovyShell(Binding binding) {
    this(null, binding);
}

public GroovyShell(ClassLoader parent, CompilerConfiguration config) {
    this(parent, new Binding(), config);
}

public GroovyShell(CompilerConfiguration config) {
    this(new Binding(), config);
}

public GroovyShell(Binding binding, CompilerConfiguration config) {
    this(null, binding, config);
}

public GroovyShell(ClassLoader parent, Binding binding) {
    this(parent, binding, CompilerConfiguration.DEFAULT);
}

public GroovyShell(ClassLoader parent) {
    this(parent, new Binding(), CompilerConfiguration.DEFAULT);
}

public GroovyShell(ClassLoader parent, Binding binding, final CompilerConfiguration config) {
    if (binding == null) {
        throw new IllegalArgumentException("Binding must not be null.");
    }
    if (config == null) {
        throw new IllegalArgumentException("Compiler configuration must not be null.");
    }
    final ClassLoader parentLoader = (parent!=null)?parent:GroovyShell.class.getClassLoader();
    this.loader = AccessController.doPrivileged(new PrivilegedAction<GroovyClassLoader>() {
        public GroovyClassLoader run() {
            return new GroovyClassLoader(parentLoader,config);
        }
    });
    this.context = binding;        
    this.config = config;
}

/**
 * Creates a child shell using a new ClassLoader which uses the parent shell's
 * class loader as its parent
 *
 * @param shell is the parent shell used for the variable bindings and the parent class loader
 */
public GroovyShell(GroovyShell shell) {
    this(shell.loader, shell.context);
}

如上该类共有7种构造方法

public Object run(String scriptText, String fileName, List list) throws CompilationFailedException {
    return run(scriptText, fileName, (String[]) list.toArray(EMPTY_STRING_ARRAY));
}

传入脚本和参数进行运行

public Object run(final File scriptFile, String[] args) throws CompilationFailedException, IOException{
    String scriptName = scriptFile.getName();
    int p = scriptName.lastIndexOf(".");
    if (p++ >= 0) {
        if (scriptName.substring(p).equals("java")) {
            throw new CompilationFailedException(0, null);
        }
    }

    // Get the current context classloader and save it on the stack
    final Thread thread = Thread.currentThread();
    //ClassLoader currentClassLoader = thread.getContextClassLoader();

    class DoSetContext implements PrivilegedAction {
        ClassLoader classLoader;

        public DoSetContext(ClassLoader loader) {
            classLoader = loader;
        }

        public Object run() {
            thread.setContextClassLoader(classLoader);
            return null;
        }
    }

    AccessController.doPrivileged(new DoSetContext(loader));

    // Parse the script, generate the class, and invoke the main method.  This is a little looser than
    // if you are compiling the script because the JVM isn't executing the main method.
    Class scriptClass;
    try {
        scriptClass = AccessController.doPrivileged(new PrivilegedExceptionAction<Class>() {
            public Class run() throws CompilationFailedException, IOException {
                return loader.parseClass(scriptFile);
            }
        });
    } catch (PrivilegedActionException pae) {
        Exception e = pae.getException();
        if (e instanceof CompilationFailedException) {
            throw (CompilationFailedException) e;
        } else if (e instanceof IOException) {
            throw (IOException) e;
        } else {
            throw (RuntimeException) pae.getException();
        }
    }

    return runScriptOrMainOrTestOrRunnable(scriptClass, args);

    // Set the context classloader back to what it was.
    //AccessController.doPrivileged(new DoSetContext(currentClassLoader));
}

上面一个运行脚本的run方法真正执行的方法

上面的run方法只是该类中的运行脚本的中某一个run方法,还有多个重载的run方法,进行运行脚本的操作

public Object evaluate(final String scriptText) throws CompilationFailedException {
    return evaluate(scriptText, generateScriptName(), DEFAULT_CODE_BASE);
}

这个方法就是例子中我们调用的例子中的执行groovy脚本的方法,当然,该方法也有多个重载

通过查看evaluate方法可以得知,真正运行脚本的方法是parse方法,通过该方法进行运行传入的各种参数形式的groovy脚本.

关于GroovyShell通过调用evaluate执行groovy脚本,传入的参数可以是代码片段,也可以是groovy脚本文件

  • 如果你想要在实际生产开发中是使用GroovyShell进行动态运行Groovy脚本,则关于FullGC问题你不得不注意,这里不做过多的解释,详情可以参考https://my.oschina.net/u/816594/blog/388590
GroovyClassLoader
GroovyClassLoader classLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader());  
    File sourceFile = new File("D:\\TestGroovy.groovy");//文本内容的源代码  
    Class testGroovyClass = classLoader.parseClass(new GroovyCodeSource(sourceFile));  
    GroovyObject instance = (GroovyObject)testGroovyClass.newInstance();//proxy  
    Long time = (Long)instance.invokeMethod("getTime", new Date());  
    System.out.println(time);  
    Date date = (Date)instance.invokeMethod("getDate", time);  
    System.out.println(date.getTime());  
    //here  
    instance = null;  
    testGroovyClass = null;  

这种方法不做深入介绍,同样是可以用来运行groovy脚本,也会编译生成class类,有可能出现FullGC问题

ScriptEngine

首先,这不只是为java运行动态编程语言groovy专门的方法,而是为了能够运行包括groovy在内的js,python等动态脚本.

首先给出通过这个类运行Groovy脚本的demo

我使用的gradle,如果你想运行本文下面的demo,你需要提价以下依赖

compile group: 'org.codehaus.groovy', name: 'groovy-jsr223', version: '3.0.0-alpha-4'
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class RunGroovyShell {
    public static void main(String [] args) throws Exception {
        ScriptEngineManager factory = new ScriptEngineManager();//step 1
        ScriptEngine engine = factory.getEngineByName("groovy");//Step 2
        engine.eval("println \"hellow word\"");//Step 3
    }
}

demo执行结果为

My First Groovy shell

ScriptEngine详解

如上面的demo所示,通过SpringEngine执行一个简单的Groovy代码片段的的流程如下

创建一个ScriptEngineManager实体

通过String找到运行脚本所对应的engine实体

通过engine的eval方法运行代码片段

一起来看一下在new一个ScriptEngineManager实例的时候的数据流程

  1. 使用者执行new ScriptEngineManager(),进入该类的构造方法中
  2. 在构造方法中创建一个类加载器,将类加载器作为参数传到初始化方法init中
  3. 在init方法中初始化各属性,并将构造方法传入的类加载器实例传入初始化engines的initEngines方法
  4. 在initEngines方法中主要是通过serviceLoader类进行寻找执行动态语言的相关engines
  5. 至此,ScriptEngineManager实例的初始化就完毕了
public ScriptEngineManager() {
    ClassLoader var1 = Thread.currentThread().getContextClassLoader();
    this.init(var1);
}
private void init(ClassLoader var1) {
    this.globalScope = new SimpleBindings();
    this.engineSpis = new HashSet();
    this.nameAssociations = new HashMap();
    this.extensionAssociations = new HashMap();
    this.mimeTypeAssociations = new HashMap();
    this.initEngines(var1);
}
private void initEngines(final ClassLoader var1) {
    Iterator var2 = null;

    try {
        ServiceLoader var3 = (ServiceLoader)AccessController.doPrivileged(new PrivilegedAction<ServiceLoader<ScriptEngineFactory>>() {
            public ServiceLoader<ScriptEngineFactory> run() {
                return ScriptEngineManager.this.getServiceLoader(var1);
            }
        });
        var2 = var3.iterator();
    } catch (ServiceConfigurationError var5) {
        System.err.println("Can't find ScriptEngineFactory providers: " + var5.getMessage());
        return;
    }

    try {
        while(var2.hasNext()) {
            try {
                ScriptEngineFactory var7 = (ScriptEngineFactory)var2.next();
                this.engineSpis.add(var7);
            } catch (ServiceConfigurationError var4) {
                System.err.println("ScriptEngineManager providers.next(): " + var4.getMessage());
            }
        }

    } catch (ServiceConfigurationError var6) {
        System.err.println("ScriptEngineManager providers.hasNext(): " + var6.getMessage());
    }
}

在demo中真正执行动态编程语言的是ScriptEngine类,而该类的实例有四种方法获得

  • getEngineByName 通name获得相应的engine

  • getEngineByExtension 通过Extension获得相应的engine

  • getEngineByMimeType 通过MimeType获得相应的engine

  • getEngineFactories 获得该ScriptEngineManager中的List<ScriptEngineFactory>

注意事项

  • 在平时用的比较多的就是byName,byMimeType
  • 在使用byName的时候,如果没有引入动态语言相关的依赖包,比如你想要运行groovy脚本,这是你通过byName方法传入"groovy"字符串,这时候返回的是null,每一种动态语言都有不同的依赖包,可以到maven中心仓库搜索
public ScriptEngine getEngineByName(String var1) {
    if (var1 == null) {
        throw new NullPointerException();
    } else {
        Object var2;
        if (null != (var2 = this.nameAssociations.get(var1))) {
            ScriptEngineFactory var3 = (ScriptEngineFactory)var2;

            try {
                ScriptEngine var13 = var3.getScriptEngine();
                var13.setBindings(this.getBindings(), 200);
                return var13;
            } catch (Exception var11) {
                ;
            }
        }

        Iterator var12 = this.engineSpis.iterator();

        label50:
        while(var12.hasNext()) {
            ScriptEngineFactory var4 = (ScriptEngineFactory)var12.next();
            List var5 = null;

            try {
                var5 = var4.getNames();
            } catch (Exception var9) {
                ;
            }

            if (var5 != null) {
                Iterator var6 = var5.iterator();

                while(true) {
                    String var7;
                    do {
                        if (!var6.hasNext()) {
                            continue label50;
                        }

                        var7 = (String)var6.next();
                    } while(!var1.equals(var7));

                    try {
                        ScriptEngine var8 = var4.getScriptEngine();
                        var8.setBindings(this.getBindings(), 200);
                        return var8;
                    } catch (Exception var10) {
                        ;
                    }
                }
            }
        }

        return null;
    }
}
public ScriptEngine getEngineByExtension(String var1) {
    if (var1 == null) {
        throw new NullPointerException();
    } else {
        Object var2;
        if (null != (var2 = this.extensionAssociations.get(var1))) {
            ScriptEngineFactory var3 = (ScriptEngineFactory)var2;

            try {
                ScriptEngine var13 = var3.getScriptEngine();
                var13.setBindings(this.getBindings(), 200);
                return var13;
            } catch (Exception var11) {
                ;
            }
        }

        Iterator var12 = this.engineSpis.iterator();

        label51:
        while(var12.hasNext()) {
            ScriptEngineFactory var4 = (ScriptEngineFactory)var12.next();
            List var5 = null;

            try {
                var5 = var4.getExtensions();
            } catch (Exception var9) {
                ;
            }

            if (var5 != null) {
                Iterator var6 = var5.iterator();

                while(true) {
                    String var7;
                    do {
                        if (!var6.hasNext()) {
                            continue label51;
                        }

                        var7 = (String)var6.next();
                    } while(!var1.equals(var7));

                    try {
                        ScriptEngine var8 = var4.getScriptEngine();
                        var8.setBindings(this.getBindings(), 200);
                        return var8;
                    } catch (Exception var10) {
                        ;
                    }
                }
            }
        }

        return null;
    }
}
public ScriptEngine getEngineByMimeType(String var1) {
    if (var1 == null) {
        throw new NullPointerException();
    } else {
        Object var2;
        if (null != (var2 = this.mimeTypeAssociations.get(var1))) {
            ScriptEngineFactory var3 = (ScriptEngineFactory)var2;

            try {
                ScriptEngine var13 = var3.getScriptEngine();
                var13.setBindings(this.getBindings(), 200);
                return var13;
            } catch (Exception var11) {
                ;
            }
        }

        Iterator var12 = this.engineSpis.iterator();

        label51:
        while(var12.hasNext()) {
            ScriptEngineFactory var4 = (ScriptEngineFactory)var12.next();
            List var5 = null;

            try {
                var5 = var4.getMimeTypes();
            } catch (Exception var9) {
                ;
            }

            if (var5 != null) {
                Iterator var6 = var5.iterator();

                while(true) {
                    String var7;
                    do {
                        if (!var6.hasNext()) {
                            continue label51;
                        }

                        var7 = (String)var6.next();
                    } while(!var1.equals(var7));

                    try {
                        ScriptEngine var8 = var4.getScriptEngine();
                        var8.setBindings(this.getBindings(), 200);
                        return var8;
                    } catch (Exception var10) {
                        ;
                    }
                }
            }
        }

        return null;
    }
}
public List<ScriptEngineFactory> getEngineFactories() {
    ArrayList var1 = new ArrayList(this.engineSpis.size());
    Iterator var2 = this.engineSpis.iterator();

    while(var2.hasNext()) {
        ScriptEngineFactory var3 = (ScriptEngineFactory)var2.next();
        var1.add(var3);
    }

    return Collections.unmodifiableList(var1);
}

ScriptEngine实例是如何运行相应的动态编程语言的呢,让我们通过数据流程看一下

1.执行eval方法,进入实现ScriptEngine抽象类的AbstractScriptEngine类的eval方法

2.将参数进行加工,转到GroovyScriptEngineImpl类,该类继承了AbstractScriptEngine

使用该类的eval(String script, ScriptContext ctx)方法执行脚本

public Object eval(String var1) throws ScriptException {
    return this.eval((String)var1, (ScriptContext)this.context);
}
public Object eval(String script, ScriptContext ctx)
        throws ScriptException {
    try {
        String val = (String) ctx.getAttribute("#jsr223.groovy.engine.keep.globals", ScriptContext.ENGINE_SCOPE);
        ReferenceBundle bundle = ReferenceBundle.getHardBundle();
        if (val != null && val.length() > 0) {
            if (val.equalsIgnoreCase("soft")) {
                bundle = ReferenceBundle.getSoftBundle();
            } else if (val.equalsIgnoreCase("weak")) {
                bundle = ReferenceBundle.getWeakBundle();
            } else if (val.equalsIgnoreCase("phantom")) {
                bundle = ReferenceBundle.getPhantomBundle();
            }
        }
        globalClosures.setBundle(bundle);
    } catch (ClassCastException cce) { /*ignore.*/ }

    try {
        Class<?> clazz = getScriptClass(script);
        if (clazz == null) throw new ScriptException("Script class is null");
        return eval(clazz, ctx);
    } catch (Exception e) {
        if (debug) e.printStackTrace();
        throw new ScriptException(e);
    }
}

ScriptEngine为抽象类,里面的内容主要定义了执行脚本的方法

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package javax.script;

import java.io.Reader;

public interface ScriptEngine {
    String ARGV = "javax.script.argv";
    String FILENAME = "javax.script.filename";
    String ENGINE = "javax.script.engine";
    String ENGINE_VERSION = "javax.script.engine_version";
    String NAME = "javax.script.name";
    String LANGUAGE = "javax.script.language";
    String LANGUAGE_VERSION = "javax.script.language_version";

    Object eval(String var1, ScriptContext var2) throws ScriptException;

    Object eval(Reader var1, ScriptContext var2) throws ScriptException;

    Object eval(String var1) throws ScriptException;

    Object eval(Reader var1) throws ScriptException;

    Object eval(String var1, Bindings var2) throws ScriptException;

    Object eval(Reader var1, Bindings var2) throws ScriptException;

    void put(String var1, Object var2);

    Object get(String var1);

    Bindings getBindings(int var1);

    void setBindings(Bindings var1, int var2);

    Bindings createBindings();

    ScriptContext getContext();

    void setContext(ScriptContext var1);

    ScriptEngineFactory getFactory();
}

真正开始进行执行脚本的工作是实现了ScriptEngine的AbstractScriptEngine,而AbstractScriptEngine会将将要执行的代码作为参数进行包装,转发给不同动态语言对应的专门运行类(如GroovyScriptEngineImpl),由此类进行脚本的运行.

在上面的源码中可以看出,在执行动态脚本的时候是可以通过Bindings传递参数的.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容