由“如何更好地配置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}>■ ${it.name}</font><br>" } r << '</div>' r.join('') }"""//... ) 同时利用BSF写了个简单的eval来执行script: private static final GROOVY_SCRIPT_PATTERN = /^s*GroovyScripts*:s*{/ static eval(String s) { eval(s,null) } static eval(String s,Object param) { if(s =~ GROOVY_SCRIPT_PATTERN) { return getBSFManager().eval("groovy",null,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}>■ ${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}>■ ${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是在他们提供的源码的基础上修改而得): ? (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |