Remove Disposables abstraction

* Remove `Disposables` since it is a package protected therefore could not be used outside
* Remove the logic relevant ot the `Disposables` in the `MessagingAnnotationPostProcessor` logic
* Replace a `registerSingleton()` and `initializeBean()` with the proper `registerBeanDefinition()`
and `getBean()` usage.
This way bean are going to be destroyed properly
* Remove `MessagingAnnotationPostProcessorChannelCreationTests` since its mocking logic is too vague
and really covered with many other real tests.
This commit is contained in:
Artem Bilan
2021-11-22 12:11:32 -05:00
committed by Gary Russell
parent d7f925896f
commit a843dae151
5 changed files with 26 additions and 193 deletions

View File

@@ -36,10 +36,11 @@ import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.ResolvableType;
@@ -120,19 +121,20 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
protected final ConfigurableListableBeanFactory beanFactory; // NOSONAR
protected final BeanDefinitionRegistry definitionRegistry; // NOSONAR
protected final ConversionService conversionService; // NOSONAR
protected final DestinationResolver<MessageChannel> channelResolver; // NOSONAR
protected final Class<T> annotationType; // NOSONAR
protected final Disposables disposables; // NOSONAR
@SuppressWarnings(UNCHECKED)
public AbstractMethodAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
Assert.notNull(beanFactory, "'beanFactory' must not be null");
this.messageHandlerAttributes.add(SEND_TIMEOUT_ATTRIBUTE);
this.beanFactory = beanFactory;
this.definitionRegistry = (BeanDefinitionRegistry) beanFactory;
this.conversionService = this.beanFactory.getConversionService() != null
? this.beanFactory.getConversionService()
: DefaultConversionService.getSharedInstance();
@@ -140,14 +142,6 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
this.annotationType =
(Class<T>) GenericTypeResolver.resolveTypeArgument(this.getClass(),
MethodAnnotationPostProcessor.class);
Disposables disposablesBean = null;
try {
disposablesBean = beanFactory.getBean(Disposables.class);
}
catch (@SuppressWarnings("unused") Exception e) {
// NOSONAR - only for test cases
}
this.disposables = disposablesBean;
}
@Override
@@ -187,23 +181,19 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
}
private MessageHandler registerHandlerBean(String beanName, Method method, final MessageHandler handler) {
MessageHandler handlerBean = handler;
String handlerBeanName = generateHandlerBeanName(beanName, method);
if (handlerBean instanceof ReplyProducingMessageHandlerWrapper
if (handler instanceof ReplyProducingMessageHandlerWrapper
&& StringUtils.hasText(MessagingAnnotationUtils.endpointIdValue(method))) {
handlerBeanName = handlerBeanName + ".wrapper";
}
if (handlerBean instanceof IntegrationObjectSupport) {
((IntegrationObjectSupport) handlerBean).setComponentName(
if (handler instanceof IntegrationObjectSupport) {
((IntegrationObjectSupport) handler).setComponentName(
handlerBeanName.substring(0,
handlerBeanName.indexOf(IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX)));
}
this.beanFactory.registerSingleton(handlerBeanName, handler);
handlerBean = (MessageHandler) this.beanFactory.initializeBean(handlerBean, handlerBeanName);
if (handlerBean instanceof DisposableBean && this.disposables != null) {
this.disposables.add((DisposableBean) handlerBean);
}
return handlerBean;
this.definitionRegistry.registerBeanDefinition(handlerBeanName,
new RootBeanDefinition(MessageHandler.class, () -> handler));
return this.beanFactory.getBean(handlerBeanName, MessageHandler.class);
}
private void orderable(Method method, MessageHandler handler) {
@@ -355,12 +345,9 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
}
catch (DestinationResolutionException e) {
if (e.getCause() instanceof NoSuchBeanDefinitionException) {
inputChannel = new DirectChannel();
this.beanFactory.registerSingleton(inputChannelName, inputChannel);
inputChannel = (MessageChannel) this.beanFactory.initializeBean(inputChannel, inputChannelName);
if (this.disposables != null) {
this.disposables.add((DisposableBean) inputChannel);
}
this.definitionRegistry.registerBeanDefinition(inputChannelName,
new RootBeanDefinition(DirectChannel.class, DirectChannel::new));
inputChannel = this.beanFactory.getBean(inputChannelName, MessageChannel.class);
}
else {
throw e;

View File

@@ -1,57 +0,0 @@
/*
* Copyright 2018-2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config.annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.DisposableBean;
/**
* A container for a collection of {@link DisposableBean} it is, itself a
* {@link DisposableBean} and will dispose of its contained beans when it is destroyed.
* Intended for any {@link DisposableBean} that is registered as a singleton in which
* case, the container does not automatically dispose of them.
*
* @author Gary Russell
* @author Artem Bilan
*
* @since 5.1
*
*/
class Disposables implements DisposableBean {
private final List<DisposableBean> disposables = new ArrayList<>();
public final void add(DisposableBean... disposablesToAdd) {
this.disposables.addAll(Arrays.asList(disposablesToAdd));
}
@Override
public void destroy() {
this.disposables.forEach((disposable) -> {
try {
disposable.destroy();
}
catch (@SuppressWarnings("unused") Exception ex) {
// NOSONAR
}
});
}
}

View File

@@ -23,6 +23,7 @@ import java.util.function.Supplier;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
@@ -122,12 +123,9 @@ public class InboundChannelAdapterAnnotationPostProcessor extends
methodInvokingMessageSource.setObject(bean);
methodInvokingMessageSource.setMethod(method);
String messageSourceBeanName = generateHandlerBeanName(beanName, method);
this.beanFactory.registerSingleton(messageSourceBeanName, methodInvokingMessageSource);
messageSource = (MessageSource<?>) this.beanFactory
.initializeBean(methodInvokingMessageSource, messageSourceBeanName);
if (this.disposables != null) {
this.disposables.add(methodInvokingMessageSource);
}
this.definitionRegistry.registerBeanDefinition(messageSourceBeanName,
new RootBeanDefinition(MethodInvokingMessageSource.class, () -> methodInvokingMessageSource));
messageSource = this.beanFactory.getBean(messageSourceBeanName, MessageSource.class);
}
return messageSource;
}

View File

@@ -56,7 +56,6 @@ import org.springframework.integration.annotation.Router;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.Splitter;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.util.MessagingAnnotationUtils;
import org.springframework.util.Assert;
@@ -104,11 +103,6 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
@Override
public void afterPropertiesSet() {
Assert.notNull(this.beanFactory, "BeanFactory must not be null");
if (!this.beanFactory.containsBeanDefinition(IntegrationContextUtils.DISPOSABLES_BEAN_NAME)) {
((BeanDefinitionRegistry) this.beanFactory)
.registerBeanDefinition(IntegrationContextUtils.DISPOSABLES_BEAN_NAME,
new RootBeanDefinition(Disposables.class, Disposables::new));
}
this.postProcessors.put(Filter.class, new FilterAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(Router.class, new RouterAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(Transformer.class, new TransformerAnnotationPostProcessor(this.beanFactory));
@@ -224,17 +218,20 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
}
}
@SuppressWarnings("unchecked")
private void postProcessMethodAndRegisterEndpointIfAny(Object bean, String beanName, Method method,
Class<? extends Annotation> annotationType, List<Annotation> annotations,
MethodAnnotationPostProcessor<?> postProcessor, Method targetMethod) {
Object result = postProcessor.postProcess(bean, beanName, targetMethod, annotations);
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
BeanDefinitionRegistry definitionRegistry = (BeanDefinitionRegistry) beanFactory;
if (result instanceof AbstractEndpoint) {
AbstractEndpoint endpoint = (AbstractEndpoint) result;
String autoStartup = MessagingAnnotationUtils.resolveAttribute(annotations, "autoStartup",
String.class);
if (StringUtils.hasText(autoStartup)) {
autoStartup = getBeanFactory().resolveEmbeddedValue(autoStartup);
autoStartup = beanFactory.resolveEmbeddedValue(autoStartup);
if (StringUtils.hasText(autoStartup)) {
endpoint.setAutoStartup(Boolean.parseBoolean(autoStartup));
}
@@ -242,7 +239,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
String phase = MessagingAnnotationUtils.resolveAttribute(annotations, "phase", String.class);
if (StringUtils.hasText(phase)) {
phase = getBeanFactory().resolveEmbeddedValue(phase);
phase = beanFactory.resolveEmbeddedValue(phase);
if (StringUtils.hasText(phase)) {
endpoint.setPhase(Integer.parseInt(phase));
}
@@ -255,8 +252,9 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
String endpointBeanName = generateBeanName(beanName, method, annotationType);
endpoint.setBeanName(endpointBeanName);
getBeanFactory().registerSingleton(endpointBeanName, endpoint);
getBeanFactory().initializeBean(endpoint, endpointBeanName);
definitionRegistry.registerBeanDefinition(endpointBeanName,
new RootBeanDefinition((Class<AbstractEndpoint>) endpoint.getClass(), () -> endpoint));
beanFactory.getBean(endpointBeanName);
}
}

View File

@@ -1,93 +0,0 @@
/*
* Copyright 2017-2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config.annotation;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.withSettings;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.core.DestinationResolutionException;
/**
* @author Gary Russell
* @author Artem Bilan
*
* @since 4.3.8
*
*/
public class MessagingAnnotationPostProcessorChannelCreationTests {
@Test
public void testAutoCreateChannel() {
ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class,
withSettings().extraInterfaces(BeanDefinitionRegistry.class));
given(beanFactory.getBean("channel", MessageChannel.class)).willThrow(NoSuchBeanDefinitionException.class);
willAnswer(invocation -> invocation.getArgument(0))
.given(beanFactory).initializeBean(any(DirectChannel.class), eq("channel"));
willAnswer(invocation -> invocation.getArgument(0))
.given(beanFactory).initializeBean(any(MessageHandler.class), eq("foo.foo.serviceActivator.handler"));
MessagingAnnotationPostProcessor mapp = new MessagingAnnotationPostProcessor();
mapp.setBeanFactory(beanFactory);
mapp.afterPropertiesSet();
mapp.afterSingletonsInstantiated();
mapp.postProcessAfterInitialization(new Foo(), "foo");
verify(beanFactory).registerSingleton(eq("channel"), any(DirectChannel.class));
}
@Test
public void testDontCreateChannelWhenChannelHasBadDefinition() {
ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class,
withSettings().extraInterfaces(BeanDefinitionRegistry.class));
given(beanFactory.getBean("channel", MessageChannel.class)).willThrow(BeanCreationException.class);
willAnswer(invocation -> invocation.getArgument(0))
.given(beanFactory).initializeBean(any(DirectChannel.class), eq("channel"));
willAnswer(invocation -> invocation.getArgument(0))
.given(beanFactory).initializeBean(any(MessageHandler.class), eq("foo.foo.serviceActivator.handler"));
MessagingAnnotationPostProcessor mapp = new MessagingAnnotationPostProcessor();
mapp.setBeanFactory(beanFactory);
mapp.afterPropertiesSet();
mapp.afterSingletonsInstantiated();
assertThatExceptionOfType(DestinationResolutionException.class)
.isThrownBy(() -> mapp.postProcessAfterInitialization(new Foo(), "foo"))
.withMessageContaining("A bean definition with name 'channel' exists, but failed to be created");
}
public static class Foo {
@ServiceActivator(inputChannel = "channel")
public void foo(String in) {
// empty
}
}
}