【问题标题】:Hooking into Grails Domain object save()挂钩到 Grails 域对象 save()
【发布时间】:2010-12-29 16:36:34
【问题描述】:

我正在编写一个 grails 插件,我需要连接到域 save() 方法以在保存后执行一些逻辑。我需要跨多个域类执行此操作。在插件用户未将休眠与 GORM 一起使用的情况下,我试图避免休眠事件。

我尝试了很多事情,但以下是我认为应该有最好的工作机会的事情。在所有情况下,grailsS​​ave 都是 null。我该怎么做?

def doWithDynamicMethods = { ctx ->
    application.domainClasses.each { dc ->
        def grailsSave = dc.metaClass.pickMethod('save', [Map] as Class[])

        domainClass.metaClass.save = { Map params ->
        grailsSave.invoke(delegate, [params] as Object[])
        println "Saved object, now do my thing"
        //...
        }
    }
}

我的 *Plugin.groovy 类中有以下设置:

def dependsOn = [domainClass: '1.1 > *', hibernate: '1.1 > *']
def loadAfter = ['hibernate']

【问题讨论】:

    标签: grails groovy grails-plugin


    【解决方案1】:

    额外的 GORM 方法在第一次调用它们中的任何一个时都会延迟初始化。 要在 doWithDynamicMethods 中初始化它们,只需调用域类上的静态方法之一:

    def doWithDynamicMethods = { ctx ->
    
        application.domainClasses.each { dc -> 
    
            // call any static method to initialize dynamic gorm methods
            dc.clazz.count()
    
            def grailsSave = dc.metaClass.pickMethod('save', [Map] as Class[])
            //...
        }
    }
    

    您的 save() 方法现在可用。由于这是在启动时调用的,因此单次计数应该不是什么大问题。

    【讨论】:

      【解决方案2】:

      这是一个过早优化的问题:旧版本的 Groovy 严重惩罚了 MetaClass 重整,因此 GORM 在检测到需要之前不会添加所有魔力。

      最简单的解决方案是让您的插件依赖 GORM Labs(我在那里解决它)。另一种解决方案是手动触发 methodMissing (这将重复我所做的工作)。请参阅 GORM Labs 文档了解我是如何做到这一点的。

      【讨论】:

        【解决方案3】:

        在插件/应用程序初始化期间,我无法成功获取对 save() 方法的引用;我不知道为什么。相反,我决定在插入、更新和删除之后为休眠事件创建一个侦听器。这篇由 Sean Hartsock 撰写的关于 Audit Logging 插件的 post 是一个完美的入门指南。

        这是 Listener 的要点:

        class MyListener implements PostInsertEventListener, PostUpdateEventListener, PostDeleteEventListener, Initializable {
        
                public void onPostInsert(final PostInsertEvent event) {
                    // logic after insert
                    return
                }
        
                public void onPostUpdate(final PostUpdateEvent event) {
                    // logic after update
                    return
                }
        
                public void onPostDelete(final PostDeleteEvent event) {
                    // logic after delete
                    return
                }
        
        
                public void initialize(final Configuration config) {
                    return
                }   
            }
        

        然后在 *GrailsPlugin.groovy 中:

        def doWithApplicationContext = { applicationContext ->
        
            // add the event listeners for reindexing on change
            def listeners = applicationContext.sessionFactory.eventListeners
            def listener = new MyListener()
        
            ['postInsert', 'postUpdate', 'postDelete'].each({
               addEventTypeListener(listeners, listener, it)
            })
        
        }
        
        
        // copied from http://hartsock.blogspot.com/2008/04/inside-hibernate-events-and-audit.html
        private addEventTypeListener(listeners, listener, type) {
            def typeProperty = "${type}EventListeners"
            def typeListeners = listeners."${typeProperty}"
        
            def expandedTypeListeners = new Object[typeListeners.length + 1]
            System.arraycopy(typeListeners, 0, expandedTypeListeners, 0, typeListeners.length)
            expandedTypeListeners[-1] = listener
        
            listeners."${typeProperty}" = expandedTypeListeners
        }
        

        在一天结束时相当简单......

        【讨论】:

        • Shawn 和他的 Audit Logging 插件非常棒。
        • 感谢分享!我一直懒得自己去查。
        【解决方案4】:

        看看Falcone Util 插件。该插件允许您连接到 Hibernate 事件(请参阅页面底部的文档)。我不知道这是否正是你想要的,但你可能会得到一些提示。

        附!我不认为该插件适用于 Grails 1.2。

        【讨论】:

        • 谢谢伙计,那个插件看起来很棒。如果这种类型的事件功能在核心中可用,那就太棒了。
        【解决方案5】:

        元类中添加了三种不同版本的保存,

        save(Map)
        save(Boolean)
        save()
        

        您在测试中调用的是哪一个?您需要为每个代码添加代码。

        要检查的另一件事是你的插件是否在 hibernate 插件之后运行,它将三个方法添加到 metaClass

        干杯

        【讨论】:

        • 谢谢李,我想我在休眠后强制插件加载时遇到了问题。如果我在 doWithDynamicMethods 中运行,所有调用 pickMethod 的尝试都是空的。如果我在 grails 控制台中运行,它们不为空。我的插件中有这个但没有运气: def dependsOn = [domainClass: '1.1 > *', hibernate: '1.1 > *'] def loadAfter = ['hibernate']
        • 嗯 loadAfter 是你想要的 - 是否需要静态 loadAfter = ['hibernate']
        • static 也不起作用。奇怪的是,我刚刚注意到 Robert Fischer 曾尝试在他的 GORM Labs 插件中使用 save() 来做类似的事情,但在以前的版本中已将代码注释掉并且在最近的版本中不存在。有关如何以不同方式处理此问题的任何想法?
        • Hibernate 拦截器怎么样?我不太了解 API,但它可能提供插入全局拦截器/侦听器的能力?
        • 这就是我最终要做的,为 PostInsertEventListener、PostUpdateEventListener 和 PostDeleteEventListener 创建一个监听器。我尝试了所有必须包装 save() 方法并利用 afterUpdate() 类型事件的方法,但是在深入挖掘之后发现所有这些逻辑无论如何都依赖于休眠,所以我不妨去休眠侦听器路线。谢谢李。
        【解决方案6】:

        这不是最好添加到拥有该工作单元的服务类中吗?这就是通常的 Spring/Grails 习惯用法具有这种逻辑的地方。你根本不需要修改存档。

        【讨论】:

        • 虽然我正在构建一个供其他人使用的插件。我的逻辑很可能在服务中,但它需要触发的点是保存。谢谢
        • 将一堆与域直接相关的逻辑委托给另一层的想法是 Spring 中最大的烦恼之一,这也是 Java 以迷恋复杂性而闻名的原因。
        • 单个域对象如何知道它何时是更大工作单元的一部分,何时不是?
        • If 它实际上是概念上更大的工作单元的一部分,并且 if 没有“拥有”域对象,那么放弃它就可以了进入服务。但是用“创建服务!”来回答“我想扩展‘保存’对域对象的意义”。是 hack,而不是答案。
        • “拥有”域对象?那是什么意思?黑客?您的意见,仅此而已。
        猜你喜欢
        • 2011-08-21
        • 1970-01-01
        • 1970-01-01
        • 2010-12-06
        • 1970-01-01
        • 2011-01-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多