`
QING____
  • 浏览: 2246147 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JAVA嵌入运行Groovy脚本

    博客分类:
  • JAVA
 
阅读更多

    最近设计一个数据统计系统,系统中上百种数据统计维度,而且这些数据统计的指标可能随时会调整.如果基于java编码的方式逐个实现数据统计的API设计,工作量大而且维护起来成本较高;最终确定为将"数据统计"的计算部分单独分离成脚本文件(javascript,或者Groovy),非常便捷了实现了"数据统计Task" 与 "数据统计规则(计算)"解耦,且可以动态的加载和运行的能力.顺便对JAVA嵌入运行Groovy脚本做个备忘.

    Java中运行Groovy,有三种比较常用的类支持:GroovyShell,GroovyClassLoader以及Java-Script引擎(JSR-223).

    1) GroovyShell: 通常用来运行"script片段"或者一些零散的表达式(Expression)

    2) GroovyClassLoader: 如果脚本是一个完整的文件,特别是有API类型的时候,比如有类似于JAVA的接口,面向对象设计时,通常使用GroovyClassLoader.

    3) ScriptEngine: JSR-223应该是推荐的一种使用策略.规范化,而且简便.

    官方参考文档:http://docs.groovy-lang.org/latest/html/documentation/guide-integrating.html

 

一.GroovyShell代码样例

    1) 简单的表达式执行,方法调用

/**
 * 简答脚本执行
 * @throws Exception
 */
public static void evalScriptText() throws Exception{
	//groovy.lang.Binding
	Binding binding = new Binding();
	GroovyShell shell = new GroovyShell(binding);
	
	binding.setVariable("name", "zhangsan");
	shell.evaluate("println 'Hello World! I am ' + name;");
	//在script中,声明变量,不能使用def,否则scrope不一致.
	shell.evaluate("date = new Date();");
	Date date = (Date)binding.getVariable("date");
	System.out.println("Date:" + date.getTime());
	//以返回值的方式,获取script内部变量值,或者执行结果
	//一个shell实例中,所有变量值,将会在此"session"中传递下去."date"可以在此后的script中获取
	Long time = (Long)shell.evaluate("def time = date.getTime(); return time;");
	System.out.println("Time:" + time);
	binding.setVariable("list", new String[]{"A","B","C"});
	//invoke method
	String joinString = (String)shell.evaluate("def call(){return list.join(' - ')};call();");
	System.out.println("Array join:" + joinString);
	shell = null;
	binding = null;
}

    GroovyShell是一种性能较低的方式,因为每次都需要创建shell和script,这也意味着每次都需要对expression进行“编译”(JAVA Class)。

    2)  伪main方法执行.

/**
 * 当groovy脚本,为完整类结构时,可以通过执行main方法并传递参数的方式,启动脚本.
 */
public static void evalScriptAsMainMethod(){
	String[] args = new String[]{"Zhangsan","10"};//main(String[] args)
	Binding binding = new Binding(args);
	GroovyShell shell = new GroovyShell(binding);
	shell.evaluate("static void main(String[] args){ if(args.length != 2) return;println('Hello,I am ' + args[0] + ',age ' + args[1])}");
	shell = null;
	binding = null;
}

    3)  通过Shell运行具有类结构的Groovy脚本

/**
 * 运行完整脚本
 * @throws Exception
 */
public static void evalScriptTextFull() throws Exception{
	StringBuffer buffer = new StringBuffer();
	//define API
	buffer.append("class User{")
			.append("String name;Integer age;")
			//.append("User(String name,Integer age){this.name = name;this.age = age};")
			.append("String sayHello(){return 'Hello,I am ' + name + ',age ' + age;}}\n");
	//Usage
	buffer.append("def user = new User(name:'zhangsan',age:1);")
			.append("user.sayHello();");
	//groovy.lang.Binding
	Binding binding = new Binding();
	GroovyShell shell = new GroovyShell(binding);
	String message = (String)shell.evaluate(buffer.toString());
	System.out.println(message);
	//重写main方法,默认执行
	String mainMethod = "static void main(String[] args){def user = new User(name:'lisi',age:12);print(user.sayHello());}";
	shell.evaluate(mainMethod);
	shell = null;
}

    4)  方法执行和分部调用

/**
 * 以面向"过程"的方式运行脚本
 * @throws Exception
 */
public static void evalScript() throws Exception{
	Binding binding = new Binding();
	GroovyShell shell = new GroovyShell(binding);
	//直接方法调用
	//shell.parse(new File(//))
	Script script = shell.parse("def join(String[] list) {return list.join('--');}");
	String joinString = (String)script.invokeMethod("join", new String[]{"A1","B2","C3"});
	System.out.println(joinString);
	////脚本可以为任何格式,可以为main方法,也可以为普通方法
	//1) def call(){...};call();
	//2) call(){...};
	script = shell.parse("static void main(String[] args){i = i * 2;}");
	script.setProperty("i", new Integer(10));
	script.run();//运行,
	System.out.println(script.getProperty("i"));
	//the same as
	System.out.println(script.getBinding().getVariable("i"));
	script = null;
	shell = null;
}

二. GroovyClassLoader代码示例

    1) 解析groovy文件

/**
 * from source file of *.groovy
 */
public static void parse() throws Exception{
	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;
}

    2) 如何加载已经编译的groovy文件(.class)

public static void load() throws Exception {
	GroovyClassLoader classLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader());
	BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\TestGroovy.class"));
	ByteArrayOutputStream bos = new ByteArrayOutputStream();
	for(;;){
		int i = bis.read();
		if( i == -1){
			break;
		}
		bos.write(i);
	}
	Class testGroovyClass = classLoader.defineClass(null, bos.toByteArray());
	//instance of proxy-class
	//if interface API is in the classpath,you can do such as:
	//MyObject instance = (MyObject)testGroovyClass.newInstance()
	GroovyObject instance = (GroovyObject)testGroovyClass.newInstance();
	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
bis.close();
	bos.close();
	instance = null;
	testGroovyClass = null;
}

三. ScriptEngine

    1) pom.xml依赖

<dependency>
	<groupId>org.codehaus.groovy</groupId>
	<artifactId>groovy</artifactId>
	<version>2.1.6</version>
</dependency>
<dependency>
	<groupId>org.codehaus.groovy</groupId>
	<artifactId>groovy-jsr223</artifactId>
	<version>2.1.6</version>
</dependency>

    2) 代码样例

public static void evalScript() throws Exception{
	ScriptEngineManager factory = new ScriptEngineManager();
	//每次生成一个engine实例
	ScriptEngine engine = factory.getEngineByName("groovy");
	System.out.println(engine.toString());
	assert engine != null;
	//javax.script.Bindings
	Bindings binding = engine.createBindings();
	binding.put("date", new Date());
	//如果script文本来自文件,请首先获取文件内容
	engine.eval("def getTime(){return date.getTime();}",binding);
	engine.eval("def sayHello(name,age){return 'Hello,I am ' + name + ',age' + age;}");
	Long time = (Long)((Invocable)engine).invokeFunction("getTime", null);
	System.out.println(time);
	String message = (String)((Invocable)engine).invokeFunction("sayHello", "zhangsan",new Integer(12));
	System.out.println(message);
}

    需要提醒的是,在groovy中,${expression} 将会被认为一个变量,如果需要输出"$"符号,需要转义为"\$".   

    这是一种性能较高的方式,engine我们可以声明为全局实例,是线程安全的。每次调用时只需要创建新的Binndings即可,此外如果脚本已经编译过(首次执行之后)其Class将会被缓存,则此后不需要再次编译。

 

    关于ScriptEngine更多介绍,请参考.

 

---END---  

1
1
分享到:
评论
3 楼 feidian1028 2014-04-25  
QING____ 写道
feidian1028 写道
兄弟,你的数据统计系统设计方案能否介绍下

这个可能说来话长..我简单描述一下:用户在系统中产生的操作,将会以日志的方式发送到采集系统(例如kafka,flume),然后再由统计系统负责分拣日志,将日志归类之后,分别存储在不同文件中,我们使用mongodb存储它们;在统计系统中,使用mongodb聚合框架--mapreduce来统计数据;因为统计系统是分布式部署,我们还有一个"单点worker"支撑系统,用来控制集群中.同一个统计任务(worker)只会在一个机器上运行;最终需要交付的是统计数据报表,比如: 2014.04.15日"PV"为120W.

mongodb中使用mapreduce是非常简单的,而且是编程良好的,可嵌入式的.这一点就决定了,开发者可以在"单点worker"系统指派统计系统分析数据时,从DB中获取map + reducer的脚本内容,然后运行它.

如果你使用过map-reducer开发过程序,你或许知道,map和reducer只不过是两个纯计算性的代码段,所以它们可以脱离运行环境,而独立的编写和保存,这也意味着它们无需被"固定"在一个java类中,所以把它们保存成Groovy脚本最好不过了;前提是你最好不要在map-reducer中执行有关业务性的代码,否则无法被剥离成Groovy脚本.

最终,我们把那些原本应该写在java类中的map-reducer代码块,"迁移"到了Groovy中,在我们的系统中,map和reducer分别为2个Groovy脚本文件,比如:PvMapper.groovy,PvReducer.groovy.
因为我们可以在java中非常便捷的调用groovy脚本,而且还可以向脚本"输入"参数,通常这种运行起来比较良好.最终groovy脚本的文本文件将会被保存在DB或者文件系统系统中.

因为Groovy可以被动态加载/解析/运行,所以我们编辑groovy脚本之后,无需重新发布统计系统,只需要在某个worker触发运行时,从文件系统中加载脚本即可.


我们有一个后台系统,我们可以在这个系统中通过文本编辑器来编写groovy脚本,也可以向每个脚本指定"参数列表",比如,你在groovy脚本中使用了$date参数,那么你可以配置$date = new Date(),不过每个参数,也是通过groovy引擎在运行时计算,并把结果传递给map-reducer脚本的.

最终统计的结果,将会后台系统中指定的存储地址,存储在文件中,并由分拣程序,负责将这些数据存储为结构化的格式,一遍后续的查询和报表设计.

十分感谢,讲解得很详细。我现在手头上也有一个类似的项目,希望在应用日志中分析出一些个性化的业务数据,分析的类型也会比较多变,所以也考虑用到这种方式。受启发了,多谢哈哈!
2 楼 QING____ 2014-04-25  
feidian1028 写道
兄弟,你的数据统计系统设计方案能否介绍下

这个可能说来话长..我简单描述一下:用户在系统中产生的操作,将会以日志的方式发送到采集系统(例如kafka,flume),然后再由统计系统负责分拣日志,将日志归类之后,分别存储在不同文件中,我们使用mongodb存储它们;在统计系统中,使用mongodb聚合框架--mapreduce来统计数据;因为统计系统是分布式部署,我们还有一个"单点worker"支撑系统,用来控制集群中.同一个统计任务(worker)只会在一个机器上运行;最终需要交付的是统计数据报表,比如: 2014.04.15日"PV"为120W.

mongodb中使用mapreduce是非常简单的,而且是编程良好的,可嵌入式的.这一点就决定了,开发者可以在"单点worker"系统指派统计系统分析数据时,从DB中获取map + reducer的脚本内容,然后运行它.

如果你使用过map-reducer开发过程序,你或许知道,map和reducer只不过是两个纯计算性的代码段,所以它们可以脱离运行环境,而独立的编写和保存,这也意味着它们无需被"固定"在一个java类中,所以把它们保存成Groovy脚本最好不过了;前提是你最好不要在map-reducer中执行有关业务性的代码,否则无法被剥离成Groovy脚本.

最终,我们把那些原本应该写在java类中的map-reducer代码块,"迁移"到了Groovy中,在我们的系统中,map和reducer分别为2个Groovy脚本文件,比如:PvMapper.groovy,PvReducer.groovy.
因为我们可以在java中非常便捷的调用groovy脚本,而且还可以向脚本"输入"参数,通常这种运行起来比较良好.最终groovy脚本的文本文件将会被保存在DB或者文件系统系统中.

因为Groovy可以被动态加载/解析/运行,所以我们编辑groovy脚本之后,无需重新发布统计系统,只需要在某个worker触发运行时,从文件系统中加载脚本即可.


我们有一个后台系统,我们可以在这个系统中通过文本编辑器来编写groovy脚本,也可以向每个脚本指定"参数列表",比如,你在groovy脚本中使用了$date参数,那么你可以配置$date = new Date(),不过每个参数,也是通过groovy引擎在运行时计算,并把结果传递给map-reducer脚本的.

最终统计的结果,将会后台系统中指定的存储地址,存储在文件中,并由分拣程序,负责将这些数据存储为结构化的格式,一遍后续的查询和报表设计.
1 楼 feidian1028 2014-04-25  
兄弟,你的数据统计系统设计方案能否介绍下

相关推荐

    java在嵌入运行groovy代码1

    Java 嵌入运行 Groovy 代码是一种常见的技术实践,特别是在需要动态脚本执行或灵活扩展功能的场景中。Groovy 是一种与 Java 兼容的动态编程语言,它的语法简洁,适合编写脚本和快速原型开发。在 Java 应用程序中运行...

    java应用简单嵌入脚本模块

    压缩包中的`Groovy_Script`很可能包含了几个Groovy脚本文件,这些文件可能包含了示例代码,展示了如何在Java应用中嵌入Groovy脚本。文件名可能如`ScriptExample.groovy`,其中包含了具体的脚本代码,如定义函数、...

    java 动态脚本语言 精通 Groovy

    9. **Scripting in Java Applications**:Groovy可以嵌入到Java应用中作为脚本语言使用,例如在服务器端处理HTTP请求,或者作为配置文件来动态改变应用行为。 10. **持续集成与构建工具**:Groovy也是构建工具如...

    groovy脚本语言bin

    此外,Groovy还与Java无缝集成,可以调用所有Java库,并且Java代码也可以直接嵌入到Groovy脚本中。 总之,Groovy脚本语言bin提供的1.6.5版本是一个全面的开发包,旨在支持开发人员在JVM上快速开发、测试和部署...

    第 1 部分: 引入 Java 脚本 API

    这个API在Java 6版本中得到了显著增强,为开发者提供了在Java程序中嵌入和执行脚本引擎的能力,例如JavaScript、Groovy或Rhino等。通过Java脚本API,开发者可以利用脚本语言的灵活性和简洁性,同时保持Java的系统级...

    groovy脚本1

    Groovy是一种动态、灵活的编程语言,它与Java高度兼容并常常被用于简化Java开发,尤其是在脚本编写和...通过`GroovyClassLoader`和`GroovyShell`,开发者可以轻松地将Groovy代码嵌入到Java应用中,实现动态行为和交互。

    java脚本 快速学习

    JSAPI是Java平台标准版(Java SE)的一部分,它允许开发者在Java程序中嵌入并执行脚本语言。JSAPI通过提供ScriptEngine接口,使得开发者能够轻松地调用不同脚本引擎,如JavaScript、Groovy或Rhino,执行脚本代码。...

    groovy-all.jar-生成JasperReport所要包含的包

    5. 嵌入式脚本:由于`groovy-all.jar`的存在,你可以直接在Java应用中执行Groovy脚本,无需额外的配置或环境。 6. 极简的语法:Groovy的语法比Java更简洁,例如,不需要分号和大括号来结束语句和块。 对于...

    groovy-all-1.8.1.jar.zip

    随着Groovy的更新迭代,后续版本提供了更多特性,如GroovyShell、GroovyScriptEngine等,用于运行和解释Groovy脚本,以及 Grape(依赖管理) 和 GPars(并行和并发处理库)等实用工具。 在实际开发中,Groovy常用于...

    在* .docx文档中使用Groovy脚本

    通过阅读并实践"Using-Groovy-scriptlets-inside-a-docx-document.pdf"文档,你将能够掌握如何在Word文档中嵌入和运行Groovy脚本,从而提升你的文档处理体验。无论你是企业用户还是个人开发者,这个技巧都值得你学习...

    Groovy语法系列教程之字符串(三).pdf

    字符串插值允许在字符串中嵌入Groovy表达式,这些表达式在字符串被处理时会被计算并替换为相应的值。插值表达式由${}包围,也可以使用$前缀直接插入表达式。 7. **内插闭包表达式的特殊情况** 在特定情况下,...

    groovy 1.8.6

    此外,Groovy与Java完全兼容,可以无缝地与Java代码混合编写,这意味着你可以在同一个项目中使用Java类和Groovy脚本。 Groovy 1.8.6中的一些关键特性包括: 1. **动态类型**:Groovy默认采用动态类型,这意味着...

    java脚本编程

    - **使用Groovy脚本**:在Java程序中可以通过`groovy.lang.GroovyShell`或`groovy.lang.GroovyClassLoader`来执行Groovy脚本。 #### 六、总结 虽然“Java脚本编程”这个说法可能并不准确,但在Java中确实可以通过...

    groovy基础语法.pdf

    Groovy支持模块化编程,可以通过import语句导入其他Groovy脚本或Java类。同时,Groovy允许以脚本的形式运行代码,这意味着可以快速执行代码片段而无需编写完整的类和方法。 元编程: Groovy提供了强大的元编程能力...

    groovy入门经典,groovyeclipse 插件

    这使得Groovy成为快速开发、脚本编写以及构建自动化等任务的理想选择。 GroovyEclipse是一款强大的Eclipse插件,专门为Groovy开发者设计。它为Eclipse IDE带来了对Groovy语言的全面支持,包括语法高亮、代码提示、...

    groovy经典_资料

    Groovy是一种基于Java平台的动态脚本语言,它在设计时考虑了简洁性和生产力的提升。Groovy之所以受到青睐,主要是因为它比Java更加抽象和高效,允许开发者编写更少的代码来实现同样的功能。以下是对Groovy的一些核心...

    groovy初学者的福音

    4. **GroovyShell与GroovyScriptEngine**:这两个工具允许你在运行时执行Groovy代码,是学习和调试Groovy脚本的好帮手。 5. **GString与字符串操作**:Groovy的GString是一种可以包含表达式的字符串,它允许你在...

Global site tag (gtag.js) - Google Analytics