groovy学习笔记

本文详述了Groovy作为JVM上的动态语言,如何与Java集成,特别是在Zuul框架中的应用。通过GroovyClassLoader、GroovyShell和GroovyScriptEngine三种方式展示了Groovy调用Java的方法。此外,文章进行了Groovy与Java的性能测试,包括递归、字符串反转和XML转JSON,结果显示Groovy在初次运行时性能较低,但在后续运行中性能有所提升,尤其是在文本和文件处理上。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Groovy研究

知识背景

Groovy是用于Java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言中的其他特性。
Groovy是JVM的一个替代语言(替代是指可以用 Groovy 在Java平台上进行 Java 编程),使用方式基本与使用Java代码的方式相同,该语言特别适合与Spring的动态语言支持一起使用,设计时充分考虑了Java集成,这使Groovy与Java代码的互操作很容易。(注意:不是指Groovy替代java,而是指Groovy和java很好的结合编程)。

Groovy和Java的结合

Groovy与java集成的方式

重温下Groovy调用Java方式,包括使用GroovyClassLoader、GroovyShell和GroovyScriptEngine。
1. GroovyClassLoader
用Groovy的GroovyClassLoader,动态地加载一个脚本并执行它的行为。GroovyClassLoader是一个定制的类装载器,
负责解释加载Java类中用到的Groovy类。

GroovyClassLoader loader = new GroovyClassLoader();
Class groovyClass = loader.parseClass(new File(groovyFileName));
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
groovyObject.invokeMethod("run", "helloworld");

invokeMethod中,第一个参数指的是调用的方法名,第二个是指该方法的参数,没有则赋值null。
2. GroovyShell
GroovyShell允许在Java类中(甚至Groovy类)求任意Groovy表达式的值。您可使用Binding对象输入参数给表达式,
并最终通过GroovyShell返回Groovy表达式的计算结果。

GroovyShell shell = new GroovyShell();
Script groovyScript = shell.parse(new File(groovyFileName));
Object[] args = {};
groovyScript.invokeMethod("run", args);

invokeMethod中,第一个参数指的是调用的方法名,第二个是指该方法的参数,没有则赋值null。
3. GroovyScriptEngine
GroovyShell多用于推求对立的脚本或表达式,如果换成相互关联的多个脚本,使用GroovyScriptEngine会更好些。
GroovyScriptEngine从您指定的位置(文件系统,URL,数据库,等等)加载Groovy脚本,并且随着脚本变化而重新加载它们。如同GroovyShell一样,GroovyScriptEngine也允许您传入参数值,并能返回脚本的值。

Zuul对Groovy的使用

Netflix的Zuul使用了Groovy用来实现动态网关,Zuul的主要思想就是通过一组Fliter来实现网关功能。
启用groovy的filter代码如下,以下代码从Zuul 1.X中筛选,加上了一些中文注释。
备注:以下代码分散在不同的包路径下,本文摘取他们只是为了说明zuul中groovy的加载使用,故在一个代码段中展示。

