Tag Archives: groovy

Groovy的性能分析

最近在继续研究Groovy,同时也在团队内部推动Groovy作为大家学习的第二语言。目前能想到Groovy在应用中的作用有两点:

  1. 利用Groovy语法的简练,用它来写测试用例,减少测试的代码量。
  2. 实现一个动态执行Groovy脚本的引擎,将一些业务规则用Groovy实现,通过对Groovy脚本可动态更改来满足运营的一些需求。

其中第一点其实比较容易搞,也不会有性能问题,第二个需求则需要考虑到性能的问题。针对这个方案,上周六写了一个简单的GroovyEngine用来从数据库中加载Groovy脚本并执行,顺便对其做了一下性能测试。GroovyEngine的核心方法代码如:

public Map< string ,Object> execute(Map< string , Object> context,
       Map< string , Object> params, Source source) {
//  Script script = scripts.get(source.getkey());
//  if (script == null) {
      GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
      Class scriptClass = groovyClassLoader.parseClass(source.getSource(),
         source.getkey());
      Script script = InvokerHelper.createScript(scriptClass,
  groovySpringApplicationContextBinding);
//    if (script != null) {
//      scripts.put(source.getkey(), script);
//    }
//}

  return (Map< string , Object>) script.invokeMethod("execute",
  new Object[]{context, params});
}

其中Source是定义的一个包含Groovy脚本的对象,groovySpringApplicationContextBinding是Binding的一个实例,目的是将spring容器的对象可以被Groovy脚本方法。
测试代码如下:

class TestGroovyExecutor extends BaseTestCase{
    @Autowired
    GroovyExecutor groovyExecutor

    @Test
    public void test_execute() {
        def script = """def execute(Map context,Map param){
                        return ["result":"hello world"]
        }
        """
        def code = new Source(source: script,type: "new",name: "hello")
        def context = new HashMap< string ,Object>()
        def params = ["name":"Groovy"]
        def start = System.currentTimeMillis()
        1000.times{
            def result = groovyExecutor.execute(context, params, code)
        }
        def time = System.currentTimeMillis() - start
        System.out.println("take time: " + time)

    }
}

上面的测试代码运行结果如下:

take time: 23843

这段很短的Groovy脚本大约每次的执行需要23ms。
如果将Script加入缓存,如将第一段代码中的注释都打开,如下:

public Map< string ,Object> execute(Map< string , Object> context,
       Map< string , Object> params, Source source) {
  Script script = scripts.get(source.getkey());
  if (script == null) {
      GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
      Class scriptClass = groovyClassLoader.parseClass(source.getSource(),
         source.getkey());
      script = InvokerHelper.createScript(scriptClass,
  groovySpringApplicationContextBinding);
    if (script != null) {
      scripts.put(source.getkey(), script);
    }
  }
}

这次将执行次数加大到100000次,测试执行结果如下:

take time: 1678

每一次执行时间少于0.1ms,这个执行效率已经可以接受了。

测试的最终结论是要利用Groovy的动态特性来实现一些需要动态变化的业务需求,如果性能上有要求的话,必须对Script做缓存。

Java,Groovy实现python中的Decorator

昨天去参加杭州一些朋友组织的圆桌技术交流,鱼哥 @nnfish分享了一个关于python的话题
,另外一位兄弟分享了一下Vim的一些使用心得,最后升哥分享了Redhat Linux安装过程背
背后所执行的逻辑。三个分享都给我不少的收获,经常参与这样的交流是学习的最佳途径。
鱼哥的分享中提到python中的一个Decorator的特性,这个特性和我最近在看的一篇文章中
提到的有异曲同工之秒。 如下是Java的实现:
public void wrapInTransaction(Command c) throws SQLException {
setupDataInfrastructure();
try {
c.execute();
completeTransaction();
} catch (RuntimeException ex) {
rollbackTransaction();
throw ex;
} finally {
cleanUp();
}
}
public void addOrderFrom(final ShoppingCart cart, final String userName,
final Order order) throws SQLException {
wrapInTransaction(new Command() {
public void execute() throws SQLException{
add(order, userKeyBasedOn(userName));
addLineItemsFrom(cart, order.getOrderKey());
}
});
}
再让我们看看groovy是如何做的:
public class OrderDbClosure {
def wrapInTransaction(command) {
setupDataInfrastructure()
try {
command()
completeTransaction()
} catch (RuntimeException ex) {
rollbackTransaction()
throw ex
} finally {
cleanUp()
}
}
def addOrderFrom(cart, userName, order) {
wrapInTransaction {
add order, userKeyBasedOn(userName)
addLineItemsFrom cart, order.getOrderKey()
}
}
}
最近我们再来看看python中通过Decorator实现的代码:
def transaction(func):
def _wrapper(*args,**kwargs):
setupDataInfrastructure()
func(*args,**kwargs)
completeTransaction()
return _wrapper
@transaction
def addOrderFrom(cart,userName,order):
add(order,userKeyBasedOn(userName))
addLineItemsFrom(cart,order.getOrderKey())

