Skip to content

Commit c7c6264

Browse files
artembilangaryrussell
authored andcommitted
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
1 parent 2f03838 commit c7c6264

File tree

7 files changed

+89
-16
lines changed

7 files changed

+89
-16
lines changed

spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorProcessor.java

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import java.util.Map;
2525
import java.util.Map.Entry;
26+
import java.util.Properties;
2627
import java.util.Set;
2728

2829
import org.apache.commons.logging.Log;
@@ -33,26 +34,31 @@
3334
import org.springframework.beans.factory.BeanFactoryAware;
3435
import org.springframework.beans.factory.ListableBeanFactory;
3536
import org.springframework.beans.factory.SmartInitializingSingleton;
37+
import org.springframework.beans.factory.config.BeanPostProcessor;
3638
import org.springframework.core.OrderComparator;
3739
import org.springframework.integration.channel.ChannelInterceptorAware;
3840
import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper;
3941
import org.springframework.integration.channel.interceptor.VetoCapableInterceptor;
42+
import org.springframework.integration.context.IntegrationContextUtils;
43+
import org.springframework.integration.context.IntegrationProperties;
4044
import org.springframework.messaging.support.ChannelInterceptor;
4145
import org.springframework.util.Assert;
4246
import org.springframework.util.CollectionUtils;
4347
import org.springframework.util.PatternMatchUtils;
4448
import org.springframework.util.StringUtils;
4549

4650
/**
47-
* Will apply global interceptors to channels (<channel-interceptor>).
51+
* This class applies global interceptors ({@code <channel-interceptor>} or {@code @GlobalChannelInterceptor})
52+
* to message channels beans.
4853
*
4954
* @author Oleg Zhurakousky
5055
* @author Mark Fisher
5156
* @author Artem Bilan
5257
* @author Gary Russell
5358
* @since 2.0
5459
*/
55-
final class GlobalChannelInterceptorProcessor implements BeanFactoryAware, SmartInitializingSingleton {
60+
public final class GlobalChannelInterceptorProcessor
61+
implements BeanFactoryAware, SmartInitializingSingleton, BeanPostProcessor {
5662

5763
private static final Log logger = LogFactory.getLog(GlobalChannelInterceptorProcessor.class);
5864

@@ -67,6 +73,8 @@ final class GlobalChannelInterceptorProcessor implements BeanFactoryAware, Smart
6773

6874
private ListableBeanFactory beanFactory;
6975

76+
private volatile boolean singletonsInstantiated;
77+
7078
@Override
7179
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
7280
Assert.isInstanceOf(ListableBeanFactory.class, beanFactory);
@@ -95,12 +103,34 @@ public void afterSingletonsInstantiated() {
95103
addMatchingInterceptors(entry.getValue(), entry.getKey());
96104
}
97105
}
106+
107+
// TODO Remove this logic in 5.1
108+
Properties integrationProperties = IntegrationContextUtils.getIntegrationProperties(this.beanFactory);
109+
110+
this.singletonsInstantiated =
111+
Boolean.parseBoolean(integrationProperties.getProperty(
112+
IntegrationProperties.POST_PROCESS_DYNAMIC_BEANS));
113+
}
114+
115+
@Override
116+
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
117+
return bean;
118+
}
119+
120+
@Override
121+
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
122+
if (this.singletonsInstantiated && bean instanceof ChannelInterceptorAware) {
123+
addMatchingInterceptors((ChannelInterceptorAware) bean, beanName);
124+
}
125+
return bean;
98126
}
99127

