GH-8703: Fix MessagingAnnotationPP for AOT

Fixes https://github.com/spring-projects/spring-integration/issues/8703

* Instantiate a `MessagingAnnotationBeanPostProcessor` via factory method from `MessagingAnnotationPostProcessor`
avoiding extra code generation on an explicitly provided complex `Map` for bean definition property
* Fix test to react properly to a new logic of `MessagingAnnotationBeanPostProcessor` bean registration
This commit is contained in:
EddieChoCho
2023-08-12 09:47:28 -04:00
committed by Artem Bilan
parent ba6d35d123
commit ef5db3059d
12 changed files with 74 additions and 72 deletions

View File

@@ -16,6 +16,8 @@
package org.springframework.integration.config;
import java.beans.Introspector;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -99,19 +101,28 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar {
/**
* Register {@link MessagingAnnotationPostProcessor} and
* {@link org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor},
* {@link MessagingAnnotationBeanPostProcessor},
* if necessary.
* Inject {@code defaultPublishedChannel} from provided {@link AnnotationMetadata}, if any.
* @param registry The {@link BeanDefinitionRegistry} to register additional {@link BeanDefinition}s.
* @see MessagingAnnotationPostProcessor#messagingAnnotationBeanPostProcessor()
*/
private void registerMessagingAnnotationPostProcessors(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(IntegrationContextUtils.MESSAGING_ANNOTATION_POSTPROCESSOR_NAME)) {
BeanDefinitionBuilder builder =
BeanDefinitionBuilder.genericBeanDefinition(MessagingAnnotationPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(IntegrationContextUtils.MESSAGING_ANNOTATION_POSTPROCESSOR_NAME,
builder.getBeanDefinition());
BeanDefinitionBuilder.genericBeanDefinition(MessagingAnnotationPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.getBeanDefinition());
}
String beanName = Introspector.decapitalize(MessagingAnnotationBeanPostProcessor.class.getName());
if (!registry.containsBeanDefinition(beanName)) {
registry.registerBeanDefinition(beanName,
BeanDefinitionBuilder.genericBeanDefinition()
.setFactoryMethodOnBean("messagingAnnotationBeanPostProcessor",
IntegrationContextUtils.MESSAGING_ANNOTATION_POSTPROCESSOR_NAME)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.getBeanDefinition());
}
}

View File

@@ -65,16 +65,13 @@ public class MessagingAnnotationBeanPostProcessor
private final List<Runnable> methodsToPostProcessAfterContextInitialization = new ArrayList<>();
private final BeanDefinitionRegistry registry;
private ConfigurableListableBeanFactory beanFactory;
private volatile boolean initialized;
public MessagingAnnotationBeanPostProcessor(BeanDefinitionRegistry registry,
public MessagingAnnotationBeanPostProcessor(
Map<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>> postProcessors) {
this.registry = registry;
this.postProcessors = postProcessors;
}
@@ -187,13 +184,12 @@ public class MessagingAnnotationBeanPostProcessor
String endpointBeanName = generateBeanName(beanName, method, annotationType);
endpoint.setBeanName(endpointBeanName);
this.registry.registerBeanDefinition(endpointBeanName,
((BeanDefinitionRegistry) this.beanFactory).registerBeanDefinition(endpointBeanName,
new RootBeanDefinition((Class<AbstractEndpoint>) endpoint.getClass(), () -> endpoint));
this.beanFactory.getBean(endpointBeanName);
}
}
protected String generateBeanName(String originalBeanName, Method method,
Class<? extends Annotation> annotationType) {

View File

@@ -16,7 +16,6 @@
package org.springframework.integration.config;
import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.List;
@@ -29,7 +28,6 @@ import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
@@ -91,14 +89,6 @@ public class MessagingAnnotationPostProcessor implements BeanDefinitionRegistryP
.map(BeanFactoryAware.class::cast)
.forEach((processor) -> processor.setBeanFactory((BeanFactory) this.registry));
this.registry.registerBeanDefinition(
Introspector.decapitalize(MessagingAnnotationBeanPostProcessor.class.getName()),
BeanDefinitionBuilder.rootBeanDefinition(MessagingAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addConstructorArgValue(this.registry)
.addConstructorArgValue(this.postProcessors)
.getBeanDefinition());
String[] beanNames = registry.getBeanDefinitionNames();
for (String beanName : beanNames) {
@@ -111,6 +101,16 @@ public class MessagingAnnotationPostProcessor implements BeanDefinitionRegistryP
}
}
/**
* The factory method for {@link MessagingAnnotationBeanPostProcessor} based
* on the environment from this {@link MessagingAnnotationPostProcessor}.
* @return the {@link MessagingAnnotationBeanPostProcessor} instance based on {@link #postProcessors}.
* @since 6.2
*/
public MessagingAnnotationBeanPostProcessor messagingAnnotationBeanPostProcessor() {
return new MessagingAnnotationBeanPostProcessor(this.postProcessors);
}
private void processCandidate(String beanName, AnnotatedBeanDefinition beanDefinition) {
MethodMetadata methodMetadata = beanDefinition.getFactoryMethodMetadata();
MergedAnnotations annotations = methodMetadata.getAnnotations(); // NOSONAR

View File

@@ -24,7 +24,7 @@ import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.MessagingAnnotationPostProcessor;
import org.springframework.integration.config.IntegrationRegistrar;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
@@ -38,6 +38,7 @@ import org.springframework.messaging.support.GenericMessage;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
/**
* @author Mark Fisher
@@ -54,7 +55,7 @@ public class DirectChannelSubscriptionTests {
@BeforeEach
public void setupChannels() {
this.context.registerBean(MessagingAnnotationPostProcessor.class);
new IntegrationRegistrar().registerBeanDefinitions(mock(), this.context.getDefaultListableBeanFactory());
this.context.registerChannel("sourceChannel", this.sourceChannel);
this.context.registerChannel("targetChannel", this.targetChannel);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.GenericMessage;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* @author Mark Fisher
@@ -42,8 +43,7 @@ public class ServiceActivatorAnnotationPostProcessorTests {
public void testAnnotatedMethod() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
try (TestApplicationContext context = TestUtils.createTestApplicationContext()) {
RootBeanDefinition postProcessorDef = new RootBeanDefinition(MessagingAnnotationPostProcessor.class);
context.registerBeanDefinition("postProcessor", postProcessorDef);
new IntegrationRegistrar().registerBeanDefinitions(mock(), context.getDefaultListableBeanFactory());
context.registerBeanDefinition("testChannel", new RootBeanDefinition(DirectChannel.class));
RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleServiceActivatorAnnotationTestBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(latch);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2022 the original author or authors.
* Copyright 2017-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,7 +33,6 @@ import org.mockito.ArgumentCaptor;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
@@ -103,9 +102,7 @@ public class CustomMessagingAnnotationTests {
public static class Config {
@Bean(name = IntegrationContextUtils.MESSAGING_ANNOTATION_POSTPROCESSOR_NAME)
public static MessagingAnnotationPostProcessor messagingAnnotationPostProcessor(
ConfigurableListableBeanFactory beanFactory) {
public static MessagingAnnotationPostProcessor messagingAnnotationPostProcessor() {
MessagingAnnotationPostProcessor messagingAnnotationPostProcessor = new MessagingAnnotationPostProcessor();
messagingAnnotationPostProcessor.
addMessagingAnnotationPostProcessor(Logging.class, new LogAnnotationPostProcessor());

View File

@@ -27,8 +27,8 @@ import org.springframework.integration.annotation.Filter;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.IntegrationRegistrar;
import org.springframework.integration.config.MessagingAnnotationBeanPostProcessor;
import org.springframework.integration.config.MessagingAnnotationPostProcessor;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.test.util.TestUtils;
@@ -38,6 +38,7 @@ import org.springframework.messaging.support.GenericMessage;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
/**
* @author Mark Fisher
@@ -56,7 +57,7 @@ public class FilterAnnotationPostProcessorTests {
@BeforeEach
public void init() {
this.context.registerBean(MessagingAnnotationPostProcessor.class);
new IntegrationRegistrar().registerBeanDefinitions(mock(), this.context.getDefaultListableBeanFactory());
this.context.registerChannel("input", this.inputChannel);
this.context.registerChannel("output", this.outputChannel);
}

View File

@@ -34,8 +34,8 @@ import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.IntegrationRegistrar;
import org.springframework.integration.config.MessagingAnnotationBeanPostProcessor;
import org.springframework.integration.config.MessagingAnnotationPostProcessor;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.support.MessageBuilder;
@@ -49,6 +49,7 @@ import org.springframework.messaging.core.DestinationResolver;
import org.springframework.messaging.support.GenericMessage;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* @author Mark Fisher
@@ -59,10 +60,9 @@ public class MessagingAnnotationPostProcessorTests {
@Test
public void serviceActivatorAnnotation() {
TestApplicationContext context = TestUtils.createTestApplicationContext();
TestApplicationContext context = createTestApplicationContext();
DirectChannel inputChannel = new DirectChannel();
context.registerChannel("inputChannel", inputChannel);
context.registerBean(MessagingAnnotationPostProcessor.class);
context.refresh();
MessagingAnnotationBeanPostProcessor postProcessor = context.getBean(MessagingAnnotationBeanPostProcessor.class);
@@ -134,8 +134,7 @@ public class MessagingAnnotationPostProcessorTests {
@Test
public void outboundOnlyServiceActivator() throws InterruptedException {
TestApplicationContext context = TestUtils.createTestApplicationContext();
context.registerBean(MessagingAnnotationPostProcessor.class);
TestApplicationContext context = createTestApplicationContext();
context.registerChannel("testChannel", new DirectChannel());
CountDownLatch latch = new CountDownLatch(1);
OutboundOnlyTestBean testBean = new OutboundOnlyTestBean(latch);
@@ -144,16 +143,14 @@ public class MessagingAnnotationPostProcessorTests {
DestinationResolver<MessageChannel> channelResolver = new BeanFactoryChannelResolver(context);
MessageChannel testChannel = channelResolver.resolveDestination("testChannel");
testChannel.send(new GenericMessage<>("foo"));
latch.await(1000, TimeUnit.MILLISECONDS);
assertThat(latch.getCount()).isEqualTo(0);
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(testBean.getMessageText()).isEqualTo("foo");
context.close();
}
@Test
public void testChannelResolution() {
TestApplicationContext context = TestUtils.createTestApplicationContext();
context.registerBean(MessagingAnnotationPostProcessor.class);
TestApplicationContext context = createTestApplicationContext();
DirectChannel inputChannel = new DirectChannel();
QueueChannel outputChannel = new QueueChannel();
DirectChannel eventBus = new DirectChannel();
@@ -177,8 +174,7 @@ public class MessagingAnnotationPostProcessorTests {
@Test
public void testProxiedMessageEndpointAnnotation() {
TestApplicationContext context = TestUtils.createTestApplicationContext();
context.registerBean(MessagingAnnotationPostProcessor.class);
TestApplicationContext context = createTestApplicationContext();
DirectChannel inputChannel = new DirectChannel();
QueueChannel outputChannel = new QueueChannel();
context.registerChannel("inputChannel", inputChannel);
@@ -195,8 +191,7 @@ public class MessagingAnnotationPostProcessorTests {
@Test
public void testMessageEndpointAnnotationInherited() {
TestApplicationContext context = TestUtils.createTestApplicationContext();
context.registerBean(MessagingAnnotationPostProcessor.class);
TestApplicationContext context = createTestApplicationContext();
DirectChannel inputChannel = new DirectChannel();
QueueChannel outputChannel = new QueueChannel();
context.registerChannel("inputChannel", inputChannel);
@@ -211,8 +206,7 @@ public class MessagingAnnotationPostProcessorTests {
@Test
public void testMessageEndpointAnnotationInheritedWithProxy() {
TestApplicationContext context = TestUtils.createTestApplicationContext();
context.registerBean(MessagingAnnotationPostProcessor.class);
TestApplicationContext context = createTestApplicationContext();
DirectChannel inputChannel = new DirectChannel();
QueueChannel outputChannel = new QueueChannel();
context.registerChannel("inputChannel", inputChannel);
@@ -229,8 +223,7 @@ public class MessagingAnnotationPostProcessorTests {
@Test
public void testMessageEndpointAnnotationInheritedFromInterface() {
TestApplicationContext context = TestUtils.createTestApplicationContext();
context.registerBean(MessagingAnnotationPostProcessor.class);
TestApplicationContext context = createTestApplicationContext();
DirectChannel inputChannel = new DirectChannel();
QueueChannel outputChannel = new QueueChannel();
context.registerChannel("inputChannel", inputChannel);
@@ -245,8 +238,7 @@ public class MessagingAnnotationPostProcessorTests {
@Test
public void testMessageEndpointAnnotationInheritedFromInterfaceWithAutoCreatedChannels() {
TestApplicationContext context = TestUtils.createTestApplicationContext();
context.registerBean(MessagingAnnotationPostProcessor.class);
TestApplicationContext context = createTestApplicationContext();
DirectChannel inputChannel = new DirectChannel();
QueueChannel outputChannel = new QueueChannel();
context.registerChannel("inputChannel", inputChannel);
@@ -261,8 +253,7 @@ public class MessagingAnnotationPostProcessorTests {
@Test
public void testMessageEndpointAnnotationInheritedFromInterfaceWithProxy() {
TestApplicationContext context = TestUtils.createTestApplicationContext();
context.registerBean(MessagingAnnotationPostProcessor.class);
TestApplicationContext context = createTestApplicationContext();
DirectChannel inputChannel = new DirectChannel();
QueueChannel outputChannel = new QueueChannel();
context.registerChannel("inputChannel", inputChannel);
@@ -278,8 +269,7 @@ public class MessagingAnnotationPostProcessorTests {
@Test
public void testTransformer() {
TestApplicationContext context = TestUtils.createTestApplicationContext();
context.registerBean(MessagingAnnotationPostProcessor.class);
TestApplicationContext context = createTestApplicationContext();
DirectChannel inputChannel = new DirectChannel();
context.registerChannel("inputChannel", inputChannel);
QueueChannel outputChannel = new QueueChannel();
@@ -298,6 +288,12 @@ public class MessagingAnnotationPostProcessorTests {
context.close();
}
private static TestApplicationContext createTestApplicationContext() {
TestApplicationContext context = TestUtils.createTestApplicationContext();
new IntegrationRegistrar().registerBeanDefinitions(mock(), context.getDefaultListableBeanFactory());
return context;
}
@MessageEndpoint
public static class OutboundOnlyTestBean {

View File

@@ -27,13 +27,14 @@ import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.MessagingAnnotationPostProcessor;
import org.springframework.integration.config.IntegrationRegistrar;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.test.util.TestUtils.TestApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* @author Mark Fisher
@@ -56,7 +57,7 @@ public class RouterAnnotationPostProcessorTests {
@BeforeEach
public void init() {
this.context.registerBean(MessagingAnnotationPostProcessor.class);
new IntegrationRegistrar().registerBeanDefinitions(mock(), this.context.getDefaultListableBeanFactory());
context.registerChannel("input", inputChannel);
context.registerChannel("output", outputChannel);
context.registerChannel("routingChannel", routingChannel);

View File

@@ -25,7 +25,7 @@ import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.Splitter;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.MessagingAnnotationPostProcessor;
import org.springframework.integration.config.IntegrationRegistrar;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.test.util.TestUtils.TestApplicationContext;
@@ -33,6 +33,7 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* @author Mark Fisher
@@ -49,7 +50,7 @@ public class SplitterAnnotationPostProcessorTests {
@BeforeEach
public void init() {
this.context.registerBean(MessagingAnnotationPostProcessor.class);
new IntegrationRegistrar().registerBeanDefinitions(mock(), this.context.getDefaultListableBeanFactory());
this.context.registerChannel("input", this.inputChannel);
this.context.registerChannel("output", this.outputChannel);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import org.springframework.integration.MessageRejectedException;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.MessagingAnnotationPostProcessor;
import org.springframework.integration.config.IntegrationRegistrar;
import org.springframework.integration.dispatcher.RoundRobinLoadBalancingStrategy;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
@@ -37,6 +37,7 @@ import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.GenericMessage;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* @author Mark Fisher
@@ -47,8 +48,7 @@ public class SubscriberOrderTests {
@Test
public void directChannelAndFailoverDispatcherWithSingleCallPerMethod() {
try (GenericApplicationContext context = TestUtils.createTestApplicationContext()) {
context.registerBeanDefinition("postProcessor",
new RootBeanDefinition(MessagingAnnotationPostProcessor.class));
new IntegrationRegistrar().registerBeanDefinitions(mock(), context.getDefaultListableBeanFactory());
RootBeanDefinition channelDefinition = new RootBeanDefinition(DirectChannel.class);
context.registerBeanDefinition("input", channelDefinition);
RootBeanDefinition testBeanDefinition = new RootBeanDefinition(TestBean.class);
@@ -75,8 +75,7 @@ public class SubscriberOrderTests {
@Test
public void directChannelAndFailoverDispatcherWithMultipleCallsPerMethod() {
try (GenericApplicationContext context = TestUtils.createTestApplicationContext()) {
context.registerBeanDefinition("postProcessor",
new RootBeanDefinition(MessagingAnnotationPostProcessor.class));
new IntegrationRegistrar().registerBeanDefinitions(mock(), context.getDefaultListableBeanFactory());
BeanDefinitionBuilder channelBuilder = BeanDefinitionBuilder.rootBeanDefinition(DirectChannel.class);
channelBuilder.addConstructorArgValue(null);
RootBeanDefinition channelDefinition = (RootBeanDefinition) channelBuilder.getBeanDefinition();
@@ -118,8 +117,7 @@ public class SubscriberOrderTests {
@Test
public void directChannelAndRoundRobinDispatcher() {
try (GenericApplicationContext context = TestUtils.createTestApplicationContext()) {
context.registerBeanDefinition("postProcessor",
new RootBeanDefinition(MessagingAnnotationPostProcessor.class));
new IntegrationRegistrar().registerBeanDefinitions(mock(), context.getDefaultListableBeanFactory());
RootBeanDefinition channelDefinition = new RootBeanDefinition(DirectChannel.class);
channelDefinition.getConstructorArgumentValues()
.addGenericArgumentValue(new RoundRobinLoadBalancingStrategy());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -835,7 +835,7 @@ public class TcpNioConnectionTests {
assertThat(reading.await(10, TimeUnit.SECONDS)).isTrue();
socket.close();
cf.stop();
assertThat(watch.getLastTaskTimeMillis()).isLessThan(950L);
assertThat(watch.lastTaskInfo().getTimeMillis()).isLessThan(950L);
}
@Test