diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java index b96a600e43f..22e1533c3a3 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java @@ -61,8 +61,15 @@ import com.tngtech.archunit.lang.syntax.elements.ClassesShould; import com.tngtech.archunit.lang.syntax.elements.GivenMethodsConjunction; import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.context.annotation.ImportSelector; import org.springframework.context.annotation.Role; +import org.springframework.core.type.filter.TypeFilter; import org.springframework.util.ResourceUtils; /** @@ -109,6 +116,7 @@ final class ArchitectureRules { rules.add(autoConfigurationClassesShouldBePublicAndFinal()); rules.add(autoConfigurationClassesShouldHaveNoPublicMembers()); rules.add(testAutoConfigurationClassesShouldBePackagePrivateAndFinal()); + rules.add(importParticipantsShouldOnlyImplementAwareInterfacesIfTheyArePublic()); return List.copyOf(rules); } @@ -277,6 +285,36 @@ final class ArchitectureRules { .because(shouldUse("String.toLowerCase(Locale.ROOT)")); } + private static ArchRule importParticipantsShouldOnlyImplementAwareInterfacesIfTheyArePublic() { + return ArchRuleDefinition.classes() + .that(areImportParticipants()) + .and() + .areNotPublic() + .should(notDirectlyImplementAwareInterfaces()) + .allowEmptyShould(true); + } + + private static DescribedPredicate areImportParticipants() { + return JavaClass.Predicates.implement(ImportBeanDefinitionRegistrar.class) + .or(JavaClass.Predicates.implement(ImportSelector.class)) + .or(JavaClass.Predicates.implement(TypeFilter.class)); + } + + private static ArchCondition notDirectlyImplementAwareInterfaces() { + Set awareNames = Set.of(BeanClassLoaderAware.class.getName(), BeanFactoryAware.class.getName(), + EnvironmentAware.class.getName(), ResourceLoaderAware.class.getName()); + return ArchCondition + .from(DescribedPredicate.describe("not directly implement BeanClassLoaderAware, BeanFactoryAware," + + " EnvironmentAware, or ResourceLoaderAware", (javaClass) -> { + for (JavaClass rawInterface : javaClass.getRawInterfaces()) { + if (awareNames.contains(rawInterface.getFullName())) { + return false; + } + } + return true; + })); + } + private static ArchRule conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType( String annotation) { return methodsThatAreAnnotatedWith(annotation) diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java index 70645fa416f..de07c1cd627 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java @@ -30,7 +30,6 @@ import org.jspecify.annotations.Nullable; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -170,15 +169,13 @@ class ImportsContextCustomizer implements ContextCustomizer { * {@link ImportSelector} that returns the original test class so that direct * {@code @Import} annotations are processed. */ - static class ImportsSelector implements ImportSelector, BeanFactoryAware { + static class ImportsSelector implements ImportSelector { private static final String[] NO_IMPORTS = {}; - @SuppressWarnings("NullAway.Init") - private ConfigurableListableBeanFactory beanFactory; + private final ConfigurableListableBeanFactory beanFactory; - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + ImportsSelector(BeanFactory beanFactory) { this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; } diff --git a/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextConfigurationImportSelector.java b/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextConfigurationImportSelector.java index c20b77e2ea7..416ff3612e9 100644 --- a/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextConfigurationImportSelector.java +++ b/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextConfigurationImportSelector.java @@ -23,7 +23,6 @@ import java.util.Map; import org.jspecify.annotations.Nullable; -import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType; import org.springframework.boot.context.annotation.ImportCandidates; @@ -51,9 +50,13 @@ import org.springframework.util.StringUtils; * @see ImportCandidates */ @Order(Ordered.LOWEST_PRECEDENCE) -class ManagementContextConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware { +class ManagementContextConfigurationImportSelector implements DeferredImportSelector { - private @Nullable ClassLoader classLoader; + private final ClassLoader classLoader; + + ManagementContextConfigurationImportSelector(ClassLoader classLoader) { + this.classLoader = classLoader; + } @Override public String[] selectImports(AnnotationMetadata metadata) { @@ -98,11 +101,6 @@ class ManagementContextConfigurationImportSelector implements DeferredImportSele return ImportCandidates.load(ManagementContextConfiguration.class, this.classLoader).getCandidates(); } - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; - } - /** * A management configuration class which can be sorted according to {@code @Order}. */ diff --git a/module/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextConfigurationImportSelectorTests.java b/module/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextConfigurationImportSelectorTests.java index 0faed22268f..3261530123d 100644 --- a/module/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextConfigurationImportSelectorTests.java +++ b/module/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextConfigurationImportSelectorTests.java @@ -64,7 +64,8 @@ class ManagementContextConfigurationImportSelectorTests { @Test void selectImportsLoadsFromResources() { - String[] imports = new ManagementContextConfigurationImportSelector() + String[] imports = new ManagementContextConfigurationImportSelector( + ManagementContextConfigurationImportSelector.class.getClassLoader()) .selectImports(AnnotationMetadata.introspect(EnableChildContext.class)); Set expected = new HashSet<>(); ImportCandidates @@ -84,6 +85,7 @@ class ManagementContextConfigurationImportSelectorTests { private final List factoryNames; private TestManagementContextConfigurationsImportSelector(Class... classes) { + super(ManagementContextConfigurationImportSelector.class.getClassLoader()); this.factoryNames = Stream.of(classes).map(Class::getName).toList(); }