From fd42a65c6cb707470b9c1cc2c72b2b9adfe5a265 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Wed, 13 Jul 2011 23:30:57 +0000 Subject: [PATCH] Allow ConfigurationCPP to process multiple registries Prior to this change, an instance of ConfigurationClassPostProcessor would throw IllegalStateException if its postProcessBeanDefinitionRegistry method were called more than once. This check is important to ensure that @Configuration classes are not proxied by CGLIB multiple times, and works for most normal use cases. However, if the same CCPP instance is used to process multiple registries/factories/contexts, this check creates a false negative because it does not distinguish between invocations of postProcessBeanDefinitionRegistry across different registries. A use case for this, though admittedly uncommon, would be creating a CCPP instance and registering it via ConfigurableApplicationContext#addBeanDefinitionPostProcessor against several ApplicationContexts. In such a case, the same CCPP instance will post-process multiple different registry instances, and throw the above mentioned exception. With this change, CCPP now performs lightweight tracking of the registries/beanFactories that it has already processed by recording the identity hashcodes of these objects. This is only slightly more complex than the previous boolean-based 'already processed' flags, and prevents this issue (however rare it may be) from occurring. Issue: SPR-8527 --- .../ConfigurationClassPostProcessor.java | 25 +++++++++++-------- .../ConfigurationClassPostProcessorTests.java | 19 ++++++++++++++ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 982741a406c..08caf119620 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -17,6 +17,7 @@ package org.springframework.context.annotation; import java.io.IOException; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; @@ -102,9 +103,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo private boolean setMetadataReaderFactoryCalled = false; - private boolean postProcessBeanDefinitionRegistryCalled = false; + private final Set registriesPostProcessed = new HashSet(); - private boolean postProcessBeanFactoryCalled = false; + private final Set factoriesPostProcessed = new HashSet(); private Environment environment; @@ -163,15 +164,16 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo */ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { BeanDefinitionReaderUtils.registerWithGeneratedName(new RootBeanDefinition(ImportAwareBeanPostProcessor.class), registry); - if (this.postProcessBeanDefinitionRegistryCalled) { + int registryID = System.identityHashCode(registry); + if (this.registriesPostProcessed.contains(registryID)) { throw new IllegalStateException( - "postProcessBeanDefinitionRegistry already called for this post-processor"); + "postProcessBeanDefinitionRegistry already called for this post-processor against " + registry); } - if (this.postProcessBeanFactoryCalled) { + if (this.factoriesPostProcessed.contains(registryID)) { throw new IllegalStateException( - "postProcessBeanFactory already called for this post-processor"); + "postProcessBeanFactory already called for this post-processor against " + registry); } - this.postProcessBeanDefinitionRegistryCalled = true; + this.registriesPostProcessed.add(registryID); processConfigurationClasses(registry); } @@ -180,12 +182,13 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo * by replacing them with CGLIB-enhanced subclasses. */ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { - if (this.postProcessBeanFactoryCalled) { + int factoryID = System.identityHashCode(beanFactory); + if (this.factoriesPostProcessed.contains(factoryID)) { throw new IllegalStateException( - "postProcessBeanFactory already called for this post-processor"); + "postProcessBeanFactory already called for this post-processor against " + beanFactory); } - this.postProcessBeanFactoryCalled = true; - if (!this.postProcessBeanDefinitionRegistryCalled) { + this.factoriesPostProcessed.add(factoryID); + if (!this.registriesPostProcessed.contains(factoryID)) { // BeanDefinitionRegistryPostProcessor hook apparently not supported... // Simply call processConfigurationClasses lazily at this point then. processConfigurationClasses((BeanDefinitionRegistry)beanFactory); diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java index d3800a9d10c..d52a9db656f 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java @@ -82,6 +82,25 @@ public class ConfigurationClassPostProcessorTests { assertSame(foo, bar.foo); } + @Test + public void testProcessingAllowedOnlyOncePerProcessorRegistryPair() { + DefaultListableBeanFactory bf1 = new DefaultListableBeanFactory(); + DefaultListableBeanFactory bf2 = new DefaultListableBeanFactory(); + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.postProcessBeanFactory(bf1); // first invocation -- should succeed + try { + pp.postProcessBeanFactory(bf1); // second invocation for bf1 -- should throw + fail("expected exception"); + } catch (IllegalStateException ex) { + } + pp.postProcessBeanFactory(bf2); // first invocation for bf2 -- should succeed + try { + pp.postProcessBeanFactory(bf2); // second invocation for bf2 -- should throw + fail("expected exception"); + } catch (IllegalStateException ex) { + } + } + @Configuration static class SingletonBeanConfig {