Skip to content

Commit 8001842

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 a77def1 commit 8001842

File tree

7 files changed

+80
-17
lines changed

7 files changed

+80
-17
lines changed

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

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 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,18 +34,22 @@
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.integration.util.PatternMatchUtils;
4145
import org.springframework.messaging.support.ChannelInterceptor;
4246
import org.springframework.util.Assert;
4347
import org.springframework.util.CollectionUtils;
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
@@ -54,7 +59,8 @@
5459
*
5560
* @since 2.0
5661
*/
57-
final class GlobalChannelInterceptorProcessor implements BeanFactoryAware, SmartInitializingSingleton {
62+
public final class GlobalChannelInterceptorProcessor
63+
implements BeanFactoryAware, SmartInitializingSingleton, BeanPostProcessor {
5864

5965
private static final Log logger = LogFactory.getLog(GlobalChannelInterceptorProcessor.class);
6066

@@ -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);
@@ -96,12 +104,29 @@ public void afterSingletonsInstantiated() {
96104
addMatchingInterceptors(entry.getValue(), entry.getKey());
97105
}
98106
}
107+
108+
// TODO Remove this logic in 5.1
109+
Properties integrationProperties = IntegrationContextUtils.getIntegrationProperties(this.beanFactory);
110+
111+
this.singletonsInstantiated =
112+
Boolean.parseBoolean(integrationProperties.getProperty(
113+
IntegrationProperties.POST_PROCESS_DYNAMIC_BEANS));
114+
}
115+
116+
@Override
117+
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
118+
if (this.singletonsInstantiated && bean instanceof ChannelInterceptorAware) {
119+
addMatchingInterceptors((ChannelInterceptorAware) bean, beanName);
120+
}
121+
return bean;
99122
}
100123

