0

我一直在尝试做一个小型 Groovy 项目,并且想要一个ConcurrentLinkedHashSet,但 Java 没有提供。因此,我开始使用 Gpars 代理创建自己的代理来“保护”普通的 LinkedHashSet。然后我创建了我的包装类来保存代理,并将我的类上的方法委托给内部代理,就像这样(这个版本使用 methodMissing 作为委托方法)。我尝试了 Groovy interceptable/invokeMethod 并且可以让它工作

class ConcurrentLinkedHashSet /*implements GroovyInterceptable*/  {

    Agent $concurrentHashSet = new Agent (new LinkedHashSet())

    /*Object[] toArray () {
        $concurrentHashSet.val.asType(LinkedHashSet).toArray()
    }*/

    def asType (Set) {
        $concurrentHashSet.val
    }

    //set up delegation action to pass all methods to the agent to execute
    def /*invokeMethod*/methodMissing (String name, args){
        def result
        System.out.println "methodMissing with $name and $args on $this"
        System.out.println "agent value is : ${$concurrentHashSet.val.dump()} is instance of: ${$concurrentHashSet.getClass()}"
        $concurrentHashSet {

            System.out.println "\t\tconcHashSet methodMissing: it is of type ${it.getClass()} so called $it.invokemethod ($name, $args) "
            if (args == []) {
                System.out.println "\t\tconcHashSet methodMissing: invoke method '$name' with no args "
                result = it."$name"()//it.invokeMethod (name)
            } else {
                System.out.println "\t\tconcHashSet methodMissing: invoke method '$name' with args $args"
                result = it.invokeMethod(name, *args)//"$name" (*args)//it.invokeMethod(name, args)
            }
            System.out.println "\tconcHashSet methodMissing: 'it' is now showing as >  '$it'"
            "success"
       }
        //System.out.println "agent protected value is now : " + $concurrentHashSet.val + " and result now $result"
        System.out.println "agent protected value is now : " + $concurrentHashSet.val.dump() + " and result now $result"
        $concurrentHashSet.val
    }
}

但是,当我尝试使用它时 - 它第一次工作,我的字符串被添加,但在第二次调用相同的缺失方法时,agent.send 调用永远不会进行,并且会被跳过。

所以我的简单脚本使用者看起来像这样

// delegates add method via agent.send first time through but not after !
ConcurrentLinkedHashSet conhs = new ConcurrentLinkedHashSet ()

conhs.add ('col1')
println "1st conHashSet as list : ${conhs.toArray()}"

conhs.add ('col2')
println "2nd conHashSet as list : ${conhs.toArray()}"


// direct calls on an agent using invokeMethod 
Agent myHash = new Agent (new LinkedHashSet())
myHash {it.invokeMethod ('add',  'col1')}
println "1st agentHashSet as list : ${myHash.val.toArray()}"

myHash {it.invokeMethod ('add','col2')}
println "2nd agentHashSet as list : ${myHash.val.toArray()}"

我的简单跟踪日志在控制台输出上看起来像这样

methodMissing with add and [col1] on org2.softwood.rules.utils.ConcurrentLinkedHashSet@3b0090a4
agent value is : <java.util.LinkedHashSet@0 map=[:]> is instance of: class groovyx.gpars.agent.Agent
        concHashSet methodMissing: it is of type class java.util.LinkedHashSet so called [] (add, [col1]) 
        concHashSet methodMissing: invoke method 'add' with args [col1]
    concHashSet methodMissing: 'it' is now showing as >  '[col1]'
agent protected value is now : <java.util.LinkedHashSet@2eaeb1 map=[col1:java.lang.Object@6a2f6f80]> and result now true
methodMissing with toArray and [] on org2.softwood.rules.utils.ConcurrentLinkedHashSet@3b0090a4
agent value is : <java.util.LinkedHashSet@2eaeb1 map=[col1:java.lang.Object@6a2f6f80]> is instance of: class groovyx.gpars.agent.Agent
agent protected value is now : <java.util.LinkedHashSet@2eaeb1 map=[col1:java.lang.Object@6a2f6f80]> and result now null
1st conHashSet as list : [col1]
methodMissing with add and [col2] on org2.softwood.rules.utils.ConcurrentLinkedHashSet@3b0090a4
agent value is : <java.util.LinkedHashSet@2eaeb1 map=[col1:java.lang.Object@6a2f6f80]> is instance of: class groovyx.gpars.agent.Agent
agent protected value is now : <java.util.LinkedHashSet@2eaeb1 map=[col1:java.lang.Object@6a2f6f80]> and result now null
methodMissing with toArray and [] on org2.softwood.rules.utils.ConcurrentLinkedHashSet@3b0090a4
agent value is : <java.util.LinkedHashSet@2eaeb1 map=[col1:java.lang.Object@6a2f6f80]> is instance of: class groovyx.gpars.agent.Agent
agent protected value is now : <java.util.LinkedHashSet@2eaeb1 map=[col1:java.lang.Object@6a2f6f80]> and result now null
2nd conHashSet as list : [col1]
1st agentHashSet as list : [col1]
2nd agentHashSet as list : [col1, col2]

正如您在第一次尝试委派时看到的那样,您可以看到“concHashSet methodMissing:”跟踪,并且代理在代理中对其调用 invokeMethod 以影响添加元素。

在第二次调用 conhs.add ('col2') 时,agent.sand 调用永远不会发生,因此我的额外项目永远不会被添加。