100128
/**
101-
* Adds any interceptor whose pattern matches against the channel's name.
129+
* Add any interceptor whose pattern matches against the channel's name.
130+
* @param channel the message channel to add interceptors.
131+
* @param beanName the message channel bean name to match the pattern.
102132
*/
103-
private void addMatchingInterceptors(ChannelInterceptorAware channel, String beanName) {
133+
public void addMatchingInterceptors(ChannelInterceptorAware channel, String beanName) {
104134
if (logger.isDebugEnabled()) {
105135
logger.debug("Applying global interceptors on channel '" + beanName + "'");
106136
}

spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationProperties.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2017 the original author or authors.
2+
* Copyright 2014-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -87,6 +87,12 @@ public final class IntegrationProperties {
8787
*/
8888
public static final String ENDPOINTS_NO_AUTO_STARTUP = INTEGRATION_PROPERTIES_PREFIX + "endpoints.noAutoStartup";
8989

90+
/**
91+
* Whether {@link org.springframework.beans.factory.config.BeanPostProcessor}s should process beans registered at runtime.
92+
* Will be removed in 5.1.
93+
*/
94+
public static final String POST_PROCESS_DYNAMIC_BEANS = INTEGRATION_PROPERTIES_PREFIX + "postProcessDynamicBeans";
95+
9096

9197
private static Properties defaults;
9298

spring-integration-core/src/main/resources/META-INF/spring.integration.default.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ spring.integration.messagingGateway.convertReceiveMessage=false
88
# Defaults to MessageHeaders.ID and MessageHeaders.TIMESTAMP
99
spring.integration.readOnly.headers=
1010
spring.integration.endpoints.noAutoStartup=
11+
spring.integration.postProcessDynamicBeans=false

spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorTests-context.xml

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<beans xmlns="http://www.springframework.org/schema/beans"
3-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4-
xmlns:int="http://www.springframework.org/schema/integration"
5-
xmlns:p="http://www.springframework.org/schema/p"
6-
xmlns:aop="http://www.springframework.org/schema/aop"
7-
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns:int="http://www.springframework.org/schema/integration"
5+
xmlns:p="http://www.springframework.org/schema/p"
6+
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:util="http://www.springframework.org/schema/util"
7+
xmlns:beans="http://www.springframework.org/schema/c"
8+
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
89
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
9-
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
10+
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
11+
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
12+
13+
<util:properties id="integrationGlobalProperties">
14+
<prop key="spring.integration.postProcessDynamicBeans">true</prop>
15+
</util:properties>
1016

1117
<int:channel id="inputA">
1218
<int:interceptors>

spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorTests.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.integration.channel.interceptor;
1818

19+
import static org.hamcrest.Matchers.instanceOf;
20+
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertThat;
22+
1923
import java.util.ArrayList;
2024
import java.util.List;
2125
import java.util.Map;
@@ -28,9 +32,11 @@
2832

2933
import org.springframework.beans.factory.annotation.Autowired;
3034
import org.springframework.beans.factory.annotation.Qualifier;
31-
import org.springframework.context.ApplicationContext;
35+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
36+
import org.springframework.context.ConfigurableApplicationContext;
3237
import org.springframework.core.Ordered;
3338
import org.springframework.integration.channel.ChannelInterceptorAware;
39+
import org.springframework.integration.channel.DirectChannel;
3440
import org.springframework.messaging.Message;
3541
import org.springframework.messaging.MessageChannel;
3642
import org.springframework.messaging.support.ChannelInterceptor;
@@ -49,7 +55,7 @@
4955
public class GlobalChannelInterceptorTests {
5056

5157
@Autowired
52-
ApplicationContext applicationContext;
58+
ConfigurableApplicationContext applicationContext;
5359

5460
@Autowired
5561
@Qualifier("inputC")
@@ -126,6 +132,18 @@ public void testWildCardPatternMatch() {
126132
Assert.assertTrue(interceptorNames.contains("interceptor-eleven"));
127133
}
128134

135+
@Test
136+
public void testDynamicMessageChannelBeanWithAutoGlobalChannelInterceptor() {
137+
DirectChannel testChannel = new DirectChannel();
138+
ConfigurableListableBeanFactory beanFactory = this.applicationContext.getBeanFactory();
139+
beanFactory.initializeBean(testChannel, "testChannel");
140+
141+
List<ChannelInterceptor> channelInterceptors = testChannel.getChannelInterceptors();
142+
143+
assertEquals(2, channelInterceptors.size());
144+
assertThat(channelInterceptors.get(0), instanceOf(SampleInterceptor.class));
145+
assertThat(channelInterceptors.get(0), instanceOf(SampleInterceptor.class));
146+
}
129147

130148
public static class SampleInterceptor extends ChannelInterceptorAdapter {
131149

@@ -139,17 +157,21 @@ public void setTestIdentifier(String testIdentifier) {
139157
this.testIdentifier = testIdentifier;
140158
}
141159

160+
@Override
142161
public Message<?> postReceive(Message<?> message, MessageChannel channel) {
143162
return null;
144163
}
145164

165+
@Override
146166
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
147167
}
148168

169+
@Override
149170
public boolean preReceive(MessageChannel channel) {
150171
return false;
151172
}
152173

174+
@Override
153175
public Message<?> preSend(Message<?> message, MessageChannel channel) {
154176
return null;
155177
}

src/reference/asciidoc/channel.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,9 @@ To inject a global interceptor _BEFORE_ the existing interceptors, use a negativ
723723
NOTE: Note that both the `order` and `pattern` attributes are optional.
724724
The default value for `order` will be 0 and for `pattern`, the default is '*' (to match all channels).
725725

726+
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.
727+
See <<global-properties>> for more information.
728+
726729
[[channel-wiretap]]
727730
===== Wire Tap
728731

src/reference/asciidoc/configuration.adoc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ spring.integration.messagingTemplate.throwExceptionOnLateReply=false <5>
208208
spring.integration.messagingAnnotations.require.componentAnnotation=false <6>
209209
spring.integration.readOnly.headers= <7>
210210
spring.integration.endpoints.noAutoStartup= <8>
211+
spring.integration.postProcessDynamicBeans=false <9>
211212
----
212213

213214
<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
237238
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.
238239
_Since version 4.3.12_
239240

241+
<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.
242+
_Since version 4.3.15_
243+
244+
240245
These properties can be overridden by adding a file `/META-INF/spring.integration.properties` to the classpath.
241246
It is not necessary to provide all the properties, just those that you want to override.
242247

@@ -429,7 +434,7 @@ With this endpoint using the default poller:
429434
public class AnnotationService {
430435
431436
@Transformer(inputChannel = "aPollableChannel", outputChannel = "output"
432-
poller = @Poller("myPoller")
437+
poller = @Poller("myPoller"))
433438
public String handle(String payload) {
434439
...
435440
}

0 commit comments

Comments
 (0)