Groovy 类别使用仅限于当前线程是什么意思?

What does it mean that Groovy Category use is confined to the current thread?

我在 Groovy In Action, 2nd Edition 一书中找到了以下语句:

Category use is confined to the current thread

这句话到底是什么意思?

这意味着您只能在同一线程中调用通过类别class 添加的方法。考虑以下示例:

class StringUtils {
    static String transform(String source) {
        return source.toUpperCase().reverse().substring(0, source.length() / 2 as int)
    }
}

use (StringUtils) {
    println "Lorem ipsum".transform()
}

在此示例中,我们通过类别添加 String.transform() 方法。 运行 此示例产生以下输出:

MUSPI

在此示例中,我们在 main 线程中使用了类别 class,并且我们也在 main 线程中调用了 String.transform() 方法。

现在让我们稍微改变一下这个例子,让我们通过在新启动的线程中调用它来调用 main 线程外的 String.transform() 方法:

class StringUtils {
    static String transform(String source) {
        return source.toUpperCase().reverse().substring(0, source.length() / 2 as int)
    }
}

use (StringUtils) {
    Thread.start {
        println "Lorem ipsum".transform()
    }
}

我们在 main 线程中使用了 StringUtil 类别 class 并且我们从 Thread-1 线程调用了这个方法。让我们看看当我们 运行 它时会发生什么:

Exception in thread "Thread-1" groovy.lang.MissingMethodException: No signature of method: java.lang.String.transform() is applicable for argument types: () values: []
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:58)
    at org.codehaus.groovy.runtime.callsite.PojoMetaClassSite.call(PojoMetaClassSite.java:49)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:117)
    at script$_run_closure1$_closure2.doCall(script.groovy:9)
    at script$_run_closure1$_closure2.doCall(script.groovy)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    at groovy.lang.Closure.call(Closure.java:414)
    at groovy.lang.Closure.call(Closure.java:408)
    at groovy.lang.Closure.run(Closure.java:495)
    at java.lang.Thread.run(Thread.java:748)

抛出异常,因为 String.transform() 不存在于 Thread-1 线程的范围内 - 它仅存在于 main 线程中。

但假设我们必须使此方法在 Thread-1 线程范围内可用。我们可以通过在 Thread-1 块中定义 use(StringUtils){} 来实现它,例如

class StringUtils {
    static String transform(String source) {
        return source.toUpperCase().reverse().substring(0, source.length() / 2 as int)
    }
}

Thread.start {
    use(StringUtils) {
        println "Lorem ipsum".transform()
    }
}

现在一切正常 - 类别使用块在 Thread-1 中定义,我们从同一个线程调用 String.transform() 方法。 运行 此示例向控制台生成预期输出:

MUSPI

这就是

Category use is confined to the current thread

实践中的意思。

但是如何调用此方法?

当我们调用时:

"Lorem ipsum".transform()

从上面的示例中,以下 Groovy 方法处理 transform() 方法的调用:

groovy.lang.MetaClassImpl.invokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass)

您可以在第 1044 行找到它 (Groovy 2.4.12)。 transform() 方法在 class String 中不存在,因此 Groovy 必须在其他地方找到它的实现。在这种情况下,该方法位于第 1055 行:

MetaMethod method = null;
if (CLOSURE_CALL_METHOD.equals(methodName) && object instanceof GeneratedClosure) {
    method = getMethodWithCaching(sender, "doCall", arguments, isCallToSuper);
}

此方法最重要的部分是第 1283 行:

if (!isCallToSuper && GroovyCategorySupport.hasCategoryInCurrentThread()) {
    return getMethodWithoutCaching(sender, methodName, MetaClassHelper.convertToTypeArray(arguments), isCallToSuper);
} else {
    ....
}

GroovyCategorySupport.hasCategoryInCurrentThread() 检查类别是否在当前线程中使用(在这种情况下使用 ThreadLocal)。

如果您跟踪接下来发生的事情,您将到达 MetaClassImpl 行 690,其中 getMethods(Class sender, String name, boolean isCallToSuper) 所在。在第 706 行,此方法调用:

List used = GroovyCategorySupport.getCategoryMethods(name);

这是最后一部分,它实际在类别 class 中按名称查找方法。稍后它检查该方法是否是静态的,以及它是否需要具有有效类型的参数(在本例中为 String)。