INT-3332 GlobalChannelInterceptorBeanPostProcessor
Implicit channel declaration for items downstream of a `ChannelInterceptor` were not created. The `BPP` eagerly fetched the interceptors during its own creation; this caused the context initialization to fail because the channel initializer hasn't run yet. Defer creation of the interceptor beans until they are actually needed. Also, when using `@Configuration`, the channelInitializer is no longer the first bean in the bean factory. INT-3332 Use SmartLifeCycle to Apply Interceptors Instead of using a bean post processor, the interceptor processor now performs the channel interception when beans in phase Integer.MIN_VALUE are started - after all beans have been instantiated. Polishing
This commit is contained in:
committed by
Artem Bilan
parent
23a26ca38e
commit
b92134fae7
@@ -17,20 +17,27 @@
|
||||
package org.springframework.integration.channel.interceptor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
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.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -40,59 +47,85 @@ import org.springframework.util.StringUtils;
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
* @since 2.0
|
||||
*/
|
||||
final class GlobalChannelInterceptorBeanPostProcessor implements BeanPostProcessor, InitializingBean {
|
||||
final class GlobalChannelInterceptorProcessor implements BeanFactoryAware, SmartLifecycle {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(GlobalChannelInterceptorBeanPostProcessor.class);
|
||||
private static final Log logger = LogFactory.getLog(GlobalChannelInterceptorProcessor.class);
|
||||
|
||||
|
||||
private final OrderComparator comparator = new OrderComparator();
|
||||
|
||||
private volatile List<GlobalChannelInterceptorWrapper> channelInterceptors;
|
||||
|
||||
private final Set<GlobalChannelInterceptorWrapper> positiveOrderInterceptors = new LinkedHashSet<GlobalChannelInterceptorWrapper>();
|
||||
|
||||
private final Set<GlobalChannelInterceptorWrapper> negativeOrderInterceptors = new LinkedHashSet<GlobalChannelInterceptorWrapper>();
|
||||
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
GlobalChannelInterceptorBeanPostProcessor(List<GlobalChannelInterceptorWrapper> channelInterceptors) {
|
||||
this.channelInterceptors = channelInterceptors;
|
||||
}
|
||||
|
||||
private volatile boolean processed;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
for (GlobalChannelInterceptorWrapper channelInterceptor : this.channelInterceptors) {
|
||||
if (channelInterceptor.getOrder() >= 0) {
|
||||
this.positiveOrderInterceptors.add(channelInterceptor);
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
Assert.isInstanceOf(ListableBeanFactory.class, beanFactory);
|
||||
this.beanFactory = (ListableBeanFactory) beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void start() {
|
||||
if (!this.processed) {
|
||||
Collection<GlobalChannelInterceptorWrapper> interceptors = this.beanFactory.getBeansOfType(GlobalChannelInterceptorWrapper.class).values();
|
||||
if (CollectionUtils.isEmpty(interceptors)) {
|
||||
logger.debug("No global channel interceptors.");
|
||||
}
|
||||
else {
|
||||
this.negativeOrderInterceptors.add(channelInterceptor);
|
||||
for (GlobalChannelInterceptorWrapper channelInterceptor : interceptors) {
|
||||
if (channelInterceptor.getOrder() >= 0) {
|
||||
this.positiveOrderInterceptors.add(channelInterceptor);
|
||||
}
|
||||
else {
|
||||
this.negativeOrderInterceptors.add(channelInterceptor);
|
||||
}
|
||||
}
|
||||
Map<String, ChannelInterceptorAware> channels = this.beanFactory.getBeansOfType(ChannelInterceptorAware.class);
|
||||
for (Entry<String, ChannelInterceptorAware> entry : channels.entrySet()) {
|
||||
this.addMatchingInterceptors(entry.getValue(), entry.getKey());
|
||||
}
|
||||
}
|
||||
this.processed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
public void stop() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof ChannelInterceptorAware && bean instanceof MessageChannel) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Applying global interceptors on channel '" + beanName + "'");
|
||||
}
|
||||
this.addMatchingInterceptors((ChannelInterceptorAware) bean, beanName);
|
||||
}
|
||||
return bean;
|
||||
public boolean isRunning() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPhase() {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutoStartup() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(Runnable callback) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds any interceptor whose pattern matches against the channel's name.
|
||||
*/
|
||||
private void addMatchingInterceptors(ChannelInterceptorAware channel, String beanName) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Applying global interceptors on channel '" + beanName + "'");
|
||||
}
|
||||
List<GlobalChannelInterceptorWrapper> tempInterceptors = new ArrayList<GlobalChannelInterceptorWrapper>();
|
||||
for (GlobalChannelInterceptorWrapper globalChannelInterceptorWrapper : this.positiveOrderInterceptors) {
|
||||
String[] patterns = globalChannelInterceptorWrapper.getPatterns();
|
||||
@@ -103,7 +136,11 @@ final class GlobalChannelInterceptorBeanPostProcessor implements BeanPostProcess
|
||||
}
|
||||
Collections.sort(tempInterceptors, this.comparator);
|
||||
for (GlobalChannelInterceptorWrapper next : tempInterceptors) {
|
||||
channel.addInterceptor(next.getChannelInterceptor());
|
||||
ChannelInterceptor channelInterceptor = next.getChannelInterceptor();
|
||||
if (!(channelInterceptor instanceof VetoCapableInterceptor)
|
||||
|| ((VetoCapableInterceptor) channelInterceptor).shouldIntercept(beanName, channel)) {
|
||||
channel.addInterceptor(channelInterceptor);
|
||||
}
|
||||
}
|
||||
|
||||
tempInterceptors.clear();
|
||||
@@ -117,7 +154,11 @@ final class GlobalChannelInterceptorBeanPostProcessor implements BeanPostProcess
|
||||
Collections.sort(tempInterceptors, comparator);
|
||||
if (!tempInterceptors.isEmpty()) {
|
||||
for (int i = tempInterceptors.size() - 1; i >= 0; i--) {
|
||||
channel.addInterceptor(0, tempInterceptors.get(i).getChannelInterceptor());
|
||||
ChannelInterceptor channelInterceptor = tempInterceptors.get(i).getChannelInterceptor();
|
||||
if (!(channelInterceptor instanceof VetoCapableInterceptor)
|
||||
|| ((VetoCapableInterceptor) channelInterceptor).shouldIntercept(beanName, channel)) {
|
||||
channel.addInterceptor(0, channelInterceptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.channel.interceptor;
|
||||
|
||||
import org.springframework.integration.channel.ChannelInterceptorAware;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
|
||||
/**
|
||||
* {@link ChannelInterceptor}s implementing this interface can veto
|
||||
* global interception of a particular channel name. Could be used, for example,
|
||||
* when an interceptor itself writes to an output channel (which should
|
||||
* not be intercepted with this interceptor).
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 4.0
|
||||
*
|
||||
*/
|
||||
public interface VetoCapableInterceptor {
|
||||
|
||||
/**
|
||||
* @param beanName The channel name.
|
||||
* @return false if the intercept wishes to veto the interception.
|
||||
*/
|
||||
boolean shouldIntercept(String beanName, ChannelInterceptorAware channel);
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -20,6 +20,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.integration.channel.ChannelInterceptorAware;
|
||||
import org.springframework.integration.core.MessageSelector;
|
||||
import org.springframework.jmx.export.annotation.ManagedAttribute;
|
||||
import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||
@@ -35,9 +36,10 @@ import org.springframework.util.Assert;
|
||||
* to a secondary target while still sending the original message to the main channel.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
*/
|
||||
@ManagedResource
|
||||
public class WireTap extends ChannelInterceptorAdapter implements Lifecycle {
|
||||
public class WireTap extends ChannelInterceptorAdapter implements Lifecycle, VetoCapableInterceptor {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(WireTap.class);
|
||||
|
||||
@@ -133,4 +135,9 @@ public class WireTap extends ChannelInterceptorAdapter implements Lifecycle {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldIntercept(String beanName, ChannelInterceptorAware channel) {
|
||||
return !this.channel.equals(channel);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -31,6 +31,7 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -39,6 +40,7 @@ import org.springframework.util.xml.DomUtils;
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Mark Fisher
|
||||
* @author David Turanski
|
||||
* @author Gary Russell
|
||||
* @since 2.0
|
||||
*/
|
||||
public class GlobalChannelInterceptorParser extends AbstractBeanDefinitionParser {
|
||||
@@ -49,14 +51,13 @@ public class GlobalChannelInterceptorParser extends AbstractBeanDefinitionParser
|
||||
|
||||
private static final String REF_ATTRIBUTE = "ref";
|
||||
|
||||
private static final String GLOBAL_POST_PROCESSOR_CLASSNAME = "GlobalChannelInterceptorBeanPostProcessor";
|
||||
private static final String GLOBAL_INTERCEPTOR_PROCESSOR_CLASSNAME = "GlobalChannelInterceptorProcessor";
|
||||
|
||||
|
||||
private final ManagedList<RuntimeBeanReference> globalInterceptors = new ManagedList<RuntimeBeanReference>();
|
||||
|
||||
private volatile boolean postProcessorCreated;
|
||||
|
||||
|
||||
@Override
|
||||
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
|
||||
this.createAndRegisterGlobalPostProcessorIfNecessary(parserContext);
|
||||
BeanDefinitionBuilder globalChannelInterceptorBuilder = BeanDefinitionBuilder.genericBeanDefinition(GlobalChannelInterceptorWrapper.class);
|
||||
@@ -73,14 +74,12 @@ public class GlobalChannelInterceptorParser extends AbstractBeanDefinitionParser
|
||||
}
|
||||
|
||||
private void createAndRegisterGlobalPostProcessorIfNecessary(ParserContext parserContext) {
|
||||
if (!this.postProcessorCreated) {
|
||||
BeanDefinitionBuilder postProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
BASE_PACKAGE + GLOBAL_POST_PROCESSOR_CLASSNAME);
|
||||
postProcessorBuilder.addConstructorArgValue(this.globalInterceptors);
|
||||
BeanDefinition beanDef = postProcessorBuilder.getBeanDefinition();
|
||||
String beanName = BeanDefinitionReaderUtils.generateBeanName(beanDef, parserContext.getRegistry());
|
||||
parserContext.registerBeanComponent(new BeanComponentDefinition(beanDef, beanName));
|
||||
this.postProcessorCreated = true;
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,11 @@ public abstract class IntegrationContextUtils {
|
||||
public static final String INTEGRATION_DATATYPE_CHANNEL_MESSAGE_CONVERTER_BEAN_NAME = "datatypeChannelMessageConverter";
|
||||
|
||||
public static final String INTEGRATION_FIXED_SUBSCRIBER_CHANNEL_BPP_BEAN_NAME = "fixedSubscriberChannelBeanFactoryPostProcessor";
|
||||
|
||||
public static final String INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME = "messageBuilderFactory";
|
||||
|
||||
public static final String GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME = "gloabelChannelInterceptorProcessor";
|
||||
|
||||
/**
|
||||
* @param beanFactory BeanFactory for lookup, must not be null.
|
||||
* @return The {@link MetadataStore} bean whose name is "metadataStore".
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Copyright 2002-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.
|
||||
@@ -18,6 +18,7 @@ import static org.junit.Assert.assertNull;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.messaging.Message;
|
||||
@@ -27,73 +28,74 @@ import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author David Turanski
|
||||
* @author Gary Russell
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class GlobalWireTapTests {
|
||||
|
||||
|
||||
|
||||
|
||||
@Autowired
|
||||
@Qualifier("channel")
|
||||
MessageChannel channel;
|
||||
|
||||
|
||||
@Autowired
|
||||
@Qualifier("random-channel")
|
||||
MessageChannel anotherChannel;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Autowired
|
||||
@Qualifier("wiretap-single")
|
||||
PollableChannel wiretapSingle;
|
||||
|
||||
|
||||
@Autowired
|
||||
@Qualifier("wiretap-all")
|
||||
PollableChannel wiretapAll;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testWireTapsOnTargetedChannel(){
|
||||
Message<?> message = new GenericMessage<String>("hello");
|
||||
Message<?> message = new GenericMessage<String>("hello");
|
||||
channel.send(message);
|
||||
Message <?> wireTapMessage = wiretapSingle.receive(100);
|
||||
assertNotNull(wireTapMessage);
|
||||
|
||||
//There shoud be two messages on this channel. One for 'channel' and one for 'output'
|
||||
|
||||
//There should be three messages on this channel. One for 'channel', one for 'output', and one for 'wiretapSingle'.
|
||||
wireTapMessage = wiretapAll.receive(100);
|
||||
int msgCount=0;
|
||||
while (wireTapMessage != null){
|
||||
msgCount++;
|
||||
assertEquals(wireTapMessage.getPayload(),message.getPayload());
|
||||
wireTapMessage = wiretapAll.receive(100);
|
||||
}
|
||||
|
||||
assertEquals(2,msgCount);
|
||||
}
|
||||
|
||||
assertEquals(3,msgCount);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWireTapsOnRandomChannel(){
|
||||
Message<?> message = new GenericMessage<String>("hello");
|
||||
Message<?> message = new GenericMessage<String>("hello");
|
||||
anotherChannel.send(message);
|
||||
|
||||
|
||||
//This time no message on wiretapSingle
|
||||
Message <?> wireTapMessage = wiretapSingle.receive(100);
|
||||
assertNull(wireTapMessage);
|
||||
|
||||
//There shoud be two messages on this channel. One for 'channel' and one for 'output'
|
||||
|
||||
//There should be two messages on this channel. One for 'channel' and one for 'output'
|
||||
wireTapMessage = wiretapAll.receive(100);
|
||||
int msgCount=0;
|
||||
while (wireTapMessage != null){
|
||||
msgCount++;
|
||||
assertEquals(wireTapMessage.getPayload(),message.getPayload());
|
||||
wireTapMessage = wiretapAll.receive(100);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
assertEquals(2,msgCount);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
|
||||
|
||||
<int:wire-tap channel="bar" />
|
||||
|
||||
<int:service-activator input-channel="bar" expression="'foo'" />
|
||||
|
||||
<int:channel-interceptor>
|
||||
<bean class="org.springframework.integration.channel.interceptor.ImplicitConsumerChannelTests$Interceptor1">
|
||||
<property name="channel" ref="foo" />
|
||||
</bean>
|
||||
</int:channel-interceptor>
|
||||
|
||||
<int:channel-interceptor>
|
||||
<bean class="org.springframework.integration.channel.interceptor.ImplicitConsumerChannelTests$Interceptor2">
|
||||
<property name="channel" ref="baz" />
|
||||
</bean>
|
||||
</int:channel-interceptor>
|
||||
|
||||
<int:channel id="foo" />
|
||||
|
||||
<int:channel id="baz" />
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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.channel.interceptor;
|
||||
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.channel.AbstractMessageChannel;
|
||||
import org.springframework.integration.channel.ChannelInterceptorAware;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 4.0
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class ImplicitConsumerChannelTests {
|
||||
|
||||
@Autowired
|
||||
private AbstractMessageChannel bar;
|
||||
|
||||
@Autowired
|
||||
private AbstractMessageChannel foo;
|
||||
|
||||
@Autowired
|
||||
private AbstractMessageChannel baz;
|
||||
|
||||
@Test
|
||||
public void testImplicit() {
|
||||
// used to fail to load AC (no channel 'bar')
|
||||
List<ChannelInterceptor> barInterceptors = bar.getChannelInterceptors();
|
||||
assertEquals(2, barInterceptors.size());
|
||||
assertThat(barInterceptors.get(0), anyOf(instanceOf(Interceptor1.class), instanceOf(Interceptor2.class)));
|
||||
assertThat(barInterceptors.get(1), anyOf(instanceOf(Interceptor1.class), instanceOf(Interceptor2.class)));
|
||||
List<ChannelInterceptor> fooInterceptors = foo.getChannelInterceptors();
|
||||
assertEquals(2, fooInterceptors.size());
|
||||
assertThat(fooInterceptors.get(0), anyOf(instanceOf(WireTap.class), instanceOf(Interceptor2.class)));
|
||||
assertThat(fooInterceptors.get(1), anyOf(instanceOf(WireTap.class), instanceOf(Interceptor2.class)));
|
||||
List<ChannelInterceptor> bazInterceptors = baz.getChannelInterceptors();
|
||||
assertEquals(2, bazInterceptors.size());
|
||||
assertThat(bazInterceptors.get(0), anyOf(instanceOf(WireTap.class), instanceOf(Interceptor1.class)));
|
||||
assertThat(bazInterceptors.get(1), anyOf(instanceOf(WireTap.class), instanceOf(Interceptor1.class)));
|
||||
}
|
||||
|
||||
public static class Interceptor1 implements ChannelInterceptor, VetoCapableInterceptor {
|
||||
|
||||
private MessageChannel channel;
|
||||
|
||||
@Override
|
||||
public Message<?> preSend(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<?> postReceive(Message<?> message, MessageChannel channel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setChannel(MessageChannel channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public MessageChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldIntercept(String beanName, ChannelInterceptorAware channel) {
|
||||
return !this.channel.equals(channel);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Interceptor2 implements ChannelInterceptor, VetoCapableInterceptor {
|
||||
|
||||
private MessageChannel channel;
|
||||
|
||||
@Override
|
||||
public Message<?> preSend(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<?> postReceive(Message<?> message, MessageChannel channel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setChannel(MessageChannel channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public MessageChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldIntercept(String beanName, ChannelInterceptorAware channel) {
|
||||
return !this.channel.equals(channel);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
<message-history tracked-components="publishedChannel,input,*AnnotationTestService*"/>
|
||||
|
||||
<annotation-config default-publisher-channel="publishedChannel"/>
|
||||
<annotation-config default-publisher-channel="publishedChannel"/>
|
||||
<channel-interceptor pattern="none">
|
||||
<wire-tap channel="bar" />
|
||||
</channel-interceptor>
|
||||
|
||||
<service-activator input-channel="bar" expression="'foo'" />
|
||||
</beans:beans>
|
||||
|
||||
@@ -837,6 +837,17 @@ payload to an Integer.
|
||||
best of both worlds: 1) the sending of a JMS Message can occur within the transaction while
|
||||
2) it is still a "fire-and-forget" action thereby preventing any noticeable delay in the main message flow.
|
||||
</para>
|
||||
<important>
|
||||
Starting with <emphasis>version 4.0</emphasis>, it is important to avoid circular references when an
|
||||
interceptor (such as <classname>WireTap</classname>) references a channel itself. You need to exclude
|
||||
such channels from those being intercepted by the current interceptor. This can be done with appropriate
|
||||
<code>patterns</code> or programmatically. If you have a custom <interfacename>ChannelInterceptor</interfacename>
|
||||
that references a <code>channel</code>, consider implementing <interfacename>VetoCapableInterceptor</interfacename>.
|
||||
That way, the framework will ask the interceptor if it's OK to intercept each channel that is a candidate based
|
||||
on the pattern. You can also add runtime protection in the interceptor methods that ensures that the channel is
|
||||
not one that is referenced by the interceptor. The <classname>WireTap</classname> uses both of these
|
||||
techniques.
|
||||
</important>
|
||||
</section>
|
||||
<section id="channel-global-wiretap">
|
||||
<title>Global Wire Tap Configuration</title>
|
||||
|
||||
@@ -434,7 +434,7 @@ rt.setConnectionFactory(redisConnectionFactory);]]></programlisting>
|
||||
<int:queue message-store="redisMessageStore"/>
|
||||
<int:channel>]]></programlisting>
|
||||
<para>
|
||||
The keys are used to store the data have the form <code><storeBeanName>:<channelId></code>
|
||||
The keys that are used to store the data have the form <code><storeBeanName>:<channelId></code>
|
||||
(in the above example, <code>redisMessageStore:somePersistentQueueChannel</code>).
|
||||
</para>
|
||||
<para>
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
in more details, please see the Issue Tracker tickets that
|
||||
were resolved as part of the 4.0 development process.
|
||||
</para>
|
||||
<para>
|
||||
Please be sure to also see the
|
||||
<ulink url="https://github.com/spring-projects/spring-integration/wiki/Spring-Integration-3.0-to-4.0-Migration-Guide"
|
||||
>Migration Guide</ulink> for important changes that might affect your applications.
|
||||
Migration guides for all versions back to <emphasis>2.1</emphasis> can be found on the
|
||||
<ulink url="https://github.com/spring-projects/spring-integration/wiki">Wiki</ulink>.
|
||||
</para>
|
||||
<section id="4.0-new-components">
|
||||
<title>New Components</title>
|
||||
<section id="4.0-mqtt">
|
||||
|
||||
Reference in New Issue
Block a user