INT-3326 Add GlobalChannelInterceptor Annotation

JIRA: https://jira.spring.io/browse/INT-3326

INT-3326: Add parent-child test

INT-3326: Fix `ClassCastException` for `GCII`

INT-3326: Rebase and Polishing

INT-3326: Fix `GCII` for `getBeanNamesForType`

INT-3326: Add `GlobalChannelInterceptor` annotation

JIRA: https://jira.spring.io/browse/INT-3326

INT-3326: Add parent-child test

INT-3326: Fix `ClassCastException` for `GCII`

INT-3326: Rebase and Polishing

INT-3326: Fix `GCII` for `getBeanNamesForType`

INT-3326 Polishing Doc, JavaDocs

Also fix a typo in the bean name for the CI processor.

INT-3326: Polishing

Doc Polishing
This commit is contained in:
Artem Bilan
2014-03-17 16:59:16 +02:00
committed by Gary Russell
parent 42a13c830d
commit 6d6bef58b2
12 changed files with 381 additions and 36 deletions

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2014 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.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* {@link org.springframework.messaging.support.ChannelInterceptor} components with this
* annotation will be applied as global channel interceptors
* using the provided {@code patterns} to match channel names.
* <p>
* The annotation can be used at the {@code class} level for {@link org.springframework.stereotype.Component} beans
* and on methods with {@link org.springframework.context.annotation.Bean}.
*
* @author Artem Bilan
* @since 4.0
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GlobalChannelInterceptor {
/**
* An array of simple patterns against which channel names will be matched. Default is "*"
* (all channels). See {@link org.springframework.util.PatternMatchUtils#simpleMatch(String, String)}.
* @return The pattern.
*/
String[] patterns() default "*";
/**
* The order of the interceptor. Interceptors with negative order values will be placed before any
* explicit interceptors on the channel; interceptors with positive order values will be
* placed after explicit interceptors.
* @return The order.
*/
int order() default 0;
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2014 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.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.util.CollectionUtils;
/**
* The {@link IntegrationConfigurationInitializer} to populate {@link GlobalChannelInterceptorWrapper}
* for {@link ChannelInterceptor}s marked with {@link GlobalChannelInterceptor} annotation.
* <p>
* {@link org.springframework.context.annotation.Bean} methods are also processed.
*
* @author Artem Bilan
* @since 4.0
*/
public class GlobalChannelInterceptorInitializer implements IntegrationConfigurationInitializer {
@Override
public void initialize(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
for (String beanName : registry.getBeanDefinitionNames()) {
BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
if (beanDefinition instanceof AnnotatedBeanDefinition) {
AnnotationMetadata metadata = ((AnnotatedBeanDefinition) beanDefinition).getMetadata();
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(GlobalChannelInterceptor.class.getName());
if (CollectionUtils.isEmpty(annotationAttributes) && beanDefinition.getSource() instanceof MethodMetadata) {
MethodMetadata beanMethod = (MethodMetadata) beanDefinition.getSource();
annotationAttributes = beanMethod.getAnnotationAttributes(GlobalChannelInterceptor.class.getName());
}
if (!CollectionUtils.isEmpty(annotationAttributes)) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(GlobalChannelInterceptorWrapper.class)
.addConstructorArgReference(beanName)
.addPropertyValue("patterns", annotationAttributes.get("patterns"))
.addPropertyValue("order", annotationAttributes.get("order"));
BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), registry);
}
}
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.integration.channel.interceptor;
package org.springframework.integration.config;
import java.util.ArrayList;
import java.util.Collection;
@@ -35,6 +35,8 @@ import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.SmartLifecycle;
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.messaging.support.ChannelInterceptor;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

View File

@@ -84,6 +84,7 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, Bean
this.registerIntegrationEvaluationContext(registry);
this.registerIntegrationProperties(registry);
this.registerHeaderChannelRegistry(registry);
this.registerGlobalChannelInterceptorProcessor(registry);
this.registerBuiltInBeans(registry);
this.registerDefaultConfiguringBeanFactoryPostProcessor(registry);
this.registerDefaultDatatypeChannelMessageConverter(registry);
@@ -285,6 +286,20 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, Bean
}
}
/**
* Register a {@link GlobalChannelInterceptorProcessor} in the given {@link BeanDefinitionRegistry}, if necessary.
*
* @param registry The {@link BeanDefinitionRegistry} to register additional {@link org.springframework.beans.factory.config.BeanDefinition}s.
*/
private void registerGlobalChannelInterceptorProcessor(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(IntegrationContextUtils.GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME)) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(GlobalChannelInterceptorProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(IntegrationContextUtils.GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME, builder.getBeanDefinition());
}
}
/**
* Register {@link MessagingAnnotationPostProcessor} and {@link PublisherAnnotationBeanPostProcessor}, if necessary.
* Inject {@code defaultPublishedChannel} from provided {@link AnnotationMetadata}, if any.

View File

@@ -26,12 +26,9 @@ import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper;
import org.springframework.integration.config.IntegrationConfigUtils;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.util.xml.DomUtils;
/**
@@ -41,46 +38,33 @@ import org.springframework.util.xml.DomUtils;
* @author Mark Fisher
* @author David Turanski
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class GlobalChannelInterceptorParser extends AbstractBeanDefinitionParser {
private static final String BASE_PACKAGE = IntegrationConfigUtils.BASE_PACKAGE + ".channel.interceptor.";
private static final String CHANNEL_NAME_PATTERN_ATTRIBUTE = "pattern";
private static final String REF_ATTRIBUTE = "ref";
private static final String GLOBAL_INTERCEPTOR_PROCESSOR_CLASSNAME = "GlobalChannelInterceptorProcessor";
private final ManagedList<RuntimeBeanReference> globalInterceptors = new ManagedList<RuntimeBeanReference>();
@Override
protected boolean shouldGenerateId() {
return true;
}
@Override
protected boolean shouldFireEvents() {
return false;
}
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
this.createAndRegisterGlobalPostProcessorIfNecessary(parserContext);
BeanDefinitionBuilder globalChannelInterceptorBuilder = BeanDefinitionBuilder.genericBeanDefinition(GlobalChannelInterceptorWrapper.class);
Object childBeanDefinition = getBeanDefinitionBuilderConstructorValue(element, parserContext);
globalChannelInterceptorBuilder.addConstructorArgValue(childBeanDefinition);
IntegrationNamespaceUtils.setValueIfAttributeDefined(globalChannelInterceptorBuilder, element, "order");
IntegrationNamespaceUtils.setValueIfAttributeDefined(
globalChannelInterceptorBuilder, element, CHANNEL_NAME_PATTERN_ATTRIBUTE, "patterns");
String beanName = BeanDefinitionReaderUtils.generateBeanName(
globalChannelInterceptorBuilder.getBeanDefinition(), parserContext.getRegistry());
parserContext.registerBeanComponent(new BeanComponentDefinition(globalChannelInterceptorBuilder.getBeanDefinition(), beanName));
this.globalInterceptors.add(new RuntimeBeanReference(beanName));
return null;
}
private void createAndRegisterGlobalPostProcessorIfNecessary(ParserContext parserContext) {
if (!parserContext.getRegistry().containsBeanDefinition(IntegrationContextUtils.GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME)) {
BeanDefinitionBuilder processorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
BASE_PACKAGE + GLOBAL_INTERCEPTOR_PROCESSOR_CLASSNAME);
BeanDefinition beanDef = processorBuilder.getBeanDefinition();
parserContext.registerBeanComponent(new BeanComponentDefinition(beanDef,
IntegrationContextUtils.GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME));
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(globalChannelInterceptorBuilder, element, CHANNEL_NAME_PATTERN_ATTRIBUTE, "patterns");
return globalChannelInterceptorBuilder.getBeanDefinition();
}
protected Object getBeanDefinitionBuilderConstructorValue(Element element, ParserContext parserContext) {

View File

@@ -82,7 +82,7 @@ public abstract class IntegrationContextUtils {
public static final String INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME = "messageBuilderFactory";
public static final String GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME = "gloabelChannelInterceptorProcessor";
public static final String GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME = "globalChannelInterceptorProcessor";
/**
* @param beanFactory BeanFactory for lookup, must not be null.

View File

@@ -1,2 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.integration.config.boot.IntegrationAutoConfiguration
org.springframework.integration.config.IntegrationConfigurationInitializer=\
org.springframework.integration.config.GlobalChannelInterceptorInitializer

View File

@@ -8,10 +8,16 @@
<message-history tracked-components="publishedChannel,input,*AnnotationTestService*"/>
<annotation-config default-publisher-channel="publishedChannel"/>
<channel-interceptor pattern="none">
<annotation-config default-publisher-channel="publishedChannel"/>
<channel-interceptor pattern="none">
<wire-tap channel="bar" />
</channel-interceptor>
<service-activator input-channel="bar" expression="'foo'" />
<channel-interceptor>
<beans:bean class="org.springframework.integration.configuration.EnableIntegrationTests$TestChannelInterceptor"/>
</channel-interceptor>
</beans:beans>

View File

@@ -18,16 +18,24 @@ package org.springframework.integration.configuration;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.concurrent.atomic.AtomicInteger;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@@ -43,11 +51,15 @@ import org.springframework.integration.annotation.Payload;
import org.springframework.integration.annotation.Publisher;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.channel.interceptor.WireTap;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.config.EnableMessageHistory;
import org.springframework.integration.config.EnablePublisher;
import org.springframework.integration.config.GlobalChannelInterceptor;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.history.MessageHistoryConfigurer;
import org.springframework.integration.support.MessageBuilder;
@@ -55,6 +67,9 @@ import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.stereotype.Component;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -68,24 +83,41 @@ import org.springframework.test.context.support.AnnotationConfigContextLoader;
@RunWith(SpringJUnit4ClassRunner.class)
public class EnableIntegrationTests {
@Autowired
private ApplicationContext context;
@Autowired
private MessageChannel input;
@Autowired
private PollableChannel output;
private QueueChannel output;
@Autowired
private PollableChannel publishedChannel;
@Autowired
private PollableChannel wireTapChannel;
@Autowired
private MessageHistoryConfigurer configurer;
@Autowired
private TestGateway testGateway;
@Autowired
private TestChannelInterceptor testChannelInterceptor;
@Autowired
private AtomicInteger fbInterceptorCounter;
@Test
public void testAnnotatedServiceActivator() {
this.input.send(MessageBuilder.withPayload("Foo").build());
Message<?> interceptedMessage = this.wireTapChannel.receive(1000);
assertNotNull(interceptedMessage);
assertEquals("Foo", interceptedMessage.getPayload());
Message<?> receive = this.output.receive(1000);
assertNotNull(receive);
assertEquals("FOO", receive.getPayload());
@@ -107,19 +139,24 @@ public class EnableIntegrationTests {
assertThat(messageHistoryString, Matchers.not(Matchers.containsString("input")));
assertThat(messageHistoryString, Matchers.not(Matchers.containsString("output")));
assertThat(messageHistoryString, Matchers.containsString("publishedChannel"));
assertNull(this.wireTapChannel.receive(0));
assertThat(this.testChannelInterceptor.getInvoked(), Matchers.greaterThan(0));
assertThat(this.fbInterceptorCounter.get(), Matchers.greaterThan(0));
}
@Test @DirtiesContext
@Test
@DirtiesContext
public void testChangePatterns() {
try {
this.configurer.setComponentNamePatterns(new String[] {"*"});
this.configurer.setComponentNamePatterns(new String[]{"*"});
fail("ExpectedException");
}
catch (IllegalStateException e) {
assertThat(e.getMessage(), containsString("cannot be changed"));
}
this.configurer.stop();
this.configurer.setComponentNamePatterns(new String[] {"*"});
this.configurer.setComponentNamePatterns(new String[]{"*"});
assertEquals("*", TestUtils.getPropertyValue(this.configurer, "componentNamePatterns", String[].class)[0]);
}
@@ -129,6 +166,32 @@ public class EnableIntegrationTests {
assertEquals(payload.toUpperCase(), this.testGateway.echo(payload));
}
@Test
public void testParentChildAnnotationConfiguration() {
AnnotationConfigApplicationContext child = new AnnotationConfigApplicationContext();
child.register(ChildConfiguration.class);
child.setParent(this.context);
child.refresh();
AbstractMessageChannel foo = child.getBean("foo", AbstractMessageChannel.class);
ChannelInterceptor baz = child.getBean("baz", ChannelInterceptor.class);
assertTrue(foo.getChannelInterceptors().contains(baz));
assertFalse(this.output.getChannelInterceptors().contains(baz));
child.close();
}
@Test
public void testParentChildAnnotationConfigurationFromAnotherPackage() {
AnnotationConfigApplicationContext child = new AnnotationConfigApplicationContext();
child.register(org.springframework.integration.configuration2.ChildConfiguration.class);
child.setParent(this.context);
child.refresh();
AbstractMessageChannel foo = child.getBean("foo", AbstractMessageChannel.class);
ChannelInterceptor baz = child.getBean("baz", ChannelInterceptor.class);
assertTrue(foo.getChannelInterceptors().contains(baz));
assertFalse(this.output.getChannelInterceptors().contains(baz));
child.close();
}
@Configuration
@ComponentScan
@IntegrationComponentScan
@@ -147,6 +210,65 @@ public class EnableIntegrationTests {
return new QueueChannel();
}
@Bean
public PollableChannel wireTapChannel() {
return new QueueChannel();
}
@Bean
@GlobalChannelInterceptor(patterns = "input")
public WireTap wireTap() {
return new WireTap(this.wireTapChannel());
}
@Bean
public AtomicInteger fbInterceptorCounter() {
return new AtomicInteger();
}
@Bean
@GlobalChannelInterceptor
public FactoryBean<ChannelInterceptor> ciFactoryBean() {
return new AbstractFactoryBean<ChannelInterceptor>() {
@Override
public Class<?> getObjectType() {
return ChannelInterceptor.class;
}
@Override
protected ChannelInterceptor createInstance() throws Exception {
return new ChannelInterceptorAdapter() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
fbInterceptorCounter().incrementAndGet();
return super.preSend(message, channel);
}
};
}
};
}
}
@Component
@GlobalChannelInterceptor
public static class TestChannelInterceptor extends ChannelInterceptorAdapter {
private final AtomicInteger invoked = new AtomicInteger();
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
this.invoked.incrementAndGet();
return message;
}
public Integer getInvoked() {
return invoked.get();
}
}
@Configuration
@@ -173,6 +295,24 @@ public class EnableIntegrationTests {
}
@Configuration
@EnableIntegration
public static class ChildConfiguration {
@Bean
public MessageChannel foo() {
return new DirectChannel();
}
@Bean
@GlobalChannelInterceptor(patterns = "*")
public WireTap baz() {
return new WireTap(new NullChannel());
}
}
@MessageEndpoint
public static class AnnotationTestService {
@@ -196,7 +336,7 @@ public class EnableIntegrationTests {
@MessagingGateway(defaultRequestChannel = "gatewayChannel", defaultHeaders = @GatewayHeader(name = "foo", value = "FOO"))
public static interface TestGateway {
@Gateway(headers = @GatewayHeader(name = "calledMethod", expression="#gatewayMethod.name"))
@Gateway(headers = @GatewayHeader(name = "calledMethod", expression = "#gatewayMethod.name"))
String echo(String payload);
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2014 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.configuration2;
/**
* @author Artem Bilan
* @since 4.0
*/
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.interceptor.WireTap;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.config.GlobalChannelInterceptor;
import org.springframework.messaging.MessageChannel;
@Configuration
@EnableIntegration
public class ChildConfiguration {
@Bean
public MessageChannel foo() {
return new DirectChannel();
}
@Bean
public MessageChannel bar() {
return new DirectChannel();
}
@Bean
@GlobalChannelInterceptor(patterns = "*")
public WireTap baz() {
return new WireTap(this.bar());
}
}

