From 4581324038094982b266255d4c994f0d7a04d8cb Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 4 Jun 2025 14:52:49 +0200 Subject: [PATCH] =?UTF-8?q?Polish=20support=20for=20@=E2=81=A0Import=20on?= =?UTF-8?q?=20interfaces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update @⁠Import Javadoc - Move tests from ImportSelectorTests to ImportTests See gh-34820 --- .../annotation/ConfigurationClassParser.java | 9 +-- .../context/annotation/Import.java | 6 ++ .../annotation/ImportSelectorTests.java | 68 +------------------ .../annotation/configuration/ImportTests.java | 59 +++++++++++++++- 4 files changed, 70 insertions(+), 72 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 b8086aca92..1f88fbea9f 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,12 +550,13 @@ class ConfigurationClassParser { *

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. - *

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. + *

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 * @param imports the imports collected so far - * @param visited used to track visited classes to prevent infinite recursion + * @param visited used to track visited classes and interfaces to prevent infinite + * recursion * @throws IOException if there is any problem reading metadata from the named class */ private void collectImports(SourceClass sourceClass, Set imports, Set visited) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Import.java b/spring-context/src/main/java/org/springframework/context/annotation/Import.java index 60ff80dcae..7d26dc6f8b 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Import.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Import.java @@ -47,6 +47,12 @@ import org.springframework.beans.factory.BeanRegistrar; * directly declared imports to override beans registered via {@code @Import} * meta-annotations. * + *

As of Spring Framework 7.0, {@code @Import} annotations declared on interfaces + * implemented by {@code @Configuration} classes are also supported. Locally declared + * {@code @Import} annotations are processed after {@code @Import} annotations on + * interfaces, which allows local imports to override beans registered via + * {@code @Import} annotations inherited from interfaces. + * *

If XML or other non-{@code @Configuration} bean definition resources need to be * imported, use the {@link ImportResource @ImportResource} annotation instead. * 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 9741007911..2014c12000 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-2025 the original author or authors. + * Copyright 2002-2024 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. @@ -62,7 +62,6 @@ import static org.mockito.Mockito.spy; * * @author Phillip Webb * @author Stephane Nicoll - * @author Daeho Kwon */ @SuppressWarnings("resource") public class ImportSelectorTests { @@ -204,71 +203,6 @@ public class ImportSelectorTests { assertThat(TestImportGroup.environment).isEqualTo(context.getEnvironment()); } - @Test - void importAnnotationOnImplementedInterfaceIsRespected() { - 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 { - } - - @Configuration - 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"); - } - } - - static class ImportedBean { - - private final String name; - - ImportedBean() { - this.name = "imported"; - } - - ImportedBean(String name) { - this.name = name; - } - - String name() { - return name; - } - } - - @Configuration - static class ImportedConfig { - @Bean - ImportedBean importedBean() { - return new ImportedBean(); - } - } @Configuration @Import(SampleImportSelector.class) diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java index 80172e5380..f46a35af02 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.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. @@ -38,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Chris Beams * @author Juergen Hoeller + * @author Daeho Kwon */ class ImportTests { @@ -391,4 +392,60 @@ class ImportTests { assertThat(ctx.getBeansOfType(SiblingImportingConfigB.class)).hasSize(1); } + @Test // gh-34820 + void importAnnotationOnImplementedInterfaceIsRespected() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(InterfaceBasedConfig.class); + + assertThat(context.getBean(ImportedConfig.class)).isNotNull(); + assertThat(context.getBean(ImportedBean.class)).hasFieldOrPropertyWithValue("name", "imported"); + + context.close(); + } + + @Test // gh-34820 + void localImportShouldOverrideInterfaceImport() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(OverridingConfig.class); + + assertThat(context.getBean(ImportedConfig.class)).isNotNull(); + assertThat(context.getBean(OverridingImportedConfig.class)).isNotNull(); + assertThat(context.getBean(ImportedBean.class)).hasFieldOrPropertyWithValue("name", "from class"); + + context.close(); + } + + + record ImportedBean(String name) { + } + + @Configuration + static class ImportedConfig { + + @Bean + ImportedBean importedBean() { + return new ImportedBean("imported"); + } + } + + @Configuration + static class OverridingImportedConfig { + + @Bean + ImportedBean importedBean() { + return new ImportedBean("from class"); + } + } + + @Import(ImportedConfig.class) + interface ConfigImportMarker { + } + + @Configuration + static class InterfaceBasedConfig implements ConfigImportMarker { + } + + @Configuration + @Import(OverridingImportedConfig.class) + static class OverridingConfig implements ConfigImportMarker { + } + }