【问题标题】:Lombok and Spock: @RequiredArgsConstructor doesn't hide default no-args constructor for a field with a type of interfaceLombok 和 Spock:@RequiredArgsConstructor 不会隐藏具有接口类型的字段的默认无参数构造函数
【发布时间】:2018-12-23 16:17:51
【问题描述】:

似乎@RequiredArgsConstructor 在下面的代码中不起作用 - 但仅在使用 Spock 框架的测试中,并且仅适用于接口类型为 Dao 的字段。
严格来说 - 代码可以工作,但我认为它不应该工作,考虑到 JUnit5 下的类似测试根本无法编译。

有人可以解释这是一个错误还是一个功能?

package brumba;
public interface Dao {
    Integer getValueFor(Integer value);
}

package brumba;

import com.sun.istack.internal.NotNull;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class Brumba {

    @NotNull
    final private Dao dao;

//  If you uncomment the below 2 lines, then the test fails
//    @NotNull
//    final private String name;

    public Integer twice(Integer x){
        return x * 2;
    }

    public Integer twiceDao(Integer x){
        return dao.getValueFor(x);
    }
}

下面的代码工作正常 - 但仅在 Spock 中(JUnit5 下的类似测试无法编译)。
似乎 Spock 测试以某种方式看到了默认的无参数构造函数(而 JUnit 测试没有看到这个构造函数)
但是,当上面的 2 条注释行未注释时,测试失败并出现以下错误:

groovy.lang.GroovyRuntimeException: Could not find matching constructor for: brumba.Brumba()

package brumba

import spock.lang.Specification

class BrumbaTest extends Specification {

    def "twice should multiply argument by 2"() {
        given:
            def testedObject = new Brumba();

        expect:
            y == testedObject.twice( x )

        where:
            x | y
            0 | 0
            1 | 2
            2 | 4
            3 | 6
    }
}

而且这个 JUnit 测试根本无法编译:

package brumba;

class BrumbaJUnit5Test {

    @org.junit.jupiter.api.Test
    void shouldTwice() {
        Brumba br = new Brumba();
    }
} 

错误是:

Error:(7, 21) java: constructor Brumba in class brumba.Brumba cannot be applied to given types;
  required: brumba.Dao,java.lang.String
  found: no arguments

以下是我用于此项目的依赖项:

<dependency>
    <groupId>org.spockframework</groupId>
    <artifactId>spock-core</artifactId>
    <version>1.2-groovy-2.5</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.4</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.3.0-M1</version>
    <scope>test</scope>
</dependency>

【问题讨论】:

  • 我在public class Brumba 中看不到NoArgConstructor
  • @Deadpool 你看不到 - java 编译器自动创建默认的无参数构造函数,除非定义了另一个构造函数。 Lombok @RequiredArgsConstructor 应该生成 public Brumba(Dao dao) 构造函数,所以在这种情况下,该类根本不应该有这个默认构造函数(它应该是私有的 - 隐藏的) - 但是 Spock 测试以某种方式看到了这个构造函数并且能够实例化这个对象使用此构造函数的类。
  • 我知道了,但我不知道 spock

标签: java spock lombok intellij-lombok-plugin


【解决方案1】:

首先,我可以确认这也发生在我身上。以前没注意过。

我不得不通过源代码进行调试并查看反编译的文件,以便至少了解这里发生了什么。我可以告诉你几件事:

  • 与 Lombok 无关。 它也发生在任何具有采用对象类型(即不是像 int 这样的原语)的单参数构造函数的 Java 类中,例如String 或您的 Dao
  • 与 Spock 无关,因为它也发生在 Spock 之外。
  • 似乎与动态 Groovy 运行时特性有关。
  • 我宁愿称其为微妙的错误而不是功能,但我不确定。

Java 类:

package de.scrum_master.stackoverflow;

public class Brumba {
  public Brumba(String name) {}
}

Groovy 类:

package de.scrum_master.stackoverflow

class BrumbaApp {
  static void main(String[] args) {
    new Brumba()
  }
}

反编译的 Groovy 类:

package de.scrum_master.stackoverflow;

import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class BrumbaApp implements GroovyObject {
  public BrumbaApp() {
    CallSite[] var1 = $getCallSiteArray();
    MetaClass var2 = this.$getStaticMetaClass();
    this.metaClass = var2;
  }

  public static void main(String... args) {
    CallSite[] var1 = $getCallSiteArray();
    var1[0].callConstructor(Brumba.class);
  }
}

Groovy 运行时类CallSite 实际上是一个接口,但有AbstractCallSite 实现它。如果我们看看这个方法

public Object callConstructor(Object receiver) throws Throwable {
    return callConstructor(receiver, CallSiteArray.NOPARAM);
}

还有这个定义

public final class CallSiteArray {
    // ...
    public static final Object [] NOPARAM = new Object[0];
    // ...

我们知道这个方法实际上会被调用

public Object callConstructor(Object receiver, Object[] args) throws Throwable {
    return CallSiteArray.defaultCallConstructor(this, receiver, args);
}

等等。我认为发生的情况是大小为 0 的 Object[] 将以某种方式作为构造函数参数传递,并且缺少元素被解释为 null 参数。这也是您在对象实例化后在调试器中看到的内容,如果像在您的代码中将参数分配给成员一样:该成员将具有值 null

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-18
    • 2017-09-27
    • 2018-04-08
    • 1970-01-01
    • 2015-01-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多