diff --git a/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyBeanRegistrationAotProcessorTests.java b/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyBeanRegistrationAotProcessorTests.java index e6eeebe1878..a84de8b8b91 100644 --- a/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyBeanRegistrationAotProcessorTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyBeanRegistrationAotProcessorTests.java @@ -29,7 +29,7 @@ import org.springframework.aot.generate.MethodReference; import org.springframework.aot.test.generator.compile.Compiled; import org.springframework.aot.test.generator.compile.TestCompiler; import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.aot.AotFactoriesLoader; +import org.springframework.beans.factory.aot.AotServices; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; import org.springframework.beans.factory.aot.TestBeanRegistrationsAotProcessor; @@ -77,7 +77,7 @@ class ScopedProxyBeanRegistrationAotProcessorTests { @Test void scopedProxyBeanRegistrationAotProcessorIsRegistered() { - assertThat(new AotFactoriesLoader(this.beanFactory).load(BeanRegistrationAotProcessor.class)) + assertThat(AotServices.factoriesAndBeans(this.beanFactory).load(BeanRegistrationAotProcessor.class)) .anyMatch(ScopedProxyBeanRegistrationAotProcessor.class::isInstance); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotFactoriesLoader.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotFactoriesLoader.java deleted file mode 100644 index 1b241d6eee1..00000000000 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotFactoriesLoader.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2002-2022 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.beans.factory.aot; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.core.io.support.SpringFactoriesLoader; -import org.springframework.util.Assert; - -/** - * AOT specific factory loading mechanism for internal use within the framework. - * - *

Loads and instantiates factories of a given type from - * {@value #FACTORIES_RESOURCE_LOCATION} and merges them with matching beans - * from a {@link ListableBeanFactory}. - * - * @author Phillip Webb - * @since 6.0 - * @see SpringFactoriesLoader - */ -public class AotFactoriesLoader { - - /** - * The location to look for AOT factories. - */ - public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring/aot.factories"; - - - private final ListableBeanFactory beanFactory; - - private final SpringFactoriesLoader factoriesLoader; - - - /** - * Create a new {@link AotFactoriesLoader} instance backed by the given bean - * factory. - * @param beanFactory the bean factory to use - */ - public AotFactoriesLoader(ListableBeanFactory beanFactory) { - Assert.notNull(beanFactory, "'beanFactory' must not be null"); - ClassLoader classLoader = (beanFactory instanceof ConfigurableBeanFactory configurableBeanFactory) - ? configurableBeanFactory.getBeanClassLoader() : null; - this.beanFactory = beanFactory; - this.factoriesLoader = SpringFactoriesLoader.forResourceLocation(FACTORIES_RESOURCE_LOCATION, - classLoader); - } - - /** - * Create a new {@link AotFactoriesLoader} instance backed by the given bean - * factory and loading items from the given {@link SpringFactoriesLoader} - * rather than from {@value #FACTORIES_RESOURCE_LOCATION}. - * @param beanFactory the bean factory to use - * @param factoriesLoader the factories loader to use - */ - public AotFactoriesLoader(ListableBeanFactory beanFactory, - SpringFactoriesLoader factoriesLoader) { - - Assert.notNull(beanFactory, "'beanFactory' must not be null"); - Assert.notNull(factoriesLoader, "'factoriesLoader' must not be null"); - this.beanFactory = beanFactory; - this.factoriesLoader = factoriesLoader; - } - - - /** - * Load items from factories file and merge them with any beans defined in - * the {@link DefaultListableBeanFactory}. - * @param the item type - * @param type the item type to load - * @return a list of loaded instances - */ - public List load(Class type) { - List result = new ArrayList<>(); - result.addAll(BeanFactoryUtils - .beansOfTypeIncludingAncestors(this.beanFactory, type, true, false) - .values()); - result.addAll(this.factoriesLoader.load(type)); - AnnotationAwareOrderComparator.sort(result); - return Collections.unmodifiableList(result); - } - -} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotServices.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotServices.java new file mode 100644 index 00000000000..ab271011dfa --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotServices.java @@ -0,0 +1,192 @@ +/* + * Copyright 2002-2022 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans.factory.aot; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * A collection of AOT services that can be {@link Loader loaded} from + * a {@link SpringFactoriesLoader} or obtained from a {@link ListableBeanFactory}. + * + * @author Phillip Webb + * @since 6.0 + * @param the service type + */ +public final class AotServices implements Iterable { + + /** + * The location to look for AOT factories. + */ + public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring/aot.factories"; + + private final List services; + + private final Map beans; + + + private AotServices(List loaded, Map beans) { + List services = new ArrayList<>(); + services.addAll(beans.values()); + services.addAll(loaded); + AnnotationAwareOrderComparator.sort(services); + this.services = Collections.unmodifiableList(services); + this.beans = beans; + } + + + /** + * Return a new {@link Loader} that will obtain AOT services from + * {@value #FACTORIES_RESOURCE_LOCATION}. + * @return a new {@link Loader} instance + */ + public static Loader factories() { + return factories((ClassLoader) null); + } + + /** + * Return a new {@link Loader} that will obtain AOT services from + * {@value #FACTORIES_RESOURCE_LOCATION}. + * @param classLoader the class loader used to load the factories resource + * @return a new {@link Loader} instance + */ + public static Loader factories(@Nullable ClassLoader classLoader) { + return factories(getSpringFactoriesLoader(classLoader)); + } + + /** + * Return a new {@link Loader} that will obtain AOT services from the given + * {@link SpringFactoriesLoader}. + * @param springFactoriesLoader the spring factories loader + * @return a new {@link Loader} instance + */ + public static Loader factories(SpringFactoriesLoader springFactoriesLoader) { + Assert.notNull(springFactoriesLoader, "'springFactoriesLoader' must not be null"); + return new Loader(springFactoriesLoader, null); + } + + /** + * Return a new {@link Loader} that will obtain AOT services from + * {@value #FACTORIES_RESOURCE_LOCATION} as well as the given + * {@link ListableBeanFactory}. + * @param beanFactory the bean factory + * @return a new {@link Loader} instance + */ + public static Loader factoriesAndBeans(ListableBeanFactory beanFactory) { + ClassLoader classLoader = (beanFactory instanceof ConfigurableBeanFactory configurableBeanFactory) + ? configurableBeanFactory.getBeanClassLoader() : null; + return factoriesAndBeans(getSpringFactoriesLoader(classLoader), beanFactory); + } + + /** + * Return a new {@link Loader} that will obtain AOT services from the given + * {@link SpringFactoriesLoader} and {@link ListableBeanFactory}. + * @param springFactoriesLoader the spring factories loader + * @param beanFactory the bean factory + * @return a new {@link Loader} instance + */ + public static Loader factoriesAndBeans(SpringFactoriesLoader springFactoriesLoader, ListableBeanFactory beanFactory) { + Assert.notNull(beanFactory, "'beanFactory' must not be null"); + Assert.notNull(springFactoriesLoader, "'springFactoriesLoader' must not be null"); + return new Loader(springFactoriesLoader, beanFactory); + } + + private static SpringFactoriesLoader getSpringFactoriesLoader( + @Nullable ClassLoader classLoader) { + return SpringFactoriesLoader.forResourceLocation(FACTORIES_RESOURCE_LOCATION, + classLoader); + } + + @Override + public Iterator iterator() { + return this.services.iterator(); + } + + /** + * Return a {@link Stream} of the AOT services. + * @return a stream of the services + */ + public Stream stream() { + return this.services.stream(); + } + + /** + * Return the AOT services as a {@link List}. + * @return a list of the services + */ + public List asList() { + return this.services; + } + + /** + * Return the AOT services that was loaded for the given bean name. + * @param beanName the bean name + * @return the AOT service or {@code null} + */ + @Nullable + public T findByBeanName(String beanName) { + return this.beans.get(beanName); + } + + + /** + * Loader class used to actually load the services. + */ + public static class Loader { + + private final SpringFactoriesLoader springFactoriesLoader; + + private final ListableBeanFactory beanFactory; + + + Loader(SpringFactoriesLoader springFactoriesLoader, @Nullable ListableBeanFactory beanFactory) { + this.springFactoriesLoader = springFactoriesLoader; + this.beanFactory = beanFactory; + } + + + /** + * Load all AOT services of the given type. + * @param the service type + * @param type the service type + * @return a new {@link AotServices} instance + */ + public AotServices load(Class type) { + return new AotServices(this.springFactoriesLoader.load(type), loadBeans(type)); + } + + private Map loadBeans(Class type) { + return (this.beanFactory != null) ? BeanFactoryUtils + .beansOfTypeIncludingAncestors(this.beanFactory, type, true, false) + : Collections.emptyMap(); + } + + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorFactory.java index 341b76366eb..d18d76b297d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorFactory.java @@ -43,9 +43,9 @@ class BeanDefinitionMethodGeneratorFactory { .getLog(BeanDefinitionMethodGeneratorFactory.class); - private final List aotProcessors; + private final AotServices aotProcessors; - private final List excludeFilters; + private final AotServices excludeFilters; /** @@ -54,15 +54,15 @@ class BeanDefinitionMethodGeneratorFactory { * @param beanFactory the bean factory use */ BeanDefinitionMethodGeneratorFactory(ConfigurableListableBeanFactory beanFactory) { - this(new AotFactoriesLoader(beanFactory)); + this(AotServices.factoriesAndBeans(beanFactory)); } /** * Create a new {@link BeanDefinitionMethodGeneratorFactory} backed by the - * given {@link AotFactoriesLoader}. - * @param loader the AOT factory loader to use + * given {@link AotServices.Loader}. + * @param loader the AOT services loader to use */ - BeanDefinitionMethodGeneratorFactory(AotFactoriesLoader loader) { + BeanDefinitionMethodGeneratorFactory(AotServices.Loader loader) { this.aotProcessors = loader.load(BeanRegistrationAotProcessor.class); this.excludeFilters = loader.load(BeanRegistrationExcludeFilter.class); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java index f8e604fa1bf..9e06e5a1176 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java @@ -26,8 +26,7 @@ import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; -import org.springframework.beans.factory.aot.AotFactoriesLoader; -import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.beans.factory.aot.AotServices; import org.springframework.util.ClassUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -43,9 +42,9 @@ class JakartaAnnotationsRuntimeHintsTests { @BeforeEach void setup() { - SpringFactoriesLoader.forResourceLocation(AotFactoriesLoader.FACTORIES_RESOURCE_LOCATION) - .load(RuntimeHintsRegistrar.class).forEach(registrar -> registrar - .registerHints(this.hints, ClassUtils.getDefaultClassLoader())); + AotServices.factories().load(RuntimeHintsRegistrar.class) + .forEach(registrar -> registrar.registerHints(this.hints, + ClassUtils.getDefaultClassLoader())); } @Test diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/AotFactoriesLoaderTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/AotFactoriesLoaderTests.java deleted file mode 100644 index 8d5d7359e33..00000000000 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/AotFactoriesLoaderTests.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2002-2022 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.beans.factory.aot; - -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.core.Ordered; -import org.springframework.core.mock.MockSpringFactoriesLoader; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link AotFactoriesLoader}. - * - * @author Phillip Webb - */ -class AotFactoriesLoaderTests { - - @Test - void createWhenBeanFactoryIsNullThrowsException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new AotFactoriesLoader(null)) - .withMessage("'beanFactory' must not be null"); - } - - @Test - void createWhenSpringFactoriesLoaderIsNullThrowsException() { - ListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - assertThatIllegalArgumentException() - .isThrownBy(() -> new AotFactoriesLoader(beanFactory, null)) - .withMessage("'factoriesLoader' must not be null"); - } - - @Test - void loadLoadsFromBeanFactoryAndSpringFactoriesLoaderInOrder() { - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerSingleton("b1", new TestFactoryImpl(0, "b1")); - beanFactory.registerSingleton("b2", new TestFactoryImpl(2, "b2")); - MockSpringFactoriesLoader springFactoriesLoader = new MockSpringFactoriesLoader(); - springFactoriesLoader.addInstance(TestFactory.class, new TestFactoryImpl(1, "l1")); - springFactoriesLoader.addInstance(TestFactory.class, new TestFactoryImpl(3, "l2")); - AotFactoriesLoader loader = new AotFactoriesLoader(beanFactory, springFactoriesLoader); - List loaded = loader.load(TestFactory.class); - assertThat(loaded).map(Object::toString).containsExactly("b1", "l1", "b2", "l2"); - } - - interface TestFactory { - } - - static class TestFactoryImpl implements TestFactory, Ordered { - - private final int order; - - private final String name; - - TestFactoryImpl(int order, String name) { - this.order = order; - this.name = name; - } - - @Override - public int getOrder() { - return this.order; - } - - @Override - public String toString() { - return this.name; - } - - } - -} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/AotServicesTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/AotServicesTests.java new file mode 100644 index 00000000000..8d11204ed03 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/AotServicesTests.java @@ -0,0 +1,227 @@ +/* + * Copyright 2002-2022 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans.factory.aot; + +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.Ordered; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.core.mock.MockSpringFactoriesLoader; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link AotServices}. + * + * @author Phillip Webb + */ +class AotServicesTests { + + @Test + void factoriesLoadsFromAotFactoriesFiles() { + AotServices loaded = AotServices.factories() + .load(BeanFactoryInitializationAotProcessor.class); + assertThat(loaded) + .anyMatch(BeanFactoryInitializationAotProcessor.class::isInstance); + } + + @Test + void factoriesWithClassLoaderLoadsFromAotFactoriesFile() { + TestSpringFactoriesClassLoader classLoader = new TestSpringFactoriesClassLoader( + "aot-services.factories"); + AotServices loaded = AotServices.factories(classLoader) + .load(TestService.class); + assertThat(loaded).anyMatch(TestServiceImpl.class::isInstance); + } + + @Test + void factoriesWithSpringFactoriesLoaderWhenSpringFactoriesLoaderIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> AotServices.factories((SpringFactoriesLoader) null)) + .withMessage("'springFactoriesLoader' must not be null"); + } + + @Test + void factoriesWithSpringFactoriesLoaderLoadsFromSpringFactoriesLoader() { + MockSpringFactoriesLoader loader = new MockSpringFactoriesLoader(); + loader.addInstance(TestService.class, new TestServiceImpl()); + AotServices loaded = AotServices.factories(loader).load(TestService.class); + assertThat(loaded).anyMatch(TestServiceImpl.class::isInstance); + } + + @Test + void factoriesAndBeansWhenBeanFactoryIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> AotServices.factoriesAndBeans(null)) + .withMessage("'beanFactory' must not be null"); + } + + @Test + void factoriesAndBeansLoadsFromFactoriesAndBeanFactory() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.setBeanClassLoader( + new TestSpringFactoriesClassLoader("aot-services.factories")); + beanFactory.registerBeanDefinition("test", new RootBeanDefinition(TestBean.class)); + AotServices loaded = AotServices.factoriesAndBeans(beanFactory).load(TestService.class); + assertThat(loaded).anyMatch(TestServiceImpl.class::isInstance); + assertThat(loaded).anyMatch(TestBean.class::isInstance); + } + + @Test + void factoriesAndBeansWithSpringFactoriesLoaderLoadsFromSpringFactoriesLoaderAndBeanFactory() { + MockSpringFactoriesLoader loader = new MockSpringFactoriesLoader(); + loader.addInstance(TestService.class, new TestServiceImpl()); + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerBeanDefinition("test", new RootBeanDefinition(TestBean.class)); + AotServices loaded = AotServices.factoriesAndBeans(loader, beanFactory).load(TestService.class); + assertThat(loaded).anyMatch(TestServiceImpl.class::isInstance); + assertThat(loaded).anyMatch(TestBean.class::isInstance); + } + + @Test + void factoriesAndBeansWithSpringFactoriesLoaderWhenSpringFactoriesLoaderIsNullThrowsException() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + assertThatIllegalArgumentException() + .isThrownBy(() -> AotServices.factoriesAndBeans(null, beanFactory)) + .withMessage("'springFactoriesLoader' must not be null"); + } + + @Test + void iteratorReturnsServicesIterator() { + AotServices loaded = AotServices + .factories(new TestSpringFactoriesClassLoader("aot-services.factories")) + .load(TestService.class); + assertThat(loaded.iterator().next()).isInstanceOf(TestServiceImpl.class); + } + + @Test + void streamReturnsServicesStream() { + AotServices loaded = AotServices + .factories(new TestSpringFactoriesClassLoader("aot-services.factories")) + .load(TestService.class); + assertThat(loaded.stream()).anyMatch(TestServiceImpl.class::isInstance); + } + + @Test + void asListReturnsServicesList() { + AotServices loaded = AotServices + .factories(new TestSpringFactoriesClassLoader("aot-services.factories")) + .load(TestService.class); + assertThat(loaded.asList()).anyMatch(TestServiceImpl.class::isInstance); + } + + @Test + void findByBeanNameWhenMatchReturnsService() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerBeanDefinition("test", new RootBeanDefinition(TestBean.class)); + AotServices loaded = AotServices.factoriesAndBeans(beanFactory).load(TestService.class); + assertThat(loaded.findByBeanName("test")).isInstanceOf(TestBean.class); + } + + @Test + void findByBeanNameWhenNoMatchReturnsNull() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerBeanDefinition("test", new RootBeanDefinition(TestBean.class)); + AotServices loaded = AotServices.factoriesAndBeans(beanFactory).load(TestService.class); + assertThat(loaded.findByBeanName("missing")).isNull(); + } + + @Test + void loadLoadsFromBeanFactoryAndSpringFactoriesLoaderInOrder() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("b1", new TestServiceImpl(0, "b1")); + beanFactory.registerSingleton("b2", new TestServiceImpl(2, "b2")); + MockSpringFactoriesLoader springFactoriesLoader = new MockSpringFactoriesLoader(); + springFactoriesLoader.addInstance(TestService.class, + new TestServiceImpl(1, "l1")); + springFactoriesLoader.addInstance(TestService.class, + new TestServiceImpl(3, "l2")); + Iterable loaded = AotServices + .factoriesAndBeans(springFactoriesLoader, beanFactory) + .load(TestService.class); + assertThat(loaded).map(Object::toString).containsExactly("b1", "l1", "b2", "l2"); + } + + + interface TestService { + } + + + static class TestServiceImpl implements TestService, Ordered { + + private final int order; + + private final String name; + + + TestServiceImpl() { + this(0, "test"); + } + + TestServiceImpl(int order, String name) { + this.order = order; + this.name = name; + } + + + @Override + public int getOrder() { + return this.order; + } + + @Override + public String toString() { + return this.name; + } + + } + + + static class TestBean implements TestService { + + } + + + static class TestSpringFactoriesClassLoader extends ClassLoader { + + private final String factoriesName; + + + TestSpringFactoriesClassLoader(String factoriesName) { + super(Thread.currentThread().getContextClassLoader()); + this.factoriesName = factoriesName; + } + + + @Override + public Enumeration getResources(String name) throws IOException { + return (!"META-INF/spring/aot.factories".equals(name)) + ? super.getResources(name) + : super.getResources("org/springframework/beans/factory/aot/" + + this.factoriesName); + } + + } + +} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorFactoryTests.java index ba58f2ec1da..95f1e0da753 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorFactoryTests.java @@ -43,7 +43,7 @@ class BeanDefinitionMethodGeneratorFactoryTests { new MockBeanRegistrationExcludeFilter(true, 0)); RegisteredBean registeredBean = registerTestBean(beanFactory); BeanDefinitionMethodGeneratorFactory methodGeneratorFactory = new BeanDefinitionMethodGeneratorFactory( - new AotFactoriesLoader(beanFactory, springFactoriesLoader)); + AotServices.factoriesAndBeans(springFactoriesLoader, beanFactory)); assertThat(methodGeneratorFactory.getBeanDefinitionMethodGenerator(registeredBean, null)).isNull(); } @@ -56,7 +56,7 @@ class BeanDefinitionMethodGeneratorFactoryTests { beanFactory.registerSingleton("filter", new MockBeanRegistrationExcludeFilter(true, 0)); BeanDefinitionMethodGeneratorFactory methodGeneratorFactory = new BeanDefinitionMethodGeneratorFactory( - new AotFactoriesLoader(beanFactory, springFactoriesLoader)); + AotServices.factoriesAndBeans(springFactoriesLoader, beanFactory)); assertThat(methodGeneratorFactory.getBeanDefinitionMethodGenerator(registeredBean, null)).isNull(); } @@ -77,7 +77,7 @@ class BeanDefinitionMethodGeneratorFactoryTests { beanFactory.registerSingleton("filter6", filter6); RegisteredBean registeredBean = registerTestBean(beanFactory); BeanDefinitionMethodGeneratorFactory methodGeneratorFactory = new BeanDefinitionMethodGeneratorFactory( - new AotFactoriesLoader(beanFactory, springFactoriesLoader)); + AotServices.factoriesAndBeans(springFactoriesLoader, beanFactory)); assertThat(methodGeneratorFactory.getBeanDefinitionMethodGenerator(registeredBean, null)).isNull(); assertThat(filter1.wasCalled()).isTrue(); @@ -103,7 +103,7 @@ class BeanDefinitionMethodGeneratorFactoryTests { loaderProcessor); RegisteredBean registeredBean = registerTestBean(beanFactory); BeanDefinitionMethodGeneratorFactory methodGeneratorFactory = new BeanDefinitionMethodGeneratorFactory( - new AotFactoriesLoader(beanFactory, springFactoriesLoader)); + AotServices.factoriesAndBeans(springFactoriesLoader, beanFactory)); BeanDefinitionMethodGenerator methodGenerator = methodGeneratorFactory .getBeanDefinitionMethodGenerator(registeredBean, null); assertThat(methodGenerator).extracting("aotContributions").asList() @@ -121,7 +121,7 @@ class BeanDefinitionMethodGeneratorFactoryTests { .rootBeanDefinition(TestBeanRegistrationAotProcessorBean.class).getBeanDefinition()); RegisteredBean registeredBean2 = RegisteredBean.of(beanFactory, "test2"); BeanDefinitionMethodGeneratorFactory methodGeneratorFactory = new BeanDefinitionMethodGeneratorFactory( - new AotFactoriesLoader(beanFactory, springFactoriesLoader)); + AotServices.factoriesAndBeans(springFactoriesLoader, beanFactory)); assertThat(methodGeneratorFactory.getBeanDefinitionMethodGenerator(registeredBean1, null)).isNull(); assertThat(methodGeneratorFactory.getBeanDefinitionMethodGenerator(registeredBean2, null)).isNull(); } @@ -134,7 +134,7 @@ class BeanDefinitionMethodGeneratorFactoryTests { .rootBeanDefinition(TestBeanRegistrationAotProcessorAndFilterBean.class).getBeanDefinition()); RegisteredBean registeredBean1 = RegisteredBean.of(beanFactory, "test"); BeanDefinitionMethodGeneratorFactory methodGeneratorFactory = new BeanDefinitionMethodGeneratorFactory( - new AotFactoriesLoader(beanFactory, springFactoriesLoader)); + AotServices.factoriesAndBeans(springFactoriesLoader, beanFactory)); assertThat(methodGeneratorFactory.getBeanDefinitionMethodGenerator(registeredBean1, null)).isNotNull(); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java index 2db44036c36..4b949f8838f 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java @@ -75,7 +75,7 @@ class BeanDefinitionMethodGeneratorTests { this.generationContext = new TestGenerationContext(); this.beanFactory = new DefaultListableBeanFactory(); this.methodGeneratorFactory = new BeanDefinitionMethodGeneratorFactory( - new AotFactoriesLoader(this.beanFactory, new MockSpringFactoriesLoader())); + AotServices.factoriesAndBeans( new MockSpringFactoriesLoader(), beanFactory)); this.beanRegistrationsCode = new MockBeanRegistrationsCode(this.generationContext); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java index b9604e5a639..f84239532f7 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java @@ -68,7 +68,7 @@ class BeanRegistrationsAotContributionTests { MockSpringFactoriesLoader springFactoriesLoader = new MockSpringFactoriesLoader(); this.beanFactory = new DefaultListableBeanFactory(); this.methodGeneratorFactory = new BeanDefinitionMethodGeneratorFactory( - new AotFactoriesLoader(this.beanFactory, springFactoriesLoader)); + AotServices.factoriesAndBeans(springFactoriesLoader, this.beanFactory)); this.generationContext = new TestGenerationContext(); this.beanFactoryInitializationCode = new MockBeanFactoryInitializationCode(this.generationContext); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessorTests.java index e716067f53c..efd8fcdbdae 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessorTests.java @@ -35,7 +35,7 @@ class BeanRegistrationsAotProcessorTests { @Test void beanRegistrationsAotProcessorIsRegistered() { - assertThat(new AotFactoriesLoader(new DefaultListableBeanFactory()) + assertThat(AotServices.factoriesAndBeans(new DefaultListableBeanFactory()) .load(BeanFactoryInitializationAotProcessor.class)) .anyMatch(BeanRegistrationsAotProcessor.class::isInstance); } diff --git a/spring-beans/src/test/resources/org/springframework/beans/factory/aot/aot-services.factories b/spring-beans/src/test/resources/org/springframework/beans/factory/aot/aot-services.factories new file mode 100644 index 00000000000..f7e22809638 --- /dev/null +++ b/spring-beans/src/test/resources/org/springframework/beans/factory/aot/aot-services.factories @@ -0,0 +1,2 @@ +org.springframework.beans.factory.aot.AotServicesTests$TestService=\ +org.springframework.beans.factory.aot.AotServicesTests$TestServiceImpl \ No newline at end of file diff --git a/spring-context/src/main/java/org/springframework/context/aot/BeanFactoryInitializationAotContributions.java b/spring-context/src/main/java/org/springframework/context/aot/BeanFactoryInitializationAotContributions.java index a9e5d164ce5..878ed204c0b 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/BeanFactoryInitializationAotContributions.java +++ b/spring-context/src/main/java/org/springframework/context/aot/BeanFactoryInitializationAotContributions.java @@ -21,7 +21,7 @@ import java.util.Collections; import java.util.List; import org.springframework.aot.generate.GenerationContext; -import org.springframework.beans.factory.aot.AotFactoriesLoader; +import org.springframework.beans.factory.aot.AotServices; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; @@ -41,19 +41,19 @@ class BeanFactoryInitializationAotContributions { BeanFactoryInitializationAotContributions(DefaultListableBeanFactory beanFactory) { - this(beanFactory, new AotFactoriesLoader(beanFactory)); + this(beanFactory, AotServices.factoriesAndBeans(beanFactory)); } BeanFactoryInitializationAotContributions(DefaultListableBeanFactory beanFactory, - AotFactoriesLoader loader) { + AotServices.Loader loader) { this.contributions = getContributions(beanFactory, getProcessors(loader)); } - private List getProcessors( - AotFactoriesLoader loader) { + private static List getProcessors( + AotServices.Loader loader) { List processors = new ArrayList<>( - loader.load(BeanFactoryInitializationAotProcessor.class)); + loader.load(BeanFactoryInitializationAotProcessor.class).asList()); processors.add(new RuntimeHintsBeanFactoryInitializationAotProcessor()); return Collections.unmodifiableList(processors); } diff --git a/spring-context/src/main/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessor.java b/spring-context/src/main/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessor.java index e9d50e1a4a9..c83224f4b45 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessor.java @@ -28,7 +28,7 @@ import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.aot.AotFactoriesLoader; +import org.springframework.beans.factory.aot.AotServices; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; @@ -56,9 +56,8 @@ class RuntimeHintsBeanFactoryInitializationAotProcessor @Override public BeanFactoryInitializationAotContribution processAheadOfTime( ConfigurableListableBeanFactory beanFactory) { - AotFactoriesLoader loader = new AotFactoriesLoader(beanFactory); - Map, RuntimeHintsRegistrar> registrars = loader - .load(RuntimeHintsRegistrar.class).stream() + Map, RuntimeHintsRegistrar> registrars = AotServices + .factoriesAndBeans(beanFactory).load(RuntimeHintsRegistrar.class).stream() .collect(LinkedHashMap::new, (map, item) -> map.put(item.getClass(), item), Map::putAll); extractFromBeanFactory(beanFactory).forEach(registrarClass -> registrars.computeIfAbsent(registrarClass, BeanUtils::instantiateClass));