INT-4402: Apply global interceptors at runtime
JIRA: https://jira.spring.io/browse/INT-4402 Make `GlobalChannelInterceptorProcessor` as a `BeanPostProcessor`, so it can apply global `ChannelInterceptor`s to any initialized channel beans, even those created at runtime, like in case of dynamic flows with Java DSL **Cherry-pick to 4.3.x** * Add `postProcessDynamicBeans` global property with `false` by default * Rely on the `postProcessDynamicBeans` property in the `GlobalChannelInterceptorProcessor.postProcessAfterInitialization()` * Add `postProcessDynamicBeans=true` to the `GlobalChannelInterceptorTests-context.xml` to be sure in the test-case that option works * Document the option and effect from the global channel interceptors
This commit is contained in:
committed by
Gary Russell
parent
2f038384ec
commit
c7c6264642
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -23,6 +23,7 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -33,10 +34,13 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.core.OrderComparator;
|
||||
import org.springframework.integration.channel.ChannelInterceptorAware;
|
||||
import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper;
|
||||
import org.springframework.integration.channel.interceptor.VetoCapableInterceptor;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.integration.context.IntegrationProperties;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -44,7 +48,8 @@ import org.springframework.util.PatternMatchUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Will apply global interceptors to channels (<channel-interceptor>).
|
||||
* This class applies global interceptors ({@code <channel-interceptor>} or {@code @GlobalChannelInterceptor})
|
||||
* to message channels beans.
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Mark Fisher
|
||||
@@ -52,7 +57,8 @@ import org.springframework.util.StringUtils;
|
||||
* @author Gary Russell
|
||||
* @since 2.0
|
||||
*/
|
||||
final class GlobalChannelInterceptorProcessor implements BeanFactoryAware, SmartInitializingSingleton {
|
||||
public final class GlobalChannelInterceptorProcessor
|
||||
implements BeanFactoryAware, SmartInitializingSingleton, BeanPostProcessor {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(GlobalChannelInterceptorProcessor.class);
|
||||
|
||||
@@ -67,6 +73,8 @@ final class GlobalChannelInterceptorProcessor implements BeanFactoryAware, Smart
|
||||
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
private volatile boolean singletonsInstantiated;
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
Assert.isInstanceOf(ListableBeanFactory.class, beanFactory);
|
||||
@@ -95,12 +103,34 @@ final class GlobalChannelInterceptorProcessor implements BeanFactoryAware, Smart
|
||||
addMatchingInterceptors(entry.getValue(), entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Remove this logic in 5.1
|
||||
Properties integrationProperties = IntegrationContextUtils.getIntegrationProperties(this.beanFactory);
|
||||
|
||||
this.singletonsInstantiated =
|
||||
Boolean.parseBoolean(integrationProperties.getProperty(
|
||||
IntegrationProperties.POST_PROCESS_DYNAMIC_BEANS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (this.singletonsInstantiated && bean instanceof ChannelInterceptorAware) {
|
||||
addMatchingInterceptors((ChannelInterceptorAware) bean, beanName);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds any interceptor whose pattern matches against the channel's name.
|
||||
* Add any interceptor whose pattern matches against the channel's name.
|
||||
* @param channel the message channel to add interceptors.
|
||||
* @param beanName the message channel bean name to match the pattern.
|
||||
*/
|
||||
private void addMatchingInterceptors(ChannelInterceptorAware channel, String beanName) {
|
||||
public void addMatchingInterceptors(ChannelInterceptorAware channel, String beanName) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Applying global interceptors on channel '" + beanName + "'");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 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.
|
||||
@@ -87,6 +87,12 @@ public final class IntegrationProperties {
|
||||
*/
|
||||
public static final String ENDPOINTS_NO_AUTO_STARTUP = INTEGRATION_PROPERTIES_PREFIX + "endpoints.noAutoStartup";
|
||||
|
||||
/**
|
||||
* Whether {@link org.springframework.beans.factory.config.BeanPostProcessor}s should process beans registered at runtime.
|
||||
* Will be removed in 5.1.
|
||||
*/
|
||||
public static final String POST_PROCESS_DYNAMIC_BEANS = INTEGRATION_PROPERTIES_PREFIX + "postProcessDynamicBeans";
|
||||
|
||||
|
||||
private static Properties defaults;
|
||||
|
||||
|
||||
@@ -8,3 +8,4 @@ spring.integration.messagingGateway.convertReceiveMessage=false
|
||||
# Defaults to MessageHeaders.ID and MessageHeaders.TIMESTAMP
|
||||
spring.integration.readOnly.headers=
|
||||
spring.integration.endpoints.noAutoStartup=
|
||||
spring.integration.postProcessDynamicBeans=false
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:util="http://www.springframework.org/schema/util"
|
||||
xmlns:beans="http://www.springframework.org/schema/c"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
|
||||
|
||||
<util:properties id="integrationGlobalProperties">
|
||||
<prop key="spring.integration.postProcessDynamicBeans">true</prop>
|
||||
</util:properties>
|
||||
|
||||
<int:channel id="inputA">
|
||||
<int:interceptors>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package org.springframework.integration.channel.interceptor;
|
||||
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -28,9 +32,11 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.integration.channel.ChannelInterceptorAware;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
@@ -49,7 +55,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
public class GlobalChannelInterceptorTests {
|
||||
|
||||
@Autowired
|
||||
ApplicationContext applicationContext;
|
||||
ConfigurableApplicationContext applicationContext;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("inputC")
|
||||
@@ -126,6 +132,18 @@ public class GlobalChannelInterceptorTests {
|
||||
Assert.assertTrue(interceptorNames.contains("interceptor-eleven"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicMessageChannelBeanWithAutoGlobalChannelInterceptor() {
|
||||
DirectChannel testChannel = new DirectChannel();
|
||||
ConfigurableListableBeanFactory beanFactory = this.applicationContext.getBeanFactory();
|
||||
beanFactory.initializeBean(testChannel, "testChannel");
|
||||
|
||||
List<ChannelInterceptor> channelInterceptors = testChannel.getChannelInterceptors();
|
||||
|
||||
assertEquals(2, channelInterceptors.size());
|
||||
assertThat(channelInterceptors.get(0), instanceOf(SampleInterceptor.class));
|
||||
assertThat(channelInterceptors.get(0), instanceOf(SampleInterceptor.class));
|
||||
}
|
||||
|
||||
public static class SampleInterceptor extends ChannelInterceptorAdapter {
|
||||
|
||||
@@ -139,17 +157,21 @@ public class GlobalChannelInterceptorTests {
|
||||
this.testIdentifier = testIdentifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<?> postReceive(Message<?> message, MessageChannel channel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preReceive(MessageChannel channel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<?> preSend(Message<?> message, MessageChannel channel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -723,6 +723,9 @@ To inject a global interceptor _BEFORE_ the existing interceptors, use a negativ
|
||||
NOTE: Note that both the `order` and `pattern` attributes are optional.
|
||||
The default value for `order` will be 0 and for `pattern`, the default is '*' (to match all channels).
|
||||
|
||||
Starting with _version 4.3.15_, you can configure a property `spring.integration.postProcessDynamicBeans = true` to apply any global interceptors to dynamically created `MessageChannel` beans.
|
||||
See <<global-properties>> for more information.
|
||||
|
||||
[[channel-wiretap]]
|
||||
===== Wire Tap
|
||||
|
||||
|
||||
@@ -208,6 +208,7 @@ spring.integration.messagingTemplate.throwExceptionOnLateReply=false <5>
|
||||
spring.integration.messagingAnnotations.require.componentAnnotation=false <6>
|
||||
spring.integration.readOnly.headers= <7>
|
||||
spring.integration.endpoints.noAutoStartup= <8>
|
||||
spring.integration.postProcessDynamicBeans=false <9>
|
||||
----
|
||||
|
||||
<1> When true, `input-channel` s will be automatically declared as `DirectChannel` s when not explicitly found in the
|
||||
@@ -237,6 +238,10 @@ These endpoints can be started later manually by their bean name via `Control Bu
|
||||
The effect of this global property can be explicitly overridden by specifying `auto-startup` XML or `autoStartup` annotation attribute, or via call to the `AbstractEndpoint.setAutoStartup()` in bean definition.
|
||||
_Since version 4.3.12_
|
||||
|
||||
<9> A boolean flag to indicate that `BeanPostProcessor` s should post-process beans registered at runtime, e.g. message channels created via `IntegrationFlowContext` can be supplied with global channel interceptors.
|
||||
_Since version 4.3.15_
|
||||
|
||||
|
||||
These properties can be overridden by adding a file `/META-INF/spring.integration.properties` to the classpath.
|
||||
It is not necessary to provide all the properties, just those that you want to override.
|
||||
|
||||
@@ -429,7 +434,7 @@ With this endpoint using the default poller:
|
||||
public class AnnotationService {
|
||||
|
||||
@Transformer(inputChannel = "aPollableChannel", outputChannel = "output"
|
||||
poller = @Poller("myPoller")
|
||||
poller = @Poller("myPoller"))
|
||||
public String handle(String payload) {
|
||||
...
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user