INT-3115 One EvaluationContext FB Per Context

Move definition of the IntegrationEvaluationContextFactoryBean
from DefaultConfiguringBeanFactoryPostProcessor to
AbstractIntegrationNamespaceHandler.

We need a separate eval contexts for each app context so that child
contexts get the correct BeanResolver.

Add Test For IECFB and Parent Context
This commit is contained in:
Gary Russell
2013-08-27 08:36:08 -04:00
parent 52b340956f
commit 02a82d6eaa
6 changed files with 173 additions and 24 deletions

View File

@@ -16,21 +16,28 @@
package org.springframework.integration.config.xml;
import static org.springframework.integration.context.IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.ManagedSet;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.IntegrationEvaluationContextFactoryBean;
import org.springframework.integration.config.xml.ChannelInitializer.AutoCreateCandidatesCollector;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.expression.IntegrationEvaluationContextAwareBeanPostProcessor;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Base class for NamespaceHandlers that registers a BeanFactoryPostProcessor
@@ -59,6 +66,7 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
public final BeanDefinition parse(Element element, ParserContext parserContext) {
this.verifySchemaVersion(element, parserContext);
this.registerImplicitChannelCreator(parserContext);
this.registerIntegrationEvaluationContext(parserContext);
this.registerDefaultConfiguringBeanFactoryPostProcessorIfNecessary(parserContext);
return this.delegate.parse(element, parserContext);
}
@@ -90,8 +98,8 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
}
// ChannelInitializer$AutoCreateCandidatesCollector
if (parserContext.getRegistry() instanceof ListableBeanFactory) {
// unlike DefaultConfiguringBeanFactoryPostProcessor we need one of these per registry
// therefore we need to call containsBeanDefinition(..) which does not consider parent registry
// unlike DefaultConfiguringBeanFactoryPostProcessor, we need one of these per registry
// therefore we need to call containsBeanDefinition(..) which does not consider the parent registry
alreadyRegistered = ((ListableBeanFactory) parserContext.getRegistry()).
containsBeanDefinition(ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME);
}
@@ -107,6 +115,32 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
}
}
private void registerIntegrationEvaluationContext(ParserContext parserContext) {
boolean alreadyRegistered = false;
if (parserContext.getRegistry() instanceof ListableBeanFactory) {
// unlike DefaultConfiguringBeanFactoryPostProcessor, we need one of these per registry
// therefore we need to call containsBeanDefinition(..) which does not consider the parent registry
alreadyRegistered = ((ListableBeanFactory) parserContext.getRegistry()).containsBeanDefinition(
INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
}
else {
alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
}
if (!alreadyRegistered) {
BeanDefinitionBuilder integrationEvaluationContextBuilder = BeanDefinitionBuilder
.genericBeanDefinition(IntegrationEvaluationContextFactoryBean.class);
integrationEvaluationContextBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
BeanDefinitionHolder integrationEvaluationContextHolder = new BeanDefinitionHolder(
integrationEvaluationContextBuilder.getBeanDefinition(),
IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
BeanDefinitionReaderUtils.registerBeanDefinition(integrationEvaluationContextHolder,
parserContext.getRegistry());
RootBeanDefinition integrationEvalContextBPP = new RootBeanDefinition(
IntegrationEvaluationContextAwareBeanPostProcessor.class);
BeanDefinitionReaderUtils.registerWithGeneratedName(integrationEvalContextBPP, parserContext.getRegistry());
}
}
private void registerDefaultConfiguringBeanFactoryPostProcessorIfNecessary(ParserContext parserContext) {
boolean alreadyRegistered = false;
if (parserContext.getRegistry() instanceof ListableBeanFactory) {

View File

@@ -32,9 +32,7 @@ import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.config.IntegrationEvaluationContextFactoryBean;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.expression.IntegrationEvaluationContextAwareBeanPostProcessor;
/**
* A {@link BeanFactoryPostProcessor} implementation that provides default beans for the error handling and task
@@ -44,13 +42,14 @@ import org.springframework.integration.expression.IntegrationEvaluationContextAw
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
* @author Gary Russell
*/
class DefaultConfiguringBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private static final String ERROR_LOGGER_BEAN_NAME = "_org.springframework.integration.errorLogger";
private Log logger = LogFactory.getLog(this.getClass());
private final Log logger = LogFactory.getLog(this.getClass());
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
@@ -64,10 +63,6 @@ class DefaultConfiguringBeanFactoryPostProcessor implements BeanFactoryPostProce
this.registerTaskScheduler(registry);
}
this.registerIdGeneratorConfigurer(registry);
if (!beanFactory.containsBean(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME)) {
this.registerIntegrationEvaluationContext(registry);
beanFactory.addBeanPostProcessor(new IntegrationEvaluationContextAwareBeanPostProcessor(beanFactory));
}
}
else if (logger.isWarnEnabled()) {
logger.warn("BeanFactory is not a BeanDefinitionRegistry. The default '"
@@ -78,16 +73,6 @@ class DefaultConfiguringBeanFactoryPostProcessor implements BeanFactoryPostProce
}
}
private void registerIntegrationEvaluationContext(BeanDefinitionRegistry registry) {
BeanDefinitionBuilder integrationEvaluationContextBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationEvaluationContextFactoryBean.class);
integrationEvaluationContextBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
BeanDefinitionHolder integrationEvaluationContextHolder = new BeanDefinitionHolder(
integrationEvaluationContextBuilder.getBeanDefinition(),
IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
BeanDefinitionReaderUtils.registerBeanDefinition(integrationEvaluationContextHolder, registry);
}
private void registerIdGeneratorConfigurer(BeanDefinitionRegistry registry) {
String listenerClassName = "org.springframework.integration.config.IdGeneratorConfigurer";
String[] definitionNames = registry.getBeanDefinitionNames();

View File

@@ -18,19 +18,22 @@ package org.springframework.integration.expression;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.context.IntegrationContextUtils;
/**
* @author Artem Bilan
* @author Gary Russell
* @since 3.0
*/
public class IntegrationEvaluationContextAwareBeanPostProcessor implements BeanPostProcessor {
public class IntegrationEvaluationContextAwareBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private final BeanFactory beanFactory;
private volatile BeanFactory beanFactory;
public IntegrationEvaluationContextAwareBeanPostProcessor(BeanFactory beanFactory) {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}

View File

@@ -0,0 +1,20 @@
<?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">
<bean id="bar" class="java.lang.String">
<constructor-arg value="bar"/>
</bean>
<int:transformer input-channel="input" output-channel="output" expression="@foo + @bar"/>
<int:channel id="output">
<int:queue/>
</int:channel>
<bean class="org.springframework.integration.expression.ParentContextTests$Foo" />
</beans>

View File

@@ -0,0 +1,22 @@
<?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">
<bean id="foo" class="java.lang.String">
<constructor-arg value="foo"/>
</bean>
<int:transformer input-channel="parentIn" output-channel="parentOut" expression="@foo"/>
<int:channel id="parentOut">
<int:queue/>
</int:channel>
<bean class="org.springframework.integration.expression.ParentContextTests$Foo" />
<bean class="org.springframework.integration.expression.ParentContextTests$Foo" />
</beans>

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2013 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.expression;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.test.util.TestUtils;
/**
* @author Gary Russell
* @since 3.0
*
*/
public class ParentContextTests {
private static final List<EvaluationContext> evalContexts = new ArrayList<EvaluationContext>();
/**
* Verifies that beans in hierarchical contexts get an evaluation context that has the proper
* BeanResolver. Verifies that the two Foos in the parent context get an evaluation context
* with the same bean resolver. Verifies that the one Foo in the child context gets a different
* bean resolver. Verifies that bean references in SpEL expressions to beans in the child
* and parent contexts work.
*/
@Test
public void testSpelBeanReferencesInChildAndParent() {
ApplicationContext parent = new ClassPathXmlApplicationContext("ParentContext-context.xml", this.getClass());
assertEquals(2, evalContexts.size());
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext(parent);
child.setConfigLocation("org/springframework/integration/expression/ChildContext-context.xml");
child.refresh();
assertEquals(3, evalContexts.size());
assertSame(evalContexts.get(0).getBeanResolver(), evalContexts.get(1).getBeanResolver());
assertNotSame(evalContexts.get(1).getBeanResolver(), evalContexts.get(2).getBeanResolver());
assertSame(parent, TestUtils.getPropertyValue(evalContexts.get(0).getBeanResolver(), "beanFactory"));
assertSame(child, TestUtils.getPropertyValue(evalContexts.get(2).getBeanResolver(), "beanFactory"));
// Test transformer expressions
child.getBean("input", MessageChannel.class).send(new GenericMessage<String>("baz"));
Message<?> out = child.getBean("output", QueueChannel.class).receive(0);
assertNotNull(out);
assertEquals("foobar", out.getPayload());
child.getBean("parentIn", MessageChannel.class).send(new GenericMessage<String>("bar"));
out = child.getBean("parentOut", QueueChannel.class).receive(0);
assertNotNull(out);
assertEquals("foo", out.getPayload());
}
public static class Foo implements IntegrationEvaluationContextAware {
@Override
public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) {
evalContexts.add(evaluationContext);
}
}
}