From 6ff1e2d622049abb4dbeacd878c4a6eaeafd46d9 Mon Sep 17 00:00:00 2001 From: Daeho Kwon Date: Fri, 25 Apr 2025 03:29:00 +0900 Subject: [PATCH 1/3] Support `@Import` on interfaces Closes gh-34805 Signed-off-by: Daeho Kwon --- .../annotation/ConfigurationClassParser.java | 8 +++++ .../annotation/ImportSelectorTests.java | 33 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 104e2fdd2102..ca514a593db9 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -98,6 +98,7 @@ * @author Phillip Webb * @author Sam Brannen * @author Stephane Nicoll + * @author Daeho Kwon * @since 3.0 * @see ConfigurationClassBeanDefinitionReader */ @@ -549,6 +550,9 @@ private Set getImports(SourceClass sourceClass) throws IOException *

For example, it is common for a {@code @Configuration} class to declare direct * {@code @Import}s in addition to meta-imports originating from an {@code @Enable} * annotation. + *

In addition, {@code @Import} annotations declared on interfaces implemented by + * the configuration class are also considered. This allows imports to be triggered + * indirectly via marker interfaces or shared base interfaces. * @param sourceClass the class to search * @param imports the imports collected so far * @param visited used to track visited classes to prevent infinite recursion @@ -565,6 +569,10 @@ private void collectImports(SourceClass sourceClass, Set imports, S } } imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value")); + + for (SourceClass ifc : sourceClass.getInterfaces()) { + collectImports(ifc, imports, visited); + } } } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java index 2014c12000b2..fba0fab9426a 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java @@ -62,6 +62,7 @@ * * @author Phillip Webb * @author Stephane Nicoll + * @author Daeho Kwon */ @SuppressWarnings("resource") public class ImportSelectorTests { @@ -203,6 +204,38 @@ void invokeAwareMethodsInImportGroup() { assertThat(TestImportGroup.environment).isEqualTo(context.getEnvironment()); } + @Test + void importAnnotationOnImplementedInterfaceIsRespected() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(InterfaceBasedConfig.class); + context.refresh(); + + assertThat(context.getBean(ImportedConfig.class)).isNotNull(); + assertThat(context.getBean(ImportedBean.class)).isNotNull(); + assertThat(context.getBean(ImportedBean.class).name()).isEqualTo("imported"); + } + + @Import(ImportedConfig.class) + interface ConfigImportMarker { + } + + @Configuration + static class InterfaceBasedConfig implements ConfigImportMarker { + } + + static class ImportedBean { + String name() { + return "imported"; + } + } + + @Configuration + static class ImportedConfig { + @Bean + ImportedBean importedBean() { + return new ImportedBean(); + } + } @Configuration @Import(SampleImportSelector.class) From a3dd3d2aa39c82de6f2cd2d819955c947d90b131 Mon Sep 17 00:00:00 2001 From: Daeho Kwon Date: Wed, 4 Jun 2025 00:18:07 +0900 Subject: [PATCH 2/3] Adjust `@Import` processing to prioritize local declarations Closes gh-34805 Signed-off-by: Daeho Kwon --- .../annotation/ConfigurationClassParser.java | 9 +++-- .../annotation/ImportSelectorTests.java | 36 ++++++++++++++++--- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index ca514a593db9..b8086aca92fa 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -550,7 +550,7 @@ private Set getImports(SourceClass sourceClass) throws IOException *

For example, it is common for a {@code @Configuration} class to declare direct * {@code @Import}s in addition to meta-imports originating from an {@code @Enable} * annotation. - *

In addition, {@code @Import} annotations declared on interfaces implemented by + *

As of Spring Framework 7.0, {@code @Import} annotations declared on interfaces implemented by * the configuration class are also considered. This allows imports to be triggered * indirectly via marker interfaces or shared base interfaces. * @param sourceClass the class to search @@ -562,6 +562,9 @@ private void collectImports(SourceClass sourceClass, Set imports, S throws IOException { if (visited.add(sourceClass)) { + for (SourceClass ifc : sourceClass.getInterfaces()) { + collectImports(ifc, imports, visited); + } for (SourceClass annotation : sourceClass.getAnnotations()) { String annName = annotation.getMetadata().getClassName(); if (!annName.equals(Import.class.getName())) { @@ -569,10 +572,6 @@ private void collectImports(SourceClass sourceClass, Set imports, S } } imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value")); - - for (SourceClass ifc : sourceClass.getInterfaces()) { - collectImports(ifc, imports, visited); - } } } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java index fba0fab9426a..9eda34d19cac 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java @@ -206,15 +206,24 @@ void invokeAwareMethodsInImportGroup() { @Test void importAnnotationOnImplementedInterfaceIsRespected() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(InterfaceBasedConfig.class); - context.refresh(); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + InterfaceBasedConfig.class); assertThat(context.getBean(ImportedConfig.class)).isNotNull(); assertThat(context.getBean(ImportedBean.class)).isNotNull(); assertThat(context.getBean(ImportedBean.class).name()).isEqualTo("imported"); } + @Test + void localImportShouldOverrideInterfaceImport() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + OverridingConfig.class); + + assertThat(context.getBean(ImportedConfig.class)).isNotNull(); + assertThat(context.getBean(ImportedBean.class)).isNotNull(); + assertThat(context.getBean(ImportedBean.class).name()).isEqualTo("from class"); + } + @Import(ImportedConfig.class) interface ConfigImportMarker { } @@ -223,9 +232,28 @@ interface ConfigImportMarker { static class InterfaceBasedConfig implements ConfigImportMarker { } + @Configuration + static class OverridingConfig implements ConfigImportMarker { + @Bean + ImportedBean importedBean() { + return new ImportedBean("from class"); + } + } + static class ImportedBean { + + private final String name; + + ImportedBean() { + this.name = "imported"; + } + + ImportedBean(String name) { + this.name = name; + } + String name() { - return "imported"; + return name; } } From fdbfa3ee960c47bfa069ed52b96d96c407eaa6b7 Mon Sep 17 00:00:00 2001 From: Daeho Kwon Date: Wed, 4 Jun 2025 21:00:25 +0900 Subject: [PATCH 3/3] Add `@Import` to OverridingConfig Closes gh-34805 Signed-off-by: Daeho Kwon --- .../context/annotation/ImportSelectorTests.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java index 9eda34d19cac..974100791179 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -233,7 +233,12 @@ static class InterfaceBasedConfig implements ConfigImportMarker { } @Configuration + @Import(OverridingImportedConfig.class) static class OverridingConfig implements ConfigImportMarker { + } + + @Configuration + static class OverridingImportedConfig { @Bean ImportedBean importedBean() { return new ImportedBean("from class");