`
mozhenghua
  • 浏览: 318968 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Groovy动态性在项目中实践

阅读更多

      最近对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字段拆分成两个字段createyearcreatemonth存放到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”类型的字符串。

       GroovyImplProcess1GroovyImplProcess2是用来封装的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的动态性在实际项目中应用的最简单的例子,希望能够起到抛砖引玉的作用。

 

 

 

 

 

 

 

  • 大小: 125.2 KB
分享到:
评论
2 楼 Alan_aha 2016-03-08  
楼主能否共享一下第三种的完整代码?
1 楼 LinApex 2014-12-14  
一直在使用,很爽的说。

相关推荐

Global site tag (gtag.js) - Google Analytics