Spring Framework
  1. Spring Framework
  2. SPR-8808

Allow @Configuration classes to self-@ComponentScan

    Details

    • Type: Improvement Improvement
    • Status: Closed
    • Priority: Minor Minor
    • Resolution: Complete
    • Affects Version/s: 3.1 RC1
    • Fix Version/s: 3.1 RC2
    • Component/s: Core
    • Labels:
      None
    • Last commented by a User:
      false

      Description

      I am using AnnotationConfigWebApplicationContext and XML-free testing support with @ContextConfiguration(classes=...). When pointed @Configuration class contains @ComponentScan with a package containing that class:

      package com.example.foo;
      
      @Configuration
      @ComponentScan(basePackages = "com.example.foo")
      public class Config { }
      

      I get unexpected exception:

      org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'config' for bean class [com.example.foo.Config] conflicts with existing, non-compatible bean definition of same name and class [com.example.foo.Config]
      

      While I understand why it happens, IMHO Spring should handle this case and simply ignore @Configuration found in scanned package if it has already been provided in AnnotationConfigWebApplicationContext.

      Test case attached. On branch master you'll find a test case failing, branch fix fixes the problem by simply @Config to another package.

        Issue Links

          Activity

          Hide
          Chris Beams added a comment -

          You can simply add

          @ComponentScan(excludeFilters = @Filter(Configuration.class))
          

          To avoid this. We are considering making this arrangement the default for excludeFilters as a number of users have already run into it.

          Show
          Chris Beams added a comment - You can simply add @ComponentScan(excludeFilters = @Filter(Configuration.class)) To avoid this. We are considering making this arrangement the default for excludeFilters as a number of users have already run into it.
          Hide
          Chris Beams added a comment -

          Updated issue type and title to reflect previous comment.

          Considering the following implementation to fix:

          $ git diff org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java
          diff --git org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java org.springframework.context/src/main/java/org/springframework/conte
          index 2b0b7d0..86c6eb4 100644
          --- org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java
          +++ org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java
          @@ -121,7 +121,7 @@ public @interface ComponentScan {
                   * Specifies which types are not eligible for component scanning.
                   * @see #resourcePattern()
                   */
          -       Filter[] excludeFilters() default {};
          +       Filter[] excludeFilters() default { @Filter(Configuration.class) };
           
           
                  /**
          
          Show
          Chris Beams added a comment - Updated issue type and title to reflect previous comment. Considering the following implementation to fix: $ git diff org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java diff --git org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java org.springframework.context/src/main/java/org/springframework/conte index 2b0b7d0..86c6eb4 100644 --- org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java +++ org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java @@ -121,7 +121,7 @@ public @interface ComponentScan { * Specifies which types are not eligible for component scanning. * @see #resourcePattern() */ - Filter[] excludeFilters() default {}; + Filter[] excludeFilters() default { @Filter(Configuration.class) }; /**
          Hide
          Tomasz Nurkiewicz added a comment -

          Thanks for your answer and suggested solution. I was aware of @Filter(Configuration.class) workaround. However I can still think of some problematic situations:

          Imagine I add @Configuration com.example.foo.SecondaryConfig class. I expect it to be picked up because it is located in com.example.foo (see Config definition above). However excluding all @Configuration classes by default would exclude it as well. Of course I can reference all @Configuration classes explicitly (e.g. via AnnotationConfigWebApplicationContext#register) but it defeats the purpose of component scanning. Test case included (testcase3.zip, exclude-filters branch).

          So in this particular case I would again recommend excluding only explicitly referenced @Configuration classes in AnnotationConfig*ApplicationContext rather than all of them as the least unexpected behaviour. Also changing the default excludeFilters introduces minor backward incompatibility.

          Show
          Tomasz Nurkiewicz added a comment - Thanks for your answer and suggested solution. I was aware of @Filter(Configuration.class) workaround. However I can still think of some problematic situations: Imagine I add @Configuration com.example.foo.SecondaryConfig class. I expect it to be picked up because it is located in com.example.foo (see Config definition above). However excluding all @Configuration classes by default would exclude it as well. Of course I can reference all @Configuration classes explicitly (e.g. via AnnotationConfigWebApplicationContext#register ) but it defeats the purpose of component scanning. Test case included ( testcase3.zip , exclude-filters branch). So in this particular case I would again recommend excluding only explicitly referenced @Configuration classes in AnnotationConfig*ApplicationContext rather than all of them as the least unexpected behaviour. Also changing the default excludeFilters introduces minor backward incompatibility.
          Hide
          Chris Beams added a comment -

          Complete, just in time for RC2:

          commit 51eb9fe9bdec0d9761dffbc0c021c51a3a60ba32
          Author: Chris Beams <cbeams@vmware.com>
          Date:   Mon Nov 28 21:35:38 2011 +0000
          
              Allow @Configuration classes to self-@ComponentScan
              
              Prior to this change, a @Configuration classes that @ComponentScan
              themselves would result in a ConflictingBeanDefinitionException.
              
              For example:
              
                  package com.foo.config;
              
                  @Configuration
                  @ComponentScan("com.foo");
                  public class AppConfig {
                      // ...
                  }
              
              This resulted in a ConflictingBeanDefinitionException that users have
              typically worked around in the following fashion:
              
                  package com.foo.config;
              
                  @Configuration
                  @ComponentScan(basePackages="com.foo",
                      excludeFilters=@Filter(value=ANNOTATION_TYPE, type=Configuration.class);
                  public class AppConfig {
                      // ...
                  }
              
              This is obviously more verbose and cumbersome than would be desirable,
              and furthermore potentially too constraining as it prohibits the ability
              to include other legitimate @Configuration classes via scanning.
              
              The exception was being thrown because of a logic problem in
              ClassPathBeanDefinitionScanner.  The bean definition for AppConfig gets
              registered once by the user (e.g. when constructing an
              AnnotationConfigApplicationContext), then again when performing the
              component scan for 'com.foo'. Prior to this change,
              ClassPathBeanDefinitionScanner's #isCompatible returned false if the new
              bean definition was anything other than an AnnotatedBeanDefinition.  The
              intention of this check is really to see whether the new bean definition
              is a *scanned* bean definition, i.e. the result of a component-scanning
              operation.  If so, then it becomes safe to assume that the original bean
              definition is the one that should be kept, as it is the one explicitly
              registered by the user.
              
              Therefore, the fix is as simple as narrowing the instanceof check from
              AnnotatedBeanDefinition to its ScannedGenericBeanDefinition subtype.
              
              Note that this commit partially reverts changes introduced in SPR-8307
              that explicitly caught ConflictingBeanDefinitionExceptions when
              processing recursive @ComponentScan definitions, and rethrew as a
              "CircularComponentScanException.  With the changes in this commit,
              such CBDEs will no longer occur, obviating the need for this check and
              for this custom exception type altogether.
              
              Issue: SPR-8808, SPR-8307
              
          
          Show
          Chris Beams added a comment - Complete, just in time for RC2: commit 51eb9fe9bdec0d9761dffbc0c021c51a3a60ba32 Author: Chris Beams <cbeams@vmware.com> Date: Mon Nov 28 21:35:38 2011 +0000 Allow @Configuration classes to self-@ComponentScan Prior to this change, a @Configuration classes that @ComponentScan themselves would result in a ConflictingBeanDefinitionException. For example: package com.foo.config; @Configuration @ComponentScan("com.foo"); public class AppConfig { // ... } This resulted in a ConflictingBeanDefinitionException that users have typically worked around in the following fashion: package com.foo.config; @Configuration @ComponentScan(basePackages="com.foo", excludeFilters=@Filter(value=ANNOTATION_TYPE, type=Configuration.class); public class AppConfig { // ... } This is obviously more verbose and cumbersome than would be desirable, and furthermore potentially too constraining as it prohibits the ability to include other legitimate @Configuration classes via scanning. The exception was being thrown because of a logic problem in ClassPathBeanDefinitionScanner. The bean definition for AppConfig gets registered once by the user (e.g. when constructing an AnnotationConfigApplicationContext), then again when performing the component scan for 'com.foo'. Prior to this change, ClassPathBeanDefinitionScanner's #isCompatible returned false if the new bean definition was anything other than an AnnotatedBeanDefinition. The intention of this check is really to see whether the new bean definition is a *scanned* bean definition, i.e. the result of a component-scanning operation. If so, then it becomes safe to assume that the original bean definition is the one that should be kept, as it is the one explicitly registered by the user. Therefore, the fix is as simple as narrowing the instanceof check from AnnotatedBeanDefinition to its ScannedGenericBeanDefinition subtype. Note that this commit partially reverts changes introduced in SPR-8307 that explicitly caught ConflictingBeanDefinitionExceptions when processing recursive @ComponentScan definitions, and rethrew as a "CircularComponentScanException. With the changes in this commit, such CBDEs will no longer occur, obviating the need for this check and for this custom exception type altogether. Issue: SPR-8808, SPR-8307

            People

            • Assignee:
              Chris Beams
              Reporter:
              Tomasz Nurkiewicz
              Last updater:
              Trevor Marshall
            • Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:
                Days since last comment:
                2 years, 21 weeks, 2 days ago