Filter out duplicate ApplicationContextAwareProcessors in parent/child

contexts scenarios

AbstractApplicationContextFactory, when configuring a child context,
copies all of the BeanPostProcessors from the parent to the child.
Normally this is a good thing.  However, in the case of teh
ApplicationContextAwareProcessor, the child context ends up with two
instances, one for the parent context and one for the child context.
This brings two issues to light:
  1. Unknown which context is being injected - On beans implementing
ApplicationContextAware, it can't be determined which context (the
parent or the child) will be injected since both BPPs will be called in
an indeterimant order.
 2. Errors occur with ApplicationObjectSupport - If the child context
contains a bean that extends ApplicationObjectSupport, when the second
ApplicationContextAwareProcessor is called, the bean will throw an
exception since it is only allowed to be initialized with one
application context.

This fix adds additional logic to remove the
ApplicationContextAwareProcessor from the parent context before adding
all the BPPs to the child.

This fix addresses BATCH-2319.
This commit is contained in:
Michael Minella
2014-11-04 16:12:50 -06:00
parent 1dc465c6b3
commit 66776cc2cc
2 changed files with 59 additions and 17 deletions

View File

@@ -16,8 +16,14 @@
package org.springframework.batch.core.configuration.support;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
@@ -33,11 +39,7 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.springframework.util.ClassUtils;
/**
* {@link ApplicationContextFactory} implementation that takes a parent context and a path to the context to create.
@@ -200,17 +202,50 @@ public abstract class AbstractApplicationContextFactory implements ApplicationCo
protected void prepareBeanFactory(ConfigurableListableBeanFactory parent,
ConfigurableListableBeanFactory beanFactory) {
if (copyConfiguration && parent != null) {
List<BeanPostProcessor> parentPostProcessors = new ArrayList<BeanPostProcessor>();
List<BeanPostProcessor> childPostProcessors = new ArrayList<BeanPostProcessor>();
childPostProcessors.addAll(beanFactory instanceof AbstractBeanFactory ? ((AbstractBeanFactory) beanFactory)
.getBeanPostProcessors() : new ArrayList<BeanPostProcessor>());
parentPostProcessors.addAll(parent instanceof AbstractBeanFactory ? ((AbstractBeanFactory) parent)
.getBeanPostProcessors() : new ArrayList<BeanPostProcessor>());
try {
Class<?> applicationContextAwareProcessorClass =
ClassUtils.forName("org.springframework.context.support.ApplicationContextAwareProcessor",
parent.getBeanClassLoader());
for (BeanPostProcessor beanPostProcessor : new ArrayList<BeanPostProcessor>(parentPostProcessors)) {
if (applicationContextAwareProcessorClass.isAssignableFrom(beanPostProcessor.getClass())) {
logger.debug("Removing parent ApplicationContextAwareProcessor");
parentPostProcessors.remove(beanPostProcessor);
}
}
}
catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
List<BeanPostProcessor> aggregatedPostProcessors = new ArrayList<BeanPostProcessor>();
aggregatedPostProcessors.addAll(childPostProcessors);
aggregatedPostProcessors.addAll(parentPostProcessors);
for (BeanPostProcessor beanPostProcessor : new ArrayList<BeanPostProcessor>(aggregatedPostProcessors)) {
for (Class<?> cls : beanPostProcessorExcludeClasses) {
if (cls.isAssignableFrom(beanPostProcessor.getClass())) {
logger.debug("Removing bean post processor: " + beanPostProcessor + " of type " + cls);
aggregatedPostProcessors.remove(beanPostProcessor);
}
}
}
beanFactory.copyConfigurationFrom(parent);
List<BeanPostProcessor> beanPostProcessors = beanFactory instanceof AbstractBeanFactory ? ((AbstractBeanFactory) beanFactory)
.getBeanPostProcessors() : new ArrayList<BeanPostProcessor>();
for (BeanPostProcessor beanPostProcessor : new ArrayList<BeanPostProcessor>(beanPostProcessors)) {
for (Class<?> cls : beanPostProcessorExcludeClasses) {
if (cls.isAssignableFrom(beanPostProcessor.getClass())) {
logger.debug("Removing bean post processor: " + beanPostProcessor + " of type " + cls);
beanPostProcessors.remove(beanPostProcessor);
}
}
}
beanPostProcessors.clear();
beanPostProcessors.addAll(aggregatedPostProcessors);
}
}

View File

@@ -15,7 +15,12 @@
*/
package org.springframework.batch.core.configuration.annotation;
import static org.junit.Assert.assertEquals;
import javax.annotation.PostConstruct;
import org.junit.Test;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
@@ -36,10 +41,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import static org.junit.Assert.assertEquals;
import org.springframework.context.support.ApplicationObjectSupport;
/**
* @author Dave Syer
@@ -114,6 +116,11 @@ public class JobLoaderConfigurationTests {
@Configuration
public static class TestConfiguration {
@Bean
public ApplicationObjectSupport fakeApplicationObjectSupport() {
return new ApplicationObjectSupport() {};
}
@Autowired
private JobBuilderFactory jobs;