`
mozhenghua
  • 浏览: 324602 次
  • 性别: 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  
一直在使用,很爽的说。

相关推荐

    java 动态脚本语言 精通 Groovy

    Groovy是一种基于Java平台的动态脚本语言,它在Java开发者中越来越受欢迎,因为它提供了简洁、灵活的语法,以及强大的动态编程能力。Groovy与Java兼容性极佳,可以直接调用Java类库,使得它在Java生态系统中具有广泛...

    Groovy入门]第一讲.项目演示与搭建Groovy开发环境

    项目演示通常会涵盖一个具体的应用场景,例如自动化测试、数据处理或Web应用开发,通过实例帮助初学者快速理解Groovy在实际工作中的应用。 接下来,搭建Groovy开发环境是学习过程中的关键步骤。你需要先安装Java ...

    groovy+spring在web中的应用

    通过研究这些示例,读者可以更好地理解Groovy在Spring环境中的工作原理和最佳实践。 总的来说,Groovy与Spring的结合为Web开发提供了一种新的可能性,它使得开发过程更加高效、灵活,并且富有表现力。无论是从配置...

    groovy

    这种互操作性使得在现有Java项目中逐步引入Groovy成为可能,而不必重构整个项目。 五、学习资源 "Groovy的帮助文档"是学习Groovy的重要参考资料,它包含了语言的所有细节和用法,包括语法、类库、工具和最佳实践。...

    groovy programming(2006.12).pdf

    Groovy的动态性体现在其强大的元编程能力上。书中详细解释了如何利用Groovy的元编程特性来动态地生成或修改代码,这对于构建灵活的框架和工具非常有用。例如,书中可能涵盖了如何使用AST(抽象语法树)转换来修改...

    groovy入门经典,groovyeclipse 插件

    Groovy是一种动态、灵活的编程语言,它是Java平台上的一个扩展,可以无缝集成到Java项目中。Groovy的语法简洁,支持面向对象编程、函数式编程,并提供了许多现代语言特性,如闭包和动态类型。这使得Groovy成为快速...

    groovy学习

    Groovy是一种动态、开源的编程语言,它设计用于在Java平台上运行,并且与Java高度兼容。...通过深入学习和实践,开发者可以充分利用Groovy的优势,提升工作效率,特别是在处理动态性和灵活性要求较高的任务时。

    实战groovy.rar

    6. 深入理解Groovy与Java的互操作性,以便在既有Java项目中无缝引入Groovy。 五、实战项目实践 通过实际项目,将理论知识应用于解决具体问题,如构建自动化脚本、编写简单的Web应用、实现DSL等,以加深对Groovy的...

    Groovy学习资料

    Groovy是一种动态、灵活的编程语言,它是Java平台上的一个强类型、...通过深入学习和实践这份"Groovy学习资料"中的内容,开发者将能够熟练掌握Groovy语言,并能够将其运用到实际项目中,实现项目的高效开发和顺利上线。

    最新 groovy开发包

    书中可能涵盖Groovy的基础概念、进阶特性、最佳实践,以及如何在实际项目中应用Groovy。 总的来说,这个最新的Groovy开发包是学习和精通Groovy的宝贵资源,无论是为了个人兴趣还是职业发展,都能从中受益匪浅。通过...

    hugo110-java_run_groovy-master_java_

    综上所述,这个项目可能是为了帮助开发者理解和实践如何在Java项目中集成和运行Groovy,以便更好地利用Groovy的动态性和简洁性,同时为实现定时任务和动态代码执行场景做准备。通过学习和实践这个项目,你可以掌握...

    Groovy学习资料(包括Groovy in action 的中英文以及API)

    Groovy是一种动态、灵活的编程语言,它是Java平台上的一个开放源代码项目,设计用于增强开发者的生产力。Groovy结合了面向对象编程、函数式编程和动态语言的特性,使得编写简洁、高效的代码成为可能。Groovy in ...

    Groovy in Action源代码

    2. **动态性**:Groovy的动态性体现在运行时类型检查、方法的动态调用等方面。源代码会展示如何在不明确类型的情况下编写代码,以及如何利用闭包(Closure)进行函数式编程。 3. **元编程**:Groovy的元编程能力让...

    groovy in action_中文

    Groovy的动态性允许在运行时修改代码,这对于测试和快速原型设计非常有用。此外,Groovy能够无缝集成Java代码,使得已有Java项目可以逐步引入Groovy特性。 2. **Android与Gradle**:在Android开发中,Gradle是主要...

    groovy_demo

    Groovy是一种动态、开源的编程语言,它与Java平台紧密集成,可以被用作脚本语言或构建完整的应用程序。在"groovy_demo"项目中,我们将会...通过实践这些示例,你将能更好地掌握Groovy,并能够将其应用到实际项目中。

    spring-beans-groovy源码

    《深入剖析Spring Beans与Groovy集成的源码》 在软件开发中,Spring框架以其强大的依赖注入和面向切面编程特性,成为了Java企业级应用的事实标准。...在实践中,这将有助于提升开发效率,实现更加灵活的架构设计。

    精通 Groovy 中文教程

    - **互补关系**:Groovy不仅能在Java项目中与Java代码无缝协作,还能扩展和增强现有的Java应用程序。 **Groovy的优点**: - **简洁性**:相比Java,Groovy的代码量更少,这得益于其语法上的优化和简化。 - **灵活...

    Groovy in Action[文字版][中文]

    通过阅读《Groovy in Action》这本书,你可以深入了解Groovy的这些特性和实践应用,提升你的开发技能,同时也能更好地理解如何在实际项目中利用Groovy的优势,提高开发效率和代码质量。书中的例子和实战指导将帮助你...

    java在嵌入运行groovy代码1

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

Global site tag (gtag.js) - Google Analytics