Закрытые поля @ SpyBean-ed класса в многопоточной среде - PullRequest
0 голосов
/ 27 февраля 2019

У меня есть интеграция @SpringBootTest, которая также подразумевает другой фоновый поток.

Из потока теста (!) Некоторые открытые вызовы метода изменяют закрытое поле экземпляра @SpyBean -ed (@Scope вsingleton).

Проблема в том, что вызовы из фонового потока не видят ничего из этого изменения!

После нескольких часов отладки (например, попытки использования volatile) я исследовал экземпляр @SpyBean -ed и нашелупомянутое поле (_state) действительно различается между ссылочными и переносимыми экземплярами (при условии, что spiedInstance означает перенос) и в других закрытых полях.

exploring spybean instance

Есть идеи?

ОБНОВЛЕНИЕ: Я упростил свою среду, чтобы помочь воспроизведению.

@Service
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class TestServiceImpl {
    private Logger logger = LoggerFactory.getLogger(TestServiceImpl.class);

    private final TaskExecutor taskExecutor;

    private volatile int value = 0;
    private volatile boolean isEnabled = false;

    @Autowired
    public TestServiceImpl(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;

        taskExecutor.execute(() -> pingToLog());
    }

    private void pingToLog() {
        while(true) {
            logger.debug(String.valueOf(value));

            try {
                TimeUnit.MILLISECONDS.sleep(1000);
            } catch (InterruptedException e) {
                // no op
            }
        }
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = BicisoApplication.class)
@DirtiesContext
public class TestServiceTest {

    @SpyBean
    protected TestServiceImpl testService;

    @Test
    public void doTest() {

        try {
            TimeUnit.MILLISECONDS.sleep(3000);
        } catch (InterruptedException e) {
            // no op
        }

        testService.setValue(1);

        try {
            TimeUnit.MILLISECONDS.sleep(3000);
        } catch (InterruptedException e) {
            // no op
        }
    }
}

Ожидается: после нескольких строк "TestServiceImpl - 0" в журнале несколько строк "TestServiceImpl - 1"должно появиться.

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

Но TestServiceImpl выше - это упрощение.В реальном коде класс подписывается в конструкторе на событие, которое будет вызвано другими потоками.Эта подписка имеет ссылку на исходный экземпляр, а не на шпионскую оболочку, поэтому возникает проблема.

ОБНОВЛЕНИЕ2: Кажется, @SpyBean создает новый экземпляр вместо того, чтобы использовать уже существующий в контексте.Я все еще думаю, что это ошибка: @SpyBean должен обернуть существующий экземпляр, а не дублировать экземпляр, и дублировать его, возможно, уже используемые поля.

ОБНОВЛЕНИЕ3: Неожиданно в результате ситуация уже задокументирована.Документация Mockito содержит следующее в разделе Шпионаж на реальных объектах section:

Mockito не делегирует вызовы переданному реальному экземпляру, вместо этого он фактическисоздает копию этого.Поэтому, если вы сохраняете реальный экземпляр и взаимодействуете с ним, не ожидайте, что шпион узнает об этом взаимодействии и его влиянии на состояние реального экземпляра.Следствие состоит в том, что когда unstubbed метод вызывается на шпионе , но не на реальном экземпляре , вы не увидите никаких эффектов на реальном экземпляре.

Я чувствую, что это действительно ошибочная реализация!Это не шпион , а hiderAndReplacer .: (

Извините, что потратил ваше время.

1 Ответ

0 голосов
/ 03 марта 2019

Внезапно результатом является то, что ситуация уже задокументирована, по крайней мере, Вы пересмотрели проблему до тех пор, пока не научитесь не доверять очевидному значению слов , но искать документы.Документация Mockito содержит следующее в разделе Шпионаж на реальных объектах :

Mockito не делегирует вызовы переданному реальному экземпляру, вместо этого он фактически создает копиюэтогоПоэтому, если вы сохраняете реальный экземпляр и взаимодействуете с ним, не ожидайте, что шпион узнает об этом взаимодействии и его влиянии на состояние реального экземпляра.Следствие состоит в том, что когда unstubbed метод вызывается на шпионе , но не на реальном экземпляре , вы не увидите никаких эффектов на реальном экземпляре.

Я чувствую, что это действительно ошибочная реализация!Это не шпион , а hiderAndReplacer функция.: (

Извините, что потратил ваше время.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...