INT-4363: Configurable MessageHandlerMethodFactory

JIRA: https://jira.spring.io/browse/INT-4363

The JIRA requests making the `MessageHandlerMethodFactory` a bean.
This is not possible without a major rework of the `MessagingMethodInvokerHelper`.

The problem is that the `InvokerHandlerMethod` s are created before the factory
is initialized. In fact, it only works at all since the IHMs have a hard reference
to the factory's argument resolvers so, when they are initialized in `initialize()`
each handler sees the resolvers.

This really needs to be improved but, since we are so close to GA, the compromise
was to add a `HandlerMethodArgumentResolversHolder` which holds the standard resolvers
which are then wired into the factory (along with the message converter).

Delaying the creation of the `InvokerHandlerMethod` is not trivial; today they are
built in the CTOR; moving it to `initialize()` would prevent the fast failure for
a badly configured bean; the proper solution might be to make the MMIHs beans themselves.

Polishing - PR Comments
This commit is contained in:
Gary Russell
2017-11-14 14:10:01 -05:00
committed by Artem Bilan
parent 7ca20e53f7
commit 986d5fc0cd
5 changed files with 182 additions and 43 deletions

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2017 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
*
* http://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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
/**
* A holder for the configured argument resolvers.
*
* @author Gary Russell
* @since 5.0
*
*/
public class HandlerMethodArgumentResolversHolder {
private final List<HandlerMethodArgumentResolver> resolvers;
public HandlerMethodArgumentResolversHolder(List<HandlerMethodArgumentResolver> resolvers) {
this.resolvers = new ArrayList<>(resolvers);
}
public List<HandlerMethodArgumentResolver> getResolvers() {
return Collections.unmodifiableList(this.resolvers);
}
public void addResolver(HandlerMethodArgumentResolver resolver) {
this.resolvers.add(resolver);
}
public boolean removeResolver(HandlerMethodArgumentResolver resolver) {
return this.resolvers.remove(resolver);
}
}

View File