101124
/**
102-
* Adds any interceptor whose pattern matches against the channel's name.
125+
* Add any interceptor whose pattern matches against the channel's name.
126+
* @param channel the message channel to add interceptors.
127+
* @param beanName the message channel bean name to match the pattern.
103128
*/
104-
private void addMatchingInterceptors(ChannelInterceptorAware channel, String beanName) {
129+
public void addMatchingInterceptors(ChannelInterceptorAware channel, String beanName) {
105130
if (logger.isDebugEnabled()) {
106131
logger.debug("Applying global interceptors on channel '" + beanName + "'");
107132
}

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.
@@ -77,6 +77,12 @@ public final class IntegrationProperties {
7777
*/
7878
public static final String ENDPOINTS_NO_AUTO_STARTUP = INTEGRATION_PROPERTIES_PREFIX + "endpoints.noAutoStartup";
7979

80+
/**
81+
* Whether {@link org.springframework.beans.factory.config.BeanPostProcessor}s should process beans registered at runtime.
82+
* Will be removed in 5.1.
83+
*/
84+
public static final String POST_PROCESS_DYNAMIC_BEANS = INTEGRATION_PROPERTIES_PREFIX + "postProcessDynamicBeans";
85+
8086

8187
private static Properties defaults;
8288

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
@@ -6,3 +6,4 @@ spring.integration.messagingTemplate.throwExceptionOnLateReply=false
66
# Defaults to MessageHeaders.ID and MessageHeaders.TIMESTAMP
77
spring.integration.readOnly.headers=
88
spring.integration.endpoints.noAutoStartup=
9+
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: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 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,8 +16,10 @@
1616

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

19+
import static org.hamcrest.Matchers.instanceOf;
1920
import static org.junit.Assert.assertEquals;
2021
import static org.junit.Assert.assertNotNull;
22+
import static org.junit.Assert.assertThat;
2123
import static org.junit.Assert.assertTrue;
2224

2325
import java.util.ArrayList;
@@ -31,9 +33,11 @@
3133

3234
import org.springframework.beans.factory.annotation.Autowired;
3335
import org.springframework.beans.factory.annotation.Qualifier;
34-
import org.springframework.context.ApplicationContext;
36+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
37+
import org.springframework.context.ConfigurableApplicationContext;
3538
import org.springframework.core.Ordered;
3639
import org.springframework.integration.channel.ChannelInterceptorAware;
40+
import org.springframework.integration.channel.DirectChannel;
3741
import org.springframework.messaging.Message;
3842
import org.springframework.messaging.MessageChannel;
3943
import org.springframework.messaging.support.ChannelInterceptor;
@@ -54,7 +58,7 @@
5458
public class GlobalChannelInterceptorTests {
5559

5660
@Autowired
57-
ApplicationContext applicationContext;
61+
ConfigurableApplicationContext applicationContext;
5862

5963
@Autowired
6064
@Qualifier("inputC")
@@ -143,6 +147,18 @@ public void testWildCardPatternMatch() {
143147
assertTrue(interceptorNames.contains("interceptor-eleven"));
144148
}
145149

150+
@Test
151+
public void testDynamicMessageChannelBeanWithAutoGlobalChannelInterceptor() {
152+
DirectChannel testChannel = new DirectChannel();
153+
ConfigurableListableBeanFactory beanFactory = this.applicationContext.getBeanFactory();
154+
beanFactory.initializeBean(testChannel, "testChannel");
155+
156+
List<ChannelInterceptor> channelInterceptors = testChannel.getChannelInterceptors();
157+
158+
assertEquals(2, channelInterceptors.size());
159+
assertThat(channelInterceptors.get(0), instanceOf(SampleInterceptor.class));
160+
assertThat(channelInterceptors.get(0), instanceOf(SampleInterceptor.class));
161+
}
146162

147163
public static class SampleInterceptor extends ChannelInterceptorAdapter {
148164

src/reference/asciidoc/channel.adoc

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

737+
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.
738+
See <<global-properties>> for more information.
739+
737740
[[channel-wiretap]]
738741
===== Wire Tap
739742

src/reference/asciidoc/configuration.adoc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ spring.integration.taskScheduler.poolSize=10 <4>
223223
spring.integration.messagingTemplate.throwExceptionOnLateReply=false <5>
224224
spring.integration.readOnly.headers= <6>
225225
spring.integration.endpoints.noAutoStartup= <7>
226+
spring.integration.postProcessDynamicBeans=false <8>
226227
----
227228

228229
<1> When true, `input-channel` s will be automatically declared as `DirectChannel` s when not explicitly found in the
@@ -245,11 +246,16 @@ expecting a reply - because the sending thread has timed out, or already receive
245246
The list is used by the `DefaultMessageBuilderFactory` bean and propagated to the `IntegrationMessageHeaderAccessor` instances (see <<message-header-accessor>>), used to build messages via `MessageBuilder` (see <<message-builder>>).
246247
By default only `MessageHeaders.ID` and `MessageHeaders.TIMESTAMP` are not copied during message building.
247248
_Since version 4.3.2_
249+
248250
<7> A comma-separated list of `AbstractEndpoint` bean names patterns (`xxx*`, `*xxx`, `*xxx*` or `xxx*yyy`) which should not be started automatically during application startup.
249251
These endpoints can be started later manually by their bean name via `Control Bus` (see <<control-bus>>), by their role using the `SmartLifecycleRoleController` (see <<endpoint-roles>>) or via simple `Lifecycle` bean injection.
250252
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.
251253
_Since version 4.3.12_
252254

255+
<8> 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.
256+
_Since version 4.3.15_
257+
258+
253259
These properties can be overridden by adding a file `/META-INF/spring.integration.properties` to the classpath.
254260
It is not necessary to provide all the properties, just those that you want to override.
255261

@@ -438,7 +444,7 @@ With this endpoint using the default poller:
438444
public class AnnotationService {
439445
440446
@Transformer(inputChannel = "aPollableChannel", outputChannel = "output"
441-
poller = @Poller("myPoller")
447+
poller = @Poller("myPoller"))
442448
public String handle(String payload) {
443449
...
444450
}
@@ -586,7 +592,7 @@ public class MyFlowConfiguration {
586592
public Supplier<String> pojoSupplier() {
587593
return () -> "foo";
588594
}
589-
595+
590596
@Bean
591597
@InboundChannelAdapter(value = "inputChannel", poller = @Poller(fixedDelay = "1000"))
592598
public Supplier<Message<String>> messageSupplier() {

0 commit comments

Comments
 (0)