private void initGroovyFilterManager() {
    //设置过滤器Loader的编译器为GroovyCompiler
    FilterLoader.getInstance().setCompiler(new GroovyCompiler());
    //获取groovy文件的路径位置
    String scriptRoot = System.getProperty("zuul.filter.root", "");
    if (scriptRoot.length() > 0) scriptRoot = scriptRoot + File.separator;
    try {
        FilterFileManager.setFilenameFilter(new GroovyFileFilter());
        //filter文件管理器开始工作
        //5代表轮训路径间隔,后边两个参数都为轮训路径
        FilterFileManager.init(5, scriptRoot + "route", scriptRoot + "post");
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

/**
 * Initialized the GroovyFileManager.
 *
 * @param pollingIntervalSeconds the polling interval in Seconds
 * @param directories            Any number of paths to directories to be polled may be specified
 * @throws IOException
 * @throws IllegalAccessException
 * @throws InstantiationException
 */
public static void init(int pollingIntervalSeconds, String... directories) throws Exception, IllegalAccessException, InstantiationException {
    if (INSTANCE == null) INSTANCE = new FilterFileManager();

    INSTANCE.aDirectories = directories;
    INSTANCE.pollingIntervalSeconds = pollingIntervalSeconds;
    INSTANCE.manageFiles();
    INSTANCE.startPoller();

}

void manageFiles() throws Exception, IllegalAccessException, InstantiationException {
    List<File> aFiles = getFiles();
    processGroovyFiles(aFiles);
}

/**
 * puts files into the FilterLoader. The FilterLoader will only addd new or changed filters
 *
 * @param aFiles a List<File>
 * @throws IOException
 * @throws InstantiationException
 * @throws IllegalAccessException
 */
void processGroovyFiles(List<File> aFiles) throws Exception, InstantiationException, IllegalAccessException {

    for (File file : aFiles) {
        FilterLoader.getInstance().putFilter(file);
    }
}

/**
 * From a file this will read the ZuulFilter source code, compile it, and add it to the list of current filters
 * a true response means that it was successful.
 *
 * @param file
 * @return true if the filter in file successfully read, compiled, verified and added to Zuul
 * @throws IllegalAccessException
 * @throws InstantiationException
 * @throws IOException
 */
public boolean putFilter(File file) throws Exception {
    String sName = file.getAbsolutePath() + file.getName();
    //首先判断文件是否被修改,通过保存的文件最后一次修改时间来进行比较
    if (filterClassLastModified.get(sName) != null && (file.lastModified() != filterClassLastModified.get(sName))) {
        LOG.debug("reloading filter " + sName);
        filterRegistry.remove(sName);
    }
    ZuulFilter filter = filterRegistry.get(sName);
    //判断当前的groovy对应的filter是否存在
    if (filter == null) {
        //真正的读取并且加载groovy到jvm中
        Class clazz = COMPILER.compile(file);
        if (!Modifier.isAbstract(clazz.getModifiers())) {
            //类型强转为ZuulFilter
            filter = (ZuulFilter) FILTER_FACTORY.newInstance(clazz);
            List<ZuulFilter> list = hashFiltersByType.get(filter.filterType());
            if (list != null) {
                hashFiltersByType.remove(filter.filterType()); //rebuild this list
            }
            //保存一下此次加载的groovy的信息,以便跟下一次加载作比对(主要用来判断groovy是否修改)
            filterRegistry.put(file.getAbsolutePath() + file.getName(), filter);
            filterClassLastModified.put(sName, file.lastModified());
            return true;
        }
    }

    return false;
}

/**
 * Compiles groovy class from a file
 *
 * @param file
 * @return
 * @throws java.io.IOException
 */
@Override
public Class compile(File file) throws IOException {
    GroovyClassLoader loader = getGroovyClassLoader();
    Class groovyClass = loader.parseClass(file);
    return groovyClass;
}

/**
 * Returns a new implementation of ZuulFilter as specified by the provided 
 * Class. The Class is instantiated using its nullary constructor.
 * 
 * @param clazz the Class to instantiate
 * @return A new instance of ZuulFilter
 */
@Override
public ZuulFilter newInstance(Class clazz) throws InstantiationException, IllegalAccessException {
    return (ZuulFilter) clazz.newInstance();
}

/**
 * @return a new GroovyClassLoader
 */
GroovyClassLoader getGroovyClassLoader() {
    return new GroovyClassLoader();
}

void startPoller() {
    poller = new Thread("GroovyFilterFileManagerPoller") {
        public void run() {
            while (bRunning) {
                try {
                    sleep(pollingIntervalSeconds * 1000);
                    manageFiles();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    };
    poller.setDaemon(true);
    poller.start();
}

从以上代码分析,Zuul对Groovy文本的加载主要通过GroovyClassLoader,通过该实例对象的parseClass方法将groovy文件转换为Class(java可识别的Class),然后通过该class实例化一个对象且强制转换为ZuulFilter类型(groovy脚本都实现了ZuulFilter这个接口)。

Groovy性能测试

性能评估中,我们使用GroovyCalssLoader来进行groovy的装载。代码片段如下:

private static void loadGroovy(String filePath) throws CompilationFailedException, IOException, InstantiationException, IllegalAccessException {
    File sourceFile = new File(filePath);
    boolean modify = isModify(filePath, map);
    if(modify) {
        classLoader = new GroovyClassLoader();
        groovyClass = classLoader.parseClass(new GroovyCodeSource(sourceFile));
        groovy = (GroovyObject) groovyClass.newInstance();
        map.put(sourceFile.getName(), sourceFile.lastModified());
    }
}

主要思想采用了zuul对groovy解析的方法。
1. 递归性能比对
采用递归实现斐波那契数列,并计算斐波那契数列的第N项的值。
Groovy代码段:

long count(int m) {
    if(m == 1 || m ==2) {
        return 1
    }
    return count(m - 1) + count(m - 2)
}

Java代码段:

private static long count(int m) {
    if(m == 1 || m ==2) {
        return 1;
    }
    return count(m - 1) + count(m - 2);
}   

测试结果:

递归斐波那契数列Groovy运行时间(ms)Java运行时间(ms)
第一次测试(斐波那契数列第40项)1036358
第二次测试(斐波那契数列第40项)1041355
第三次测试(斐波那契数列第40项)1012357
第四次测试(斐波那契数列第40项)1020370
第五次测试(斐波那契数列第40项)1015358
第六次测试(斐波那契数列第40项)1009356
第七次测试(斐波那契数列第45项)110223484
第八次测试(斐波那契数列第45项)111963457
第九次测试(斐波那契数列第45项)111133445
第十次测试(斐波那契数列第45项)111473441
第十一次测试(斐波那契数列第45项)111233463
第十二次测试(斐波那契数列第45项)111033450

2. 字符串反转性能比对
字符串反转的算法代码Groovy和Java采用相同的逻辑实现,我们测试字符串反转50000次,Groovy和Java代码所消耗的时间。
Groovy代码段:

String reserve(String str) {
    int len = str.length()
    if(len <= 1) {
        return str
    }
    int index = len / 2
    String left = str.subString(0, index)
    String right = str.subString(index)
    return reserve(right) + reserve(left)
}

Java代码段:

private static String reserve(String str) {
    int len = str.length();
    if(len <= 1) {
        return str;
    }
    int index = len / 2;
    String left = str.subString(0, index);
    String right = str.subString(index);
    return reserve(right) + reserve(left);
}

测试结果:

字符串反转50000次Groovy运行时间(ms)Java运行时间(ms)
第一次测试(字符串长度100位)1040466
第二次测试(字符串长度100位)608360
第三次测试(字符串长度100位)631375
第四次测试(字符串长度100位)588364
第五次测试(字符串长度100位)587365
第六次测试(字符串长度100位)592362
第七次测试(字符串长度200位)1758767
第八次测试(字符串长度200位)1252749
第九次测试(字符串长度200位)1317737
第十次测试(字符串长度200位)1243723
第十一次测试(字符串长度200位)1284732
第十二次测试(字符串长度200位)1248732

3. XML转换为JSON性能比对
XML转换为JSON,我们使用javaAPI中的XMLSerializer进行转换处理。
Groovy代码段:

String parseXML(String xmlPath) {
    File file = new File(xmlPath)
    BufferReader reader = new BufferReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))
    String str = null
    StringBuffer sb = new StringBuffer()
    while((str = reader.readLine()) != null) {
        sb.append(str)
    }
    XMLSerializer xml = new XMLSerializer()
    JSON json = xml.read(sb.toString())
    reader.close()
    return json
}

Java代码段:

public static String parseXML(String xmlPath) {
    File file = new File(xmlPath);
    BufferReader reader = new BufferReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
    String str = null;
    StringBuffer sb = new StringBuffer();
    while((str = reader.readLine()) != null) {
        sb.append(str);
    }
    XMLSerializer xml = new XMLSerializer();
    JSON json = xml.read(sb.toString());
    reader.close();
    return json;
}

测试结果:

XML转换Groovy运行时间(ms)Java运行时间(ms)
第一次测试(5次)893360
第二次测试(5次)258261
第三次测试(5次)188143
第四次测试(5次)144149
第五次测试(5次)193149
第六次测试(5次)143141
第七次测试(10次)1158489
第八次测试(10次)508323
第九次测试(10次)315281
第十次测试(10次)276326
第十一次测试(10次)276283
第十二次测试(10次)267278

结果分析

上述三组测试中,递归是属于纯数学计算,字符串反转是针对字符文本的操作,而XML解析则是读取XML文件之后进行解析。
递归计算中,Groovy代码的执行消耗时间大约均是java代码执行消耗时间的3倍。字符串反转中,Groovy代码执行消耗时间大约是java代码执行消耗时间的2倍。但是groovy代码第一次执行之后,之后的执行,可以明显看出消耗时间有所下降。Java代码的消耗时间也是有所下降,下降幅度不如groovy明显,groovy的执行消耗时间下降为java代码消耗时间的1.5倍左右。
XML解析中,Groovy代码初次运行时间大约是java代码初次运行时间的2倍。但是随着程序的多次执行,groovy和java代码运行时间都有所下降。且两者最后消耗的时间趋近与一致。
结果推论
Groovy语言本身在运算方面的处理效率比java语言要低,java大概是groovy处理效率的3倍-4倍之间,且多次执行,该消耗时间并未有所改观。
但是在文本方面的处理(文本+数学运算),groovy的除此执行消耗时间还是比较大,大概是java消耗时间的2-3倍之间,groovy的后续运行,消耗时间有所下降,但还是趋近与java消耗时间的2倍。
在文件处理方面,groovy和java的初次运行都占用比较大的时间,但groovy的消耗时间仍然是java的2倍左右。多次运行之后,groovy运行消耗时间有了大幅度提升,java代码略微提升,最后两者运行消耗时间趋近与相等。
结论:groovy单纯的数学运算(递归)与java之间有较大的差距,java效率可以约等为groovy的3倍。文本(字符串反转)和文件(XML转换为JSON)中,groovy的后续运行效率都会有所改观,这个我倾向于jvm本身对文本和文件的一个多次执行的优化,而不是groovy运算效率的提升。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值