由“如何更好地配置Module”引发的...

公司有一个服役了N年的基于Java技术实现的业务系统,其中Module的配置比较死板:比如配置某个Module的label,通过更改displayFields和separator来实现,如果displayFields和separator分别设置成“jobCode,nickname,sponsor”和" : ",则label被拼成形如"ABB1-705 : Abbott : Sam"的字符串。问题来了:如果我希望分隔符不是单一的" : "呢?如果我希望显示成"ABB1-705 : Abbott ( sponsored by Sam )"呢?

 

一个最容易想到的办法就是用包含占位符的字符串(跟公式或格式类似)来定义label,比如可以把label设置成"#{jobCode} : #{nickname} ( sponsored by #{sponsor} )",在运行期把具体值代入公式,计算出显示结果,计算的方法如下:

private String assembleLabel(String label, Object bean) {
    label.replaceAll(/#\{.[^\}]+\}/){
        def arr = it[2..-2].split('\\.')
        def r = bean
        for(int j=0;j<arr.length;j++){
            r = r?."${arr[j]}"
        }
        r
    }
}

写出这个方法,自己感觉颇有些得意 :其中的

def r = bean
for(int j=0;j<arr.length;j++){
    r = r?."${arr[j]}"
} 

支持形如#{person.profile.name}的占位符,不光可以处理一层的属性提取,更深层次的object navigation都没问题(我使用Db4o,它的transparent activation特性威力相当惊人)。

 

看起来似乎很完美了,但是我们得记住:There's nothing that lends itself to the “one size fits all” paradigm.

 

设想这样一个scenario: 我有一个名叫Sites and Institutions的Module,它的description告诉软件使用者“标为红色的Site表示其状态为Enrollment,标为灰色表示Declined...”,虽然在Domain SiteStatus中保存的数据不是经常改变的,但一旦改变我们就不得不更新Sites and Institutions的description - 很显然,这样会很“湿” - 严格遵循DRY(Don't Repeat Yourself)原则是good practice

怎样才够DRY?我的做法是,利用Groovy的Closure和BSF。先看Module的定义

class Module {

    String name, icon, label, action
    String description // can be a plain String or a Closure definition script(starts with "GroovyScript:{")

    Class clazz
    Boolean scaffold, prototype
    List children
    Module parent
    String belongsTo
    List sort

    static constraints = {
        name nullable:false
    }

}

这样我就可以这样写了:

Module sites = new Module(
    name: '5it35', 
    description:"""GroovyScript:{o->
      def r = ['<div style="padding:5px;">']
      SiteStatus.findAll(sort:'seq').each{
        r << "<font color=\${it.color}>&#9632; \${it.name}</font><br>"
      }
      r << '</div>'
      r.join('')
    }"""//...
)

同时利用BSF写了个简单的eval来执行script:

  private static final GROOVY_SCRIPT_PATTERN = /^\s*GroovyScript\s*:\s*\{/

  static eval(String s) {
    eval(s, null)
  }

  static eval(String s, Object param) {
    if(s =~ GROOVY_SCRIPT_PATTERN) {
      return getBSFManager().eval("groovy", null, 0, 0, s.replace(GROOVY_SCRIPT_PATTERN,'')).call(param)
    }
    return s
  }

产生的效果是酱紫的:

 

把description写成“GroovyScript: {...}"是为了告诉自定义的解析器:这是个Groovy script,不是普通的String,你需要用BSF来eval. 为什么不把description定义成Object类型,然后给它赋个String或Closure呢?实际上我原来就是这样做的:

def desc = module.description
def result = desc instanceof Closure ? desc() : desc

看起来很美,实践中却出了点小问题:动态更改clousre时无法持久化到Db4o数据库,除非closure是在某个类中写的。我原先在Module类中有直接把description设成一个Closure:

description: {
    def r = ['<div style="padding:5px;">']
    SiteStatus.findAll(sort:'seq').each{
        r << "<font color=${it.color}>&#9632; ${it.name}</font><br>"
    }
    r << '</div>'
    r.join('')
}

它也确实被保存到Db4o的数据库中了(OM中显示了一个名叫com.grs.sctms.Module$_setup_closure1的类):

 

 

但是我在web-based的Grails Console中(服务器端是一GroovyShell在执行我提交的script)执行更改Module description(把它改成另一个Closure)却不能成功。

为了曲线救国,我只能用String类型的description + BSF运行期解析并执行脚本的办法了。实际上这个办法我比较喜欢,因为我可以在不重启应用的情况下,瞬间修改module,同时让修改瞬间生效。我在web-based的Grails Console中执行如下代码:

import com.grs.sctms.*
def oc = ctx.objectContainer
def sites = Module.find(name:'5it35')
println "original value: ${sites.description}"
sites.description  = """
GroovyScript:{o->
   def r = ['<div  style="padding:5px;">']
     SiteStatus.findAll(sort:'seq').each{
       r <<  "<font  color=\${it.color}>&#9632;  \${it.name}</font><br>"
     }
   r  << '</div>'
   r[0..-2].join(':-)')+r[-1]
 }
"""
sites.save()
oc.commit()
println "current value: ${sites.description}"

 浏览器刷新一下,效果立马变成了:

 

 

最后,上一张让我能“操控sctms于万里之外”的web-based Groovy Console的照片(同时感谢这个Grails Plugin的原作者Siegfried Puchbauer and Mingfai Ma,我的Console是在他们提供的源码的基础上修改而得):

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值