Java 元类构造函数重写不适用于@CompileStatic注释类内的方法

Java 元类构造函数重写不适用于@CompileStatic注释类内的方法,java,unit-testing,groovy,metaprogramming,spock,Java,Unit Testing,Groovy,Metaprogramming,Spock,当我们对任何特定类使用someClass.metaClass.constructor时,比如在用@CompileStatic注释的类的方法中可用的类,构造函数重写根本不起作用 当我们删除@CompileStatic注释时,它可以正常工作。我错过什么了吗 示例代码: @CompileStatic class FooClass { String getDataFromProvider() { String url = "https://www.example.com"

当我们对任何特定类使用someClass.metaClass.constructor时,比如在用@CompileStatic注释的类的方法中可用的类,构造函数重写根本不起作用

当我们删除@CompileStatic注释时,它可以正常工作。我错过什么了吗

示例代码:

@CompileStatic
class FooClass {

    String getDataFromProvider() {
        String url = "https://www.example.com"
        RESTClient restClient = new RESTClient(url)

        HttpResponseDecorator response = restClient.post([:]) as HttpResponseDecorator
        return response
    }
}
以及测试用例:

import groovyx.net.http.HttpResponseDecorator
import groovyx.net.http.RESTClient
import spock.lang.Specification

class FooContentSpec extends Specification {

    void "test getDataFromProvider method"() {
        given: "Rest url"
        String restURL = "https://www.example.com"

        and: "Mock RESTClient"
        RESTClient mockedRestClient = Mock(RESTClient)

        // THIS IS NOT WORKING
        RESTClient.metaClass.constructor = { Object url ->
            assert restURL == url
            return mockedRestClient
        }

        mockedRestClient.metaClass.post = { Map<String, ?> args ->
            return ""
        }

        when: "We hit the method"
        HttpResponseDecorator response = Content.getDataFromProvider()

        then: "We should get status 200"
        response.statusCode == 200
    }
}
Jira:

你说得对@CompileStatic不能与元类操作结合使用。原因是,顾名思义,所有这些都是在编译时解析和绑定的,因此没有元类查找,因此也没有方法重写它

我建议研究IoC/依赖注入,这样您就可以将模拟注入到代码中。使用经典单例会使您的代码更难测试。

在以下评论之后:

Yes@CompileStatic将在编译时解析FooClass中RESTClient的构造函数,因此它不会在运行时使用元类将其锁定。如果你想看看它的样子,我建议使用一个反编译器,比如字节码查看器,看看生成的字节码

我们针对两种情况反编译生成的字节码:

使用@CompileStatic 没有@CompileStatic
所以@Leonard给出的答案是完全正确的。我们错过了这个简单的Java概念。

关于使用DI的一个很好的建议,但是@CompileStatic在另一个类中使用,而我们正试图重写第一个类中使用的另一个类的构造函数。那么这也很重要吗?是的@CompileStatic将在编译时解析FooClass中RESTClient的构造函数,这样它就不会在运行时使用元类将其锁定。如果你想看看它的样子,我建议使用反编译器,比如字节码查看器,看看生成的字节码。没错,我错过了编译时构造函数。谢谢你的帮助。
------------------------------------------------------------
Gradle 3.5
------------------------------------------------------------

Groovy:       2.4.10,
Ant:          Apache Ant(TM) version 1.9.6 compiled on June 29 2015,
JVM:          1.8.0_221 (Oracle Corporation 25.221-b11),
OS:           Mac OS X 10.15.2 x86_64
public class FooClass implements GroovyObject {
    public FooClass() {
        MetaClass var1 = this.$getStaticMetaClass();
        this.metaClass = var1;
    }

    public String getDataFromProvider() {
        String url = "https://www.example.com";

        // Directly constructor is getting used
        RESTClient restClient = new RESTClient(url);

        HttpResponseDecorator response = (HttpResponseDecorator)ScriptBytecodeAdapter.asType(restClient.post(ScriptBytecodeAdapter.createMap(new Object[0])), HttpResponseDecorator.class);
        return (String)ShortTypeHandling.castToString(response);
    }
}
public class FooClass implements GroovyObject {
    public FooClass() {
        CallSite[] var1 = $getCallSiteArray();
        super();
        MetaClass var2 = this.$getStaticMetaClass();
        this.metaClass = var2;
    }

    public String getDataFromProvider() {
        CallSite[] var1 = $getCallSiteArray();
        String url = "https://www.example.com";

        // Here Groovy's metaprogramming is into play instead of directly calling constructor
        RESTClient restClient = (RESTClient)ScriptBytecodeAdapter.castToType(var1[0].callConstructor(RESTClient.class, url), RESTClient.class);

        HttpResponseDecorator response = (HttpResponseDecorator)ScriptBytecodeAdapter.asType(var1[1].call(restClient, ScriptBytecodeAdapter.createMap(new Object[0])), HttpResponseDecorator.class);
        return (String)ShortTypeHandling.castToString(response);
    }
}