@@ -35,6 +35,7 @@ import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedSet;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.Resource;
@@ -46,11 +47,16 @@ import org.springframework.integration.channel.DefaultHeaderChannelRegistry;
import org.springframework.integration.config.annotation.MessagingAnnotationPostProcessor;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.context.IntegrationProperties;
import org.springframework.integration.handler.support.CollectionArgumentResolver;
import org.springframework.integration.handler.support.MapArgumentResolver;
import org.springframework.integration.handler.support.PayloadExpressionArgumentResolver;
import org.springframework.integration.handler.support.PayloadsArgumentResolver;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
import org.springframework.integration.support.converter.ConfigurableCompositeMessageConverter;
import org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter;
import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.util.ClassUtils;
/**
@@ -98,6 +104,8 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, Bean
registerDefaultConfiguringBeanFactoryPostProcessor(registry);
registerDefaultDatatypeChannelMessageConverter(registry);
registerArgumentResolverMessageConverter(registry);
registerArgumentResolvers(registry);
registerListCapableArgumentResolvers(registry);
if (importingClassMetadata != null) {
registerMessagingAnnotationPostProcessors(importingClassMetadata, registry);
}
@@ -414,14 +422,50 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, Bean
* @param registry the registry.
*/
private void registerArgumentResolverMessageConverter(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) {
BeanDefinitionBuilder postProcessorBuilder = BeanDefinitionBuilder
if (!registry.containsBeanDefinition(
IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) {
BeanDefinitionBuilder converterBuilder = BeanDefinitionBuilder
.genericBeanDefinition(ConfigurableCompositeMessageConverter.class);
registry.registerBeanDefinition(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME,
postProcessorBuilder.getBeanDefinition());
converterBuilder.getBeanDefinition());
}
}
/**
* Register the default {@link HandlerMethodArgumentResolversHolder} for handler
* method invocation.
* @param registry the registry.
*/
private void registerArgumentResolvers(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(IntegrationContextUtils.ARGUMENT_RESOLVERS_BEAN_NAME)) {
registry.registerBeanDefinition(IntegrationContextUtils.ARGUMENT_RESOLVERS_BEAN_NAME,
internalArgumentResolversBuilder(registry, false).getBeanDefinition());
}
}
/**
* Register the default {@link HandlerMethodArgumentResolversHolder} for handler
* method invocation for lists.
* @param registry the registry.
*/
private void registerListCapableArgumentResolvers(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(
IntegrationContextUtils.LIST_ARGUMENT_RESOLVERS_BEAN_NAME)) {
registry.registerBeanDefinition(IntegrationContextUtils.LIST_ARGUMENT_RESOLVERS_BEAN_NAME,
internalArgumentResolversBuilder(registry, true).getBeanDefinition());
}
}
private BeanDefinitionBuilder internalArgumentResolversBuilder(BeanDefinitionRegistry registry,
boolean listCapable) {
ManagedList<HandlerMethodArgumentResolver> resolvers = new ManagedList<>();
resolvers.add(new PayloadExpressionArgumentResolver());
resolvers.add(new PayloadsArgumentResolver());
resolvers.add(new CollectionArgumentResolver(listCapable));
resolvers.add(new MapArgumentResolver());
return BeanDefinitionBuilder.genericBeanDefinition(HandlerMethodArgumentResolversHolder.class)
.addConstructorArgValue(resolvers);
}
private void registerMessageBuilderFactory(BeanDefinitionRegistry registry) {
boolean alreadyRegistered = false;

View File

@@ -61,18 +61,22 @@ public abstract class IntegrationContextUtils {
public static final String MESSAGING_ANNOTATION_POSTPROCESSOR_NAME = IntegrationConfigUtils.BASE_PACKAGE
+ ".internalMessagingAnnotationPostProcessor";
public static final String PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME = IntegrationConfigUtils.BASE_PACKAGE +
".internalPublisherAnnotationBeanPostProcessor";
public static final String PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME = IntegrationConfigUtils.BASE_PACKAGE
+ ".internalPublisherAnnotationBeanPostProcessor";
public static final String INTEGRATION_CONFIGURATION_POST_PROCESSOR_BEAN_NAME = "IntegrationConfigurationBeanFactoryPostProcessor";
public static final String INTEGRATION_CONFIGURATION_POST_PROCESSOR_BEAN_NAME =
"IntegrationConfigurationBeanFactoryPostProcessor";
public static final String INTEGRATION_MESSAGE_HISTORY_CONFIGURER_BEAN_NAME = "messageHistoryConfigurer";
public static final String INTEGRATION_DATATYPE_CHANNEL_MESSAGE_CONVERTER_BEAN_NAME = "datatypeChannelMessageConverter";
public static final String INTEGRATION_DATATYPE_CHANNEL_MESSAGE_CONVERTER_BEAN_NAME =
"datatypeChannelMessageConverter";
public static final String INTEGRATION_FIXED_SUBSCRIBER_CHANNEL_BPP_BEAN_NAME = "fixedSubscriberChannelBeanFactoryPostProcessor";
public static final String INTEGRATION_FIXED_SUBSCRIBER_CHANNEL_BPP_BEAN_NAME =
"fixedSubscriberChannelBeanFactoryPostProcessor";
public static final String GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME = "globalChannelInterceptorProcessor";
public static final String GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME =
"globalChannelInterceptorProcessor";
public static final String TO_STRING_FRIENDLY_JSON_NODE_TO_STRING_CONVERTER_BEAN_NAME =
"toStringFriendlyJsonNodeToStringConverter";
@@ -84,7 +88,12 @@ public abstract class IntegrationContextUtils {
public static final String SPEL_PROPERTY_ACCESSOR_REGISTRAR_BEAN_NAME = "spelPropertyAccessorRegistrar";
public static final String ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME = "integrationArgumentResolverMessageConverter";
public static final String ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME =
"integrationArgumentResolverMessageConverter";
public static final String ARGUMENT_RESOLVERS_BEAN_NAME = "integrationArgumentResolvers";
public static final String LIST_ARGUMENT_RESOLVERS_BEAN_NAME = "integrationListArgumentResolvers";
/**
* @param beanFactory BeanFactory for lookup, must not be null.

View File

@@ -69,7 +69,8 @@ public class CollectionArgumentResolver extends AbstractExpressionEvaluator
if (this.canProcessMessageList) {
Assert.state(value instanceof Collection,
"This Argument Resolver only supports messages with a payload of Collection<Message<?>>");
"This Argument Resolver only supports messages with a payload of Collection<Message<?>>, "
+ "payload is: " + value.getClass());
Collection<Message<?>> messages = (Collection<Message<?>>) value;
parameter.increaseNestingLevel();

View File

@@ -42,6 +42,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
@@ -69,6 +70,7 @@ import org.springframework.integration.annotation.Default;
import org.springframework.integration.annotation.Payloads;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.UseSpelInvoker;
import org.springframework.integration.config.HandlerMethodArgumentResolversHolder;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.handler.support.CollectionArgumentResolver;
import org.springframework.integration.handler.support.MapArgumentResolver;
@@ -245,7 +247,8 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
if (beanExpressionResolver != null) {
this.resolver = beanExpressionResolver;
}
this.expressionContext = new BeanExpressionContext((ConfigurableListableBeanFactory) beanFactory, null);
this.expressionContext =
new BeanExpressionContext((ConfigurableListableBeanFactory) beanFactory, null);
}
}
@@ -257,7 +260,6 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
}
}
@SuppressWarnings("unchecked")
public T process(Message<?> message) throws Exception {
Message<?> messageToProcess = possiblyConvert(message);
ParametersWrapper parameters = new ParametersWrapper(messageToProcess);
@@ -524,43 +526,74 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
private synchronized void initialize() throws Exception {
if (!this.initialized) {
PayloadExpressionArgumentResolver payloadExpressionArgumentResolver =
new PayloadExpressionArgumentResolver();
payloadExpressionArgumentResolver.setBeanFactory(getBeanFactory());
PayloadsArgumentResolver payloadsArgumentResolver = new PayloadsArgumentResolver();
payloadsArgumentResolver.setBeanFactory(getBeanFactory());
CollectionArgumentResolver collectionArgumentResolver =
new CollectionArgumentResolver(this.canProcessMessageList);
collectionArgumentResolver.setBeanFactory(getBeanFactory());
MapArgumentResolver mapArgumentResolver = new MapArgumentResolver();
mapArgumentResolver.setBeanFactory(getBeanFactory());
List<HandlerMethodArgumentResolver> customArgumentResolvers = new LinkedList<>();
customArgumentResolvers.add(payloadExpressionArgumentResolver);
customArgumentResolvers.add(payloadsArgumentResolver);
customArgumentResolvers.add(collectionArgumentResolver);
customArgumentResolvers.add(mapArgumentResolver);
this.messageHandlerMethodFactory.setCustomArgumentResolvers(customArgumentResolvers);
if (getBeanFactory() != null &&
getBeanFactory()
.containsBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) {
this.messageHandlerMethodFactory
.setMessageConverter(getBeanFactory()
.getBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME,
MessageConverter.class));
if (getBeanFactory() != null
&& getBeanFactory().containsBean(
IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) {
try {
this.messageHandlerMethodFactory.setMessageConverter(getBeanFactory().getBean(
IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME,
MessageConverter.class));
if (this.canProcessMessageList) {
this.messageHandlerMethodFactory.setCustomArgumentResolvers(getBeanFactory().getBean(
IntegrationContextUtils.LIST_ARGUMENT_RESOLVERS_BEAN_NAME,
HandlerMethodArgumentResolversHolder.class).getResolvers());
}
else {
this.messageHandlerMethodFactory.setCustomArgumentResolvers(getBeanFactory().getBean(
IntegrationContextUtils.ARGUMENT_RESOLVERS_BEAN_NAME,
HandlerMethodArgumentResolversHolder.class).getResolvers());
}
}
catch (NoSuchBeanDefinitionException e) {
configureLocalMessageHandlerFactory();
}
}
else {
configureLocalMessageHandlerFactory();
}
this.messageHandlerMethodFactory.afterPropertiesSet();
prepareEvaluationContext();
this.initialized = true;
}
}
/*
* This should not be needed in production but we have many tests
* that don't run in an application context.
*/
private void configureLocalMessageHandlerFactory() {
PayloadExpressionArgumentResolver payloadExpressionArgumentResolver =
new PayloadExpressionArgumentResolver();
payloadExpressionArgumentResolver.setBeanFactory(getBeanFactory());
PayloadsArgumentResolver payloadsArgumentResolver = new PayloadsArgumentResolver();
payloadsArgumentResolver.setBeanFactory(getBeanFactory());
CollectionArgumentResolver collectionArgumentResolver =
new CollectionArgumentResolver(this.canProcessMessageList);
collectionArgumentResolver.setBeanFactory(getBeanFactory());
MapArgumentResolver mapArgumentResolver = new MapArgumentResolver();
mapArgumentResolver.setBeanFactory(getBeanFactory());
List<HandlerMethodArgumentResolver> customArgumentResolvers = new LinkedList<>();
customArgumentResolvers.add(payloadExpressionArgumentResolver);
customArgumentResolvers.add(payloadsArgumentResolver);
customArgumentResolvers.add(collectionArgumentResolver);
customArgumentResolvers.add(mapArgumentResolver);
this.messageHandlerMethodFactory.setCustomArgumentResolvers(customArgumentResolvers);
if (getBeanFactory() != null &&
getBeanFactory()
.containsBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) {
this.messageHandlerMethodFactory
.setMessageConverter(getBeanFactory()
.getBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME,
MessageConverter.class));
}
}
@SuppressWarnings("unchecked")
private T invokeHandlerMethod(HandlerMethod handlerMethod, ParametersWrapper parameters) throws Exception {
try {