昨天参加了杭州一些朋友组织的圆桌技术交流。鱼哥 @nnfish分享了一个关于python的话题,另外一位兄弟分享了一下Vim的一些使用心得,最后升哥分享了Redhat Linux安装过程背后所执行的逻辑,明白了自己定制Linux时的基本原则。三个分享都有收获,经常参与这样的交流是学习的最佳途径。

鱼哥的分享中提到python中的一个Decorator的特性,这个特性和我最近在看的一篇文章《组合方法与SLAP》中讲述SLAP提到的设计有异曲同工之秒。 如下是Java的实现,使用了匿名内部类来包装要执行的逻辑:

public void wrapInTransaction(Command c) throws SQLException {

    setupDataInfrastructure();

    try {
        c.execute();
        completeTransaction();

    } catch (RuntimeException ex) {
        rollbackTransaction();
        throw ex;
    } finally {
        cleanUp();
    }
}

public void addOrderFrom(final ShoppingCart cart, final String userName,final Order order) throws SQLException {

    wrapInTransaction(new Command() {
        public void execute() throws SQLException{
            add(order, userKeyBasedOn(userName));
            addLineItemsFrom(cart, order.getOrderKey());
        }
    });

}

再让我们看看文中提到的使用groovy是如何做的:

public class OrderDbClosure {

    def wrapInTransaction(command) {
         setupDataInfrastructure()

         try {
             command()

             completeTransaction()
         } catch (RuntimeException ex) {
             rollbackTransaction()
             throw ex
         } finally {
             cleanUp()
         }
    }

    def addOrderFrom(cart, userName, order) {

        wrapInTransaction {
            add order, userKeyBasedOn(userName)
            addLineItemsFrom cart, order.getOrderKey()
        }

    }

}

Groovy的做法明显比Java简洁了,不过对业务代码的侵入性还是存在的。

最后再来看看python中通过Decorator 实现上述设计的代码(下面代码只为阐述Decorator):

def transaction(func):
    def _wrapper(*args,**kwargs):
        setupDataInfrastructure()
        func(*args,**kwargs)
        completeTransaction()
    return _wrapper

@transaction
def addOrderFrom(cart,userName,order):
    add(order,userKeyBasedOn(userName))
    addLineItemsFrom(cart,order.getOrderKey())

也可能通过如下方法调用:

transaction(addOrderFrom(cart,userName,order))

从上面三个例子可以看出语言层提供的特性深深影响着开发者的思唯方式,也这是为什么如何成为优秀的程序员的书中提倡开发者学会多种语言。目前正努力学习Python中,之前看这的Groovy又都快忘记了,要熟悉工作之外的编程语言并不容易。

Groovy生成Spring配置文件的脚本

最近在学习Groovy,发现它提供的XML操作API非常方法。在看《卓有成效的程序员》一书时,有一节是通过用Groovy读取数据库中表的MetaData生成对应的BeaniBatis文件,其中那段Groovy代码让我印象深刻。最近在看Groovy Recipes时刚好也看到了XML这一段,于是想着自己也尝试写个脚本来自生成配置文件这类非常繁琐的事情。

最近在开发时发现在写完一个Bean之后总要手动去更新Spring的配置文件,而且更新时经常来回拷贝,虽然有用多剪切板的功能,但是仍然觉得很繁琐。于是就有了将写一个自动生成更新配置文件的脚本的想法。刚好Groovy友好的XML API能胜任这一项工作。

因为公司里面写代码有很多的模式化的东西,因为在处理如何找到这些Bean简单,并不需要去考虑很多扩展性的问题。这个脚本的执行过程如下:

  1. 搜索特定目录列表下的类文件,根据统一的命名格式,确认这个类是不是一个Bean
  2. 生成Bean的名称及对应的Class全名的Map
  3. 针对Map中的数据生成Bean的配置文件