View File

@@ -359,6 +359,15 @@
for the default channel.
See <xref linkend="publisher-annotation"/> for more information.
</para>
<para>
The <classname>@GlobalChannelInterceptor</classname> annotation has been introduced to
mark <interfacename>ChannelInterceptor</interfacename> beans for global channel interception.
This annotation is an analogue of the <code>&lt;int:channel-interceptor&gt;</code> xml element
(see <xref linkend="global-channel-configuration-interceptors"/>). <code>@GlobalChannelInterceptor</code> annotations
can be placed at the class level (with a <classname>@Component</classname> stereotype annotation), or
on <classname>@Bean</classname> methods within <classname>@Configuration</classname> classes. In either case,
the bean <emphasis role="bold">must</emphasis> be a <interfacename>ChannelInterceptor</interfacename>.
</para>
</section>
</chapter>

View File

@@ -72,6 +72,14 @@
>Spring Boot - AutoConfigure</ulink>.
</para>
</section>
<section id="4.0-global-channel-interceptor">
<title>@GlobalChannelInterceptor</title>
<para>
As well as the <code>@EnableIntegration</code> annotation mentioned above,
the <code>@GlobalChannelInterceptor</code> annotation has bean introduced.
For more information, see <xref linkend="enable-integration"/>.
</para>
</section>
<section id="4.0-enable-publisher">
<title>@EnablePublisher</title>
<para>