Category 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做缓存。

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)
  }
}