最近对Groovy语言全面地学习了一下,语言本身因为加入了闭包,自动生成property机制,让其有别于Java,代码本身可以写得比较精简。Groovy具有很多Java所不具备的特性,其中最有意思的特性之一就是动态性。
说到动态语言最长使用的就是Javascript,在代码中可以使用eval函数动态拼接字符串,组装执行函数。
这里我向大家介绍一个在项目中使用Groovy语言动态性的实际案例,在案例中一开始由于没有经验没有合理使用Groovy的脚本,走了弯路,一并介绍,希望大家也避免犯同样的错误。
先说说需求,我负责的终搜导入、查询客户端代码,其中有一个模块负责从业务方系统中数据库中导入数据到终搜的分布式文件系统的代码,导入过程需要对记录中的日期进行格式化处理,下面截取一段遍历数据表的代码:
Connection conn = null; PreparedStatement statement = conn .prepareStatement("select col1,col2,createTime from tabA"); ResultSet result = statement.executeQuery(); List<Map<String, String>> resultList = new ArrayList<Map<String, String>>(); Map<String, String> row = null; if (result.next()) { row = new HashMap<String, String>(); row.put("col1", result.getString(1)); row.put("col2", result.getString(2)); row.put("createTime", result.getString(3)); resultList.add(row); }
以上这段代码中map中保存的createTime是“yyyy-MM-dd HH:mm:ss”这样的格式的,在放到map中将时间格式化成“yyyyMMddHHmmss”于是会将代码改成如下:
Connection conn = null; PreparedStatement statement = conn .prepareStatement("select col1,col2,createTime from tabA"); ResultSet result = statement.executeQuery(); List<Map<String, String>> resultList = new ArrayList<Map<String, String>>(); Map<String, String> row = null; if (result.next()) { row = new HashMap<String, String>(); row.put("col1", result.getString(1)); row.put("col2", result.getString(2)); Pattern p = Pattern .compile("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}"); Matcher m = p.matcher(result.getString(3)); if (m.matches()) { row.put("createTime", m.group(1) + m.group(2) + m.group(3) + m.group(4)+ m.group(5) + m.group(6)); } resultList.add(row); }
利用正则式匹配的方式将createtime的时间字段格式化(当然可以利用SimpleDateFormat来格式化日期字段)。
修改完代码重新发布上线,可以很好地工作了,但是作为一个平台化的产品仅仅做到这一步还是不够的,还需要考虑是否可以将这端代码通用化,比如,下次业务方想在导出用户数据同时,还需要通过每条记录上的userid到会员中心中取用户的相关属性插入到map对象中,再或者需要将之前用户的createtime字段拆分成两个字段createyear和createmonth存放到map中,一句话总结这个需求就是“实现导出字段扩展”。如果每次提出类似的需求后都需要用硬编码的方式来实现,需要开发时间还不错,而且还需要重新部署。
面向对象设计中的OCP原则(对于修改封闭,对于扩展开放),对于前面提出的需求即要做到在不修改现有代码的前提下,如何将扩展的字段的功能方便地添加进去。
XML配置的方式,就能很好地实现这个需求,即将需要如何扩展字段的规则配置进xml中,但是问题又来了,我们怎么去配置这个规则呢?早先我们实现过一个方案,在XML配置如下一段脚本:
<bean id="dateFormaterProcessor" class="com.taobao.terminator.indexbuilder.doc.DateFormater" init-method="init"> <property name="formats"> <map> <entry key=" createTime " value="yyyy-MM-dd hh:mm:ss -> yyyyMMddhhmmss"></entry> </map> </property> </bean>
配置化后,通过解析“yyyy-MM-dd hh:mm:ss -> yyyyMMddhhmmss”这个规则,在DateFormater这个类中可以处理将createTime时间格式化的需求,但是,字段扩展不仅仅只限制在日期格式化这样一种类型的字段扩展,还有其他格式各样扩展种类。
那么如果将这种扩展彻底地开放,能够在配置文件中配置呢?将代码成为一种规则配置到配置文件中,因此我们就想到使用Groovy的脚本来配置这种规则,在运行时解析Groovy定义的规则,在处理结果集时回调这个Groovy脚本规则。
这里先讲讲在JAVA中如何与Groovy协作的问题, 在JAVA中有三种与Groovy协作的方式,具体如下:
1. 直接将Groovy编译成字节码
Groovy是执行在JVM虚拟机上的,最终是需要编译成JVM字节码,事先可以通过工具编译成字节码并且打成Jar包,依赖到当前JAVA运行环境的上下文中来执行,因为我们需要将脚本配置化,是要运行时来编译脚本,所以这种预编译的方式不符合我们的需求场景。
<!--[if !supportLists]-->2.
2 利用GroovyShel这种方式在代码中直接运行一段groovy的脚本的方式来运行,并且返回运行结果给调用端,代码如下:
import groovy.lang.GroovyShell; public class GroovyShellExample { public static void main(String args[]) { Map<String,String> record ; Binding binding = newBinding(); binding.setVariable("record ", record); GroovyShell shell = newGroovyShell(binding); Object value =shell.evaluate("return formatDate(record[‘createTime’]);"); Record.put(“createTime”, value); } }
3 使用GroovyClassLoader
上一种基于shell的方式,会导致频繁地fullgc,终其原有就是整个执行生命周期中只需要执行一次的流程”编译字节码-----》加载内存-----》create Object” 却被执行了N次,所以为了避免fullgc,选择了GroovyClassLoader预加载的方式来实现。将需要利用Java接口来向调用者隔离。通过接口的实现类将groovy脚本进行封装。
首先定义IProcess接口,接口中只有一个process方法,参数是数据库表中一条记录封装而成的一个map对象,个process对象对应的是一个结果集扩展规则。
在AbstractProcess类中,可以添加一些处理数据的util方法,可以根据需要添加多个。先这里加了一个fmtStrTime方法,可以对“yyyy-MM-dd HH:mm:ss”格式的时间字符串格式转化成“yyyyMMddHHmmss”类型的字符串。
GroovyImplProcess1和GroovyImplProcess2是用来封装的Groovy执行脚本的类。
先介绍一下定义数据库字段的扩展规则,如下在Xml中定义两个规则:
<alias name="birthday" > return record['birthday'][4..7]; </alias> <alias name="mobile_type" > return record['mobile'][0..2]; </alias>
birthday是截取生日的月日部分内容,mobile_type是截取手机号码的前三位(这里忽略如何解析xml的内容)。
下面这段代码是在封装一条扩展规则:
String className = "AliasFieldProcess" + this.getColumn(); String script = " package com.taobao.tsearcher ;" + "import java.util.Map; class " + className + " extends com.taobao.terminator.wangjubao.jingwei.impl.AliasProcessImpl {" + " @Override" + " public Object process(Map<String, String> record) {" + this.getGroovyScript() + " }" + "}"; loader.loadMyClass(tabName + this.getColumn(), script); Class<?> groovyClass = loader.loadClass("com.taobao.tsearcher." + className); process = (IProcess) groovyClass.newInstance();
getGroovyScript()方法返回的是alias标签中定义的body content。经过组装返回了IAliasProcess对象实例,对于调用者来说Groovy的实现细节是透明的。
上面loader对象所对应的类就是以下继承于GroovyClassLoader的AliasGroovyClassLoader类
import groovy.lang.GroovyClassLoader; import org.codehaus.groovy.control.CompilationUnit; import org.codehaus.groovy.control.Phases; import org.codehaus.groovy.control.SourceUnit; public class AliasGroovyClassLoader extends GroovyClassLoader { public void loadMyClass(String name, String script) throws Exception { CompilationUnit unit = new CompilationUnit(); SourceUnit su = unit.addSource(name, script); ClassCollector collector = createCollector(unit, su); unit.setClassgenCallback(collector); unit.compile(Phases.CLASS_GENERATION); for (Object o : collector.getLoadedClasses()) { setClassCacheEntry((Class<?>) o); System.out.println(o); } } }
返回到最前面遍历resultSet结果的代码,稍加改动就行:
Connection conn = null; PreparedStatement statement = conn .prepareStatement("select col1,col2,createTime from tabA"); ResultSet result = statement.executeQuery(); List<Map<String, String>> resultList = new ArrayList<Map<String, String>>(); Map<String, String> row = null; if (result.next()) { row = new HashMap<String, String>(); row.put("col1", result.getString(1)); row.put("col2", result.getString(2)); row.put("createTime", result.getString(3)); //add for extend property start List<IProcess> processList = getProcess(); for(IProcess p: processList){ p.process(row) } //add for extend property end resultList.add(row); }
总结
有了groovy可以在Xml定义中可以定义任何规则,因为现在规则也是可执行代码,这个和以前定义的规则完全是两回事,以前定义一个规则,例如:<entry key=" createTime " value="yyyy-MM-dd hh:mm:ss -> yyyyMMddhhmmss"></entry>必须同时为这个规则写一个解析规则的代码,扩展功能之前必须先对已有的代码进行修改才行,真是挺麻烦的。
当使用groovy脚本之后,代码即规则,可以对现有程序做任意扩展,没有任何限制。
java是一个静态语言,当项目中引入了Groovy之后在语言特性上对java作了很好的补充,可以有很多新的玩法。本文中对Groovy的动态性在实际项目中应用的最简单的例子,希望能够起到抛砖引玉的作用。
相关推荐
软件项目的可扩展性和动态维护性是非常重要的,例如在微服务的网关动态过滤器中,要实现过滤器的动态更新而不影响项目的持续运行,就需要借助groovy的动态编译功能,而这个功能是通过 .groovy文件实现的。而在微服务...
java 动态脚本语言 精通 Groovy
Java调用Groovy,实时动态加载数据库groovy脚本,java读取mongoDB的groovy脚本,加载实时运行,热部署
groovy 敏捷 开发 动态 语言 急速 web 应用 开发
动态加载指定目录下的groovy脚本,并将其注册为groovy bean,放置于ApplicationContext容器中,并使用命名空间进行分类区分(一个namespace对应于一个ApplicationContext)。同时能够动态感知到groovy脚本的新增、修改...
Groovy在Spring中的简单使用,欢迎下载!
NULL 博文链接:https://genius.iteye.com/blog/620029
Beginning_Groovy_and_Grails_From_Novice_to_Professional开源项目教学代码配合Beginning_Groovy_and_Grails教材使用全是原代码开源。由于上传限制分8个部分
赠送jar包:groovy-3.0.9.jar; 赠送原API文档:groovy-3.0.9-javadoc.jar; 赠送源代码:groovy-3.0.9-sources.jar; 赠送Maven依赖信息文件:groovy-3.0.9.pom; 包含翻译后的API文档:groovy-3.0.9-javadoc-API...
Java中使用Groovy的三种方式,详细见我的博客。
eclipse中的groovy插件 eclipse中的groovy插件 实用
JUN SpringBoot API Service 是一个基于SpringBoot+Groovy+SQL动态生成API并动态发布,且发布后可动态执行groovy脚本及SQL脚本的API服务项目。提供在线执行动态程序脚热加载本及动态生成API并执行的功能。支持动态...
groovy快速入门指南(中文),想了解groovy的朋友可以下载看看
Groovy入门]第一讲.项目演示与搭建Groovy开发环境 还有第二讲,可能有点大床不上来了,尽力
Groovy入门经典 中英文版本,包含以下文件: Groovy Program.pdf Groovy入门经典.pdf 图书链接:http://product.china-pub.com/36984
Groovy学习资料-中文.rar。Groovy学习资料。Grails学习资料。mht网页格式。
Groovy 调用 Java 类groovy 调用 Java class 十分方便,只需要在类前导入该 Java 类,在 Groovy 代码中就可以无缝使用该
软件开发设计:应用软件开发、系统软件开发、移动应用开发、网站开发C++、Java、python、web、C#等语言的项目开发与学习资料 硬件与设备:单片机、EDA、proteus、RTOS、包括计算机硬件、服务器、网络设备、存储设备...
自己总结的metaClass和ExpandoMetaClass的基本使用方法,代码量虽不到但是我觉得很有用处。
NULL 博文链接:https://zw7534313.iteye.com/blog/488520