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:
committed by
Artem Bilan
parent
7ca20e53f7
commit
986d5fc0cd
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user