在写Groovy脚本时,用的是Intellij IDEA,提供语法高亮的支持,代码补全功能太弱,于是只能不断地查API文档。历经三个小时,终于写成了这个脚本,初步测试,可用性尚可。(发现我是那种没有IDE支持就啥也写不出来的烂程序员,伤心啊:()

下面是Groovy生成XML的代码,因此需要加入XML声明及DocType,因此选择了StreamingMarkupBuilder来生成XML,这个类生成的文件有些难看,不过在Eclipse下格式化一下,就美观了。生成XML的代码如下:

import java.util.regex.Pattern
import groovy.xml.StreamingMarkupBuilder

class XmlConfigBuilder {
  static final DIRS = ["manager": "biz/core/src/java/",
          "dao": "biz/dal/src/java/",
          "ao": "biz/home/src/java/",
          "xml": "bundle/war/src/webroot/WEB-INF/biz/bean"]

  def ao = [:]
  def manager = [:]
  def dao = [:]

  def findClasses(projPath) {
    File aoFile = new File(projPath, DIRS.ao)
    aoFile.eachFileRecurse {file ->
      if (file.name.matches("Default[\\w]*AO.java")) {
        def classPath = file.getPath().minus(aoFile.getPath())
        ao.put(beanName(file.name), fullClassName(classPath))
      }

    }

    File mfile = new File(projPath, DIRS.manager)
    mfile.eachFileRecurse {file->
      if (file.name.matches("Default[\\w]*Manager.java")) {
        def classPath = file.getPath().minus(mfile.getPath())
        manager.put(beanName(file.name), fullClassName(classPath))
      }

    }

    File daoFile = new File(projPath, DIRS.dao)
    daoFile.eachFileRecurse {file ->
      if (file.name.matches("Ibatis[\\w]*DAO.java")) {
        def classPath = file.getPath().minus(daoFile.getPath())
        dao.put(beanName(file.name), fullClassName(classPath))
      }
    }
  }

  def beanName(fileName) {
    fileName.substring(7, 8).toLowerCase() +
    fileName.substring(8,fileName.length()-5)
  }

  def fullClassName(filePath) {
    filePath.substring(1,filePath.length()-5).replaceAll(Pattern.quote(File.separator),
    ".")
  }

  def doctype = """< !DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">"""
  def buildConfigXML(path) {
    findClasses(path)

    buildXml(path, "biz-ao.xml", ao)
    buildXml(path, "biz-dao.xml", dao)
    buildXml(path, "biz-manager.xml", manager)

  }

  private def buildXml(path, filename, map) {
    def xmlWriter = new FileWriter("${path}/${filename}")
    def xmlBuilder = new StreamingMarkupBuilder()
    xmlBuilder.encoding = "GB2312"

    def beans = {
      mkp.xmlDeclaration()
      unescaped < < doctype
      beans("default-autowire": "byName") {
          map.each {k, v ->
          bean(id: k, class: v)
        }
      }
    }
    xmlWriter < < xmlBuilder.bind(beans)
  }
}

初试Grovvy

因为接下来的工作中可能会用到Groovy,于是最近开始了解一下。主要看的书是Groovy Recipes,这本书非常不错。从装上到现在还不到一周,目前只写过一些简单的例子,记录一下学习过程中的体会。
由于之前学过一点Python,最近又刚系统地看了一下JavaScript语言,所以在看Groovy时感觉许多特性与它们两者非常相似。在看Groovy过程中没有遇到什么困难,因为Groovy全面支持Java API,对于Java程序员来说学习曲线很平,只要了解其中的语法,就可以开始写了。Groovy中许多的特性弥补了Java的缺憾,或者是Java中设计得不好的地方,在看的过程中也能了解到Java语言存在的问题。
与Java比较了一下,总结一下Groovy的特点:

  1. 内置的List 和Map 支持,并提供类似Python中的List及Map一样的特性,操作起来更加方便了;
  2. 支持闭包,在处理一些事情时不再需要Java中的内部类这种很Ugly的方法来模拟闭包了;
  3. 不再区分checked Exception和unchecked Exception,groovy后台处理这些这些烦人的东西,其实Java异常体系的设计一直被人诟病。
  4. 在Object这个类上实现了each方法,比Java中的For in更方便。
  5. 封装了一些Java API的类,这些些类增加了更多有趣且有用的方法。
  6. 内置 正则表达式 的支持
  7. 作为动态语言,具体动态语言的一切特征,比如说执行期才进行类型检查等
  8. Groovy在后台为field添加Setter和Getter,不再需要用IDE生成那些看起来不美观的代码了。
  9. 支持运算符重载,更容易实现DSL;
  10. Truth的判断与Javascript中类似,自动处理NullPointerException问题,不用写那些烦人的 if( xx != null)之类的判断了;
  11. GString,比Java中的String有用得多,不再需要用StringBuilder或者+号等拼装字符串了;
  12. 支持Range操作,和Python中的Range类似;
  13. 支持可选参数,和Python类似,避免了Java中的那些不必要的方法重载(如,两个方法只是参数数量不一样);
  14. 支持直接运行Shell脚本,但是对Shell的通配符支持有点难看,一种程序上可以不用学Bash那有点怪异的语法了;
  15. 与Java完全兼容,现有的Java代码在Groovy全部可用…

还有许多其它的一些东西没有列出来。总的来讲Groovy作为一门语言比Java要优秀许多,毕竟它的诞生就是为了弥补Java的不足。Groovy借鉴了其它优秀动态语言的许多优点,比如它的很多语法特性就是照抄Python和JavaScript的。这也使得它的学习曲线很平,不像Scala那些难学,这也是它比Scala更让人接受的原因之一吧。