这很烦人,因为我认为我有一个简单的方法来创建我的 ConcurrentLinkedHashSet,但是代码不起作用。我可以使用什么机制来获得正确的结果?

正如你所看到的,当我直接调用方法(添加)时Agent<LinkedHashSet>它工作得很好。在我真正的消费类中,如果我用普通的 LinkedHashSet 替换我的 ConcurrentLinkedHashSet 它可以实现梦想,但不是线程安全的。我想创建一个线程安全版本,这取决于试图让它工作。

我想我可以尝试替换代理并仅在我的 LinkedHashSet 周围使用同步块 - 但它有点难看 - 我认为 Gpars 代理会为我将所有这些作为通用解决方案模式作为具有委托的包装器进行排序。

PS我尝试了另一种方法,我认为这种工作方式,但感觉不对 - 它在实现 GroovyInterceptable 的类上使用 @Synchronise on invokeMethod 以在委派时实现线程安全调用。我不确定这是否真的线程安全。

class ConcurrentLinkedHashSet2 implements GroovyInterceptable{
@Delegate private LinkedHashSet $mySet = new LinkedHashSet()

   @Synchronized
    def invokeMethod (String name, args) {
        System.out.println "call to $name intercepted invoke using metaclass invoke"

        ConcurrentLinkedHashSet2.metaClass.getMetaMethod(name).invoke (this, *args)
    }
}
4

2 回答 2

1

Vaclav 发现了我的错误——我的 System.out.println 有一个错误,导致代理体内出现异常。

因此,我构建了原始示例的更正版本:

/**
 * using methodMissing to delegate calls
 *
 * Created by William on 29/12/2016.
 */

    
class ConcurrentLinkedHashSet2 implements GroovyInterceptable {
    Agent $concurrentHashSet = new Agent (new LinkedHashSet())

    //set up delegation action to pass all methods to the agent to execute
    def invokeMethod (String name, args){
        DataflowVariable invokeResult = new DataflowVariable()
         $concurrentHashSet {
             invokeResult << it?.invokeMethod(name, args)
        }
        invokeResult.val
    }

}

这导致在 Genie 类中得到更普遍的答案——我希望这可能对其他人有用。在这个版本中,当类挂起时,决定将 equals 和 toString 委托给包装变量,这解决了这个问题。此版本采用任何类或实例变量并用线程安全代理包装它,然后将方法调用委托给包装的变量 - 因此是“精灵”绰号。

/**
 * Thread safe container for instance of any type, where the
 * genies invokeMethod is used to delegate calls to agent protected
 * value.  If the genie provides local methods, then the
 * delegation calls the local method
 *
 * Created by William on 29/12/2016.
 */


class Genie<T> implements GroovyInterceptable {
    Agent $concurrentVariable = new Agent ()

    Genie (Class clazz) {
        assert clazz instanceof Class
        def instance = clazz.newInstance()
        $concurrentVariable {
            updateValue (instance)
        }
        def ref = $concurrentVariable.val
        assert clazz.is (ref.getClass() )
    }

    Genie (instanceVariable) {
        $concurrentVariable { updateValue (instanceVariable)}
    }

    void setVariable (instanceVariable) {
        $concurrentVariable { updateValue (instanceVariable)}
    }

    def getVariable () {
        $concurrentVariable.val
    }

    def getWrappedClass () {
        $concurrentVariable.val?.getClass()
    }

    //set up delegation action to pass all methods to the agent to execute
    def invokeMethod (String name, args) throws MissingMethodException {
        DataflowVariable invokeResult = new DataflowVariable()
        //if holding reference to a null then just try the call on the Genie instance
        if ($concurrentVariable.val == null) {
            invokeResult << this.metaClass.getMetaMethod("$name", args).invoke (this, args)
            return invokeResult.val
        }

        //else look and see if method is expected to be called on the Genie instance
        def listOfMethods = this.metaClass.getMethods().collect {it.name}
        //we want delegation of toString() and equals(), and hashCode() to wrapped variable so remove from check of methods on Genie
        listOfMethods.remove ("toString")
        listOfMethods.remove ("equals")
        listOfMethods.remove ("hashCode")
        boolean methodMatched = listOfMethods.find {it == name}
        if (methodMatched && this instanceof Genie) {
            //see if there's a local method in Genie in scope, then call it
            invokeResult << this.metaClass.getMetaMethod("$name", args).invoke (this, args)
        } else {
            def method = $concurrentVariable.val.metaClass.getMetaMethod(name, args)
            if (method == null) {
                invokeResult << $concurrentVariable.val.metaClass.invokeMissingMethod($concurrentVariable.val, name, args)
            } else {
                //delegate the method to the variable wrapped by the Agent<T>
                $concurrentVariable {
                    MetaMethod validMethod = it.metaClass.getMetaMethod(name, args)
                    if (validMethod) {
                        invokeResult << validMethod.invoke(it, args)
                    } else invokeResult << null
                }
            }
        }
        invokeResult.val
    }

}
于 2017-01-01T17:14:44.023 回答
1

注释掉跟踪输出后,它按预期工作: System.out.println "\t\tconcHashSet methodMissing: it is of type ${it.getClass()} so called $it.invokemethod ($name, $args) "

此行会导致代理抛出未处理的异常,从而终止代理。

于 2016-12-30T12:40:46.443 回答