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:
Artem Bilan
2018-02-15 11:02:28 -05:00
committed by Gary Russell
parent 2f038384ec
commit c7c6264642
7 changed files with 89 additions and 16 deletions

View File

@@ -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 + "'");
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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) {
...
}