意外行为在将方法委派给代理保护值

时间:2016-12-29 20:56:17

标签: groovy concurrency gpars linkedhashset

我一直在尝试做一个小的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]

正如您在第一次尝试授权时所看到的那样,您可以看到&#39; concHashSet方法,即:&#39;跟踪和代理在代理中调用invokeMethod以实现添加元素。

第二次调用conhs.add(&#39; col2&#39;)时,agent.sand调用永远不会发生,所以我的额外项目永远不会被添加。

这真的很烦人,因为我觉得我有一个简单的方法来创建我的ConcurrentLinkedHashSet,但代码不起作用。有没有人知道为什么/或可以建议一种机制来获得正确的结果?

正如您所看到的,当我直接在代理上调用方法(添加)时,它可以正常工作。我是Bambozzled,任何帮助都非常接受 - 我花了一整天努力让这个工作。在我真正的connsuming类中,如果我用普通的LinkedHashSet替换我的ConcurrentLinkedHashSet,它可以实现梦想,但不是线程安全的。我想创建一个线程保存版本,它依赖于尝试使其工作。

我想我可以尝试替换代理,只使用我的LinkedHashSet周围的同步块 - 但它有点难看 - 我认为Gpars代理会将这一切都作为一般解决方案模式作为委托包装器对我进行排序。

请求帮助

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

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

PPS(01/01/2017) Vaclav,发现我的错误,我的输出跟踪调用了类并在代理主体中生成了一个错误。

所以现在我已经附加了更正后的版本,并创建了一个名为Genie的更通用的线程安全类,它将为构造函数中提供的实例提供委托包装器。

class ConcurrentLinkedHashSet2 implements GroovyInterceptable {
    def result
    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)
        }
        //System.out.println "\treturning result now//$concurrentHashSet.val
    }

}

通用精灵类看起来像这样。我不得不使用Dataflow变量来存储委托操作的结果 - 当我尝试使用标准变量时,我遇到了其他未设置的问题。数据流变量修正了。

我希望这可能对其他人有用,因为它是一个通用的线程安全包装器。

/**
 * Thread safe container for instance of any tpe, 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
    }

}

2 个答案:

答案 0 :(得分:1)

在注释掉跟踪输出后,它按预期工作: System.out.println“\ t \ tconcHashSet methodMissing:它的类型是$ {it.getClass()}所以叫做$ it.invokemethod($ name,$ args)”

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

答案 1 :(得分: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委托给包装变量,当类挂起时,这就解决了这个问题。此版本接受任何类或实例变量并使用线程安全代理程序包装它,然后将方法调用委托给包装变量 - 因此'Genie'名字对象

/**
 * 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
    }

}