Ломбок и Спок: @RequiredArgsConstructor не скрывает конструктор по умолчанию без аргументов для поля с типом интерфейса - PullRequest
0 голосов
/ 23 декабря 2018

Кажется, что @RequiredArgsConstructor не работает в приведенном ниже коде - но только в тесте, использующем структуру Spock, и только для поля, имеющего тип интерфейса Dao.
Строго говоря - theкод работает , хотя, на мой взгляд, он не должен работать, учитывая, что подобный тест в 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 не компилируется).
Кажется, что тест Спока каким-то образом видитконструктор по умолчанию без аргументов (в то время как тест 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>

1 Ответ

0 голосов
/ 24 декабря 2018

Прежде всего, я могу подтвердить, что это происходит и со мной.Я никогда раньше не замечал.

Мне пришлось отлаживать исходный код и просматривать декомпилированные файлы, чтобы хотя бы немного понять, что здесь происходит.Я могу сказать вам несколько вещей:

  • Это , не связанное с Lombok. Это также происходит с любым классом Java с конструктором с одним аргументом, принимающим тип объекта (т.е. непримитив, как int), например String или ваш Dao.
  • Это не связано со Споком , потому что это также происходит за пределами Спока.
  • Кажетсябыть связанным с динамическими функциями 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);
}

и так далее.Я думаю, что случится так, что Object[] размера 0 будет как-то передан как параметр конструктора, а отсутствие элемента интерпретируется как аргумент null.Это также то, что вы видите в отладчике после создания экземпляра объекта, если, как в вашем коде, параметр назначен члену: элемент будет иметь значение null.

...