Merge branch '4.0.x' into 4.1.x

Closes gh-51544
This commit is contained in:
Andy Wilkinson
2026-09-02 14:18:14 +01:00
8 changed files with 68 additions and 49 deletions
@@ -61,8 +61,15 @@ import com.tngtech.archunit.lang.syntax.elements.ClassesShould;
import com.tngtech.archunit.lang.syntax.elements.GivenMethodsConjunction; import com.tngtech.archunit.lang.syntax.elements.GivenMethodsConjunction;
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition; 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.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.context.annotation.Role;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.lang.CheckReturnValue; import org.springframework.lang.CheckReturnValue;
import org.springframework.util.ResourceUtils; import org.springframework.util.ResourceUtils;
@@ -111,6 +118,7 @@ final class ArchitectureRules {
rules.add(autoConfigurationClassesShouldBePublicAndFinal()); rules.add(autoConfigurationClassesShouldBePublicAndFinal());
rules.add(autoConfigurationClassesShouldHaveNoPublicMembers()); rules.add(autoConfigurationClassesShouldHaveNoPublicMembers());
rules.add(testAutoConfigurationClassesShouldBePackagePrivateAndFinal()); rules.add(testAutoConfigurationClassesShouldBePackagePrivateAndFinal());
rules.add(importParticipantsShouldOnlyImplementAwareInterfacesIfTheyArePublic());
return List.copyOf(rules); return List.copyOf(rules);
} }
@@ -299,6 +307,36 @@ final class ArchitectureRules {
.because(shouldUse("String.toLowerCase(Locale.ROOT)")); .because(shouldUse("String.toLowerCase(Locale.ROOT)"));
} }
private static ArchRule importParticipantsShouldOnlyImplementAwareInterfacesIfTheyArePublic() {
return ArchRuleDefinition.classes()
.that(areImportParticipants())
.and()
.areNotPublic()
.should(notDirectlyImplementAwareInterfaces())
.allowEmptyShould(true);
}
private static DescribedPredicate<JavaClass> areImportParticipants() {
return JavaClass.Predicates.implement(ImportBeanDefinitionRegistrar.class)
.or(JavaClass.Predicates.implement(ImportSelector.class))
.or(JavaClass.Predicates.implement(TypeFilter.class));
}
private static ArchCondition<JavaClass> notDirectlyImplementAwareInterfaces() {
Set<String> 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( private static ArchRule conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType(
String annotation) { String annotation) {
return methodsThatAreAnnotatedWith(annotation) return methodsThatAreAnnotatedWith(annotation)
@@ -30,7 +30,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 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 * {@link ImportSelector} that returns the original test class so that direct
* {@code @Import} annotations are processed. * {@code @Import} annotations are processed.
*/ */
static class ImportsSelector implements ImportSelector, BeanFactoryAware { static class ImportsSelector implements ImportSelector {
private static final String[] NO_IMPORTS = {}; private static final String[] NO_IMPORTS = {};
@SuppressWarnings("NullAway.Init") private final ConfigurableListableBeanFactory beanFactory;
private ConfigurableListableBeanFactory beanFactory;
@Override ImportsSelector(BeanFactory beanFactory) {
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
} }
@@ -23,7 +23,6 @@ import java.util.Map;
import org.jspecify.annotations.Nullable; 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.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
import org.springframework.boot.context.annotation.ImportCandidates; import org.springframework.boot.context.annotation.ImportCandidates;
@@ -51,9 +50,13 @@ import org.springframework.util.StringUtils;
* @see ImportCandidates * @see ImportCandidates
*/ */
@Order(Ordered.LOWEST_PRECEDENCE) @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 @Override
public String[] selectImports(AnnotationMetadata metadata) { public String[] selectImports(AnnotationMetadata metadata) {
@@ -98,11 +101,6 @@ class ManagementContextConfigurationImportSelector implements DeferredImportSele
return ImportCandidates.load(ManagementContextConfiguration.class, this.classLoader).getCandidates(); 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}. * A management configuration class which can be sorted according to {@code @Order}.
*/ */
@@ -64,7 +64,8 @@ class ManagementContextConfigurationImportSelectorTests {
@Test @Test
void selectImportsLoadsFromResources() { void selectImportsLoadsFromResources() {
String[] imports = new ManagementContextConfigurationImportSelector() String[] imports = new ManagementContextConfigurationImportSelector(
ManagementContextConfigurationImportSelector.class.getClassLoader())
.selectImports(AnnotationMetadata.introspect(EnableChildContext.class)); .selectImports(AnnotationMetadata.introspect(EnableChildContext.class));
Set<String> expected = new HashSet<>(); Set<String> expected = new HashSet<>();
ImportCandidates ImportCandidates
@@ -84,6 +85,7 @@ class ManagementContextConfigurationImportSelectorTests {
private final List<String> factoryNames; private final List<String> factoryNames;
private TestManagementContextConfigurationsImportSelector(Class<?>... classes) { private TestManagementContextConfigurationsImportSelector(Class<?>... classes) {
super(ManagementContextConfigurationImportSelector.class.getClassLoader());
this.factoryNames = Stream.of(classes).map(Class::getName).toList(); this.factoryNames = Stream.of(classes).map(Class::getName).toList();
} }
@@ -19,9 +19,7 @@ package org.springframework.boot.integration.autoconfigure;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAttributes;
@@ -36,13 +34,11 @@ import org.springframework.integration.config.IntegrationComponentScanRegistrar;
* @author Artem Bilan * @author Artem Bilan
* @author Phillip Webb * @author Phillip Webb
*/ */
class IntegrationAutoConfigurationScanRegistrar extends IntegrationComponentScanRegistrar implements BeanFactoryAware { class IntegrationAutoConfigurationScanRegistrar extends IntegrationComponentScanRegistrar {
@SuppressWarnings("NullAway.Init") private final BeanFactory beanFactory;
private BeanFactory beanFactory;
@Override IntegrationAutoConfigurationScanRegistrar(BeanFactory beanFactory) {
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory; this.beanFactory = beanFactory;
} }
@@ -18,9 +18,7 @@ package org.springframework.boot.validation.autoconfigure;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -42,20 +40,18 @@ import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
* @author Matej Nedic * @author Matej Nedic
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
class PrimaryDefaultValidatorPostProcessor implements ImportBeanDefinitionRegistrar, BeanFactoryAware { class PrimaryDefaultValidatorPostProcessor implements ImportBeanDefinitionRegistrar {
/** /**
* The bean name of the auto-configured Validator. * The bean name of the auto-configured Validator.
*/ */
private static final String VALIDATOR_BEAN_NAME = "defaultValidator"; private static final String VALIDATOR_BEAN_NAME = "defaultValidator";
private @Nullable ConfigurableListableBeanFactory beanFactory; private final @Nullable ConfigurableListableBeanFactory beanFactory;
@Override PrimaryDefaultValidatorPostProcessor(BeanFactory beanFactory) {
public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = (beanFactory instanceof ConfigurableListableBeanFactory listableBeanFactory)
if (beanFactory instanceof ConfigurableListableBeanFactory listableBeanFactory) { ? listableBeanFactory : null;
this.beanFactory = listableBeanFactory;
}
} }
@Override @Override
@@ -18,9 +18,7 @@ package org.springframework.boot.web.server.autoconfigure.reactive;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -68,15 +66,13 @@ public class ReactiveWebServerConfiguration {
* Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via * Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
* {@link ImportBeanDefinitionRegistrar} for early registration. * {@link ImportBeanDefinitionRegistrar} for early registration.
*/ */
static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar {
private @Nullable ConfigurableListableBeanFactory beanFactory; private final @Nullable ConfigurableListableBeanFactory beanFactory;
@Override BeanPostProcessorsRegistrar(BeanFactory beanFactory) {
public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = (beanFactory instanceof ConfigurableListableBeanFactory listableBeanFactory)
if (beanFactory instanceof ConfigurableListableBeanFactory listableBeanFactory) { ? listableBeanFactory : null;
this.beanFactory = listableBeanFactory;
}
} }
@Override @Override
@@ -19,9 +19,7 @@ package org.springframework.boot.web.server.autoconfigure.servlet;
import jakarta.servlet.DispatcherType; import jakarta.servlet.DispatcherType;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -86,15 +84,13 @@ public class ServletWebServerConfiguration {
* Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via * Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
* {@link ImportBeanDefinitionRegistrar} for early registration. * {@link ImportBeanDefinitionRegistrar} for early registration.
*/ */
static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar {
private @Nullable ConfigurableListableBeanFactory beanFactory; private final @Nullable ConfigurableListableBeanFactory beanFactory;
@Override BeanPostProcessorsRegistrar(BeanFactory beanFactory) {
public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = (beanFactory instanceof ConfigurableListableBeanFactory listableBeanFactory)
if (beanFactory instanceof ConfigurableListableBeanFactory listableBeanFactory) { ? listableBeanFactory : null;
this.beanFactory = listableBeanFactory;
}
} }
@Override @Override