diff --git a/spring-context/src/main/java/org/springframework/context/aot/AotApplicationContextInitializer.java b/spring-context/src/main/java/org/springframework/context/aot/AotApplicationContextInitializer.java new file mode 100644 index 0000000000..798f18dcf1 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/aot/AotApplicationContextInitializer.java @@ -0,0 +1,113 @@ +/* + * 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.context.aot; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeanInstantiationException; +import org.springframework.beans.BeanUtils; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.log.LogMessage; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Specialized {@link ApplicationContextInitializer} used to initialize a + * {@link ConfigurableApplicationContext} using artifacts that were generated + * ahead-of-time. + *

+ * Instances of this initializer are usually created using + * {@link #forInitializerClasses(String...)}, passing in the names of code + * generated initializer classes. + * + * @author Stephane Nicoll + * @author Phillip Webb + * @since 6.0 + * @param the application context type + */ +@FunctionalInterface +public interface AotApplicationContextInitializer + extends ApplicationContextInitializer { + + /** + * Factory method to create a new {@link AotApplicationContextInitializer} + * instance that delegates to other initializers loaded from the given set + * of class names. + * @param the application context type + * @param initializerClassNames the class names of the initializers to load + * @return a new {@link AotApplicationContextInitializer} instance + */ + static ApplicationContextInitializer forInitializerClasses( + String... initializerClassNames) { + + Assert.noNullElements(initializerClassNames, "'initializerClassNames' must not contain null elements"); + return applicationContext -> initialize(applicationContext, initializerClassNames); + } + + private static void initialize( + C applicationContext, String... initializerClassNames) { + Log logger = LogFactory.getLog(AotApplicationContextInitializer.class); + ClassLoader classLoader = applicationContext.getClassLoader(); + logger.debug("Initializing ApplicationContext with AOT"); + for (String initializerClassName : initializerClassNames) { + logger.trace(LogMessage.format("Applying %s", initializerClassName)); + instantiateInitializer(classLoader, initializerClassName) + .initialize(applicationContext); + } + } + + @SuppressWarnings("unchecked") + static ApplicationContextInitializer instantiateInitializer( + ClassLoader classLoader, String initializerClassName) { + try { + Class initializerClass = ClassUtils.resolveClassName(initializerClassName, classLoader); + Assert.isAssignable(ApplicationContextInitializer.class, initializerClass); + return (ApplicationContextInitializer) BeanUtils.instantiateClass(initializerClass); + } + catch (BeanInstantiationException ex) { + throw new IllegalArgumentException( + "Failed to instantiate ApplicationContextInitializer: " + initializerClassName, ex); + } + } + + /** + * Return a new {@link List} containing only {@link AotApplicationContextInitializer} instances. + * @param the application context type + * @param initializers the source initializers + * @return a list of the {@link AotApplicationContextInitializer} instances + */ + @SuppressWarnings("unchecked") + public static List> getAotInitializers( + Collection> initializers) { + + Assert.notNull(initializers, "'initializers' must not be null"); + List> aotInitializers = new ArrayList<>(); + for (ApplicationContextInitializer candidate : initializers) { + if (candidate instanceof AotApplicationContextInitializer aotInitializer) { + aotInitializers.add((AotApplicationContextInitializer) aotInitializer); + } + } + return aotInitializers; + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/aot/ApplicationContextAotInitializer.java b/spring-context/src/main/java/org/springframework/context/aot/ApplicationContextAotInitializer.java index efc148ba76..3671aadb27 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/ApplicationContextAotInitializer.java +++ b/spring-context/src/main/java/org/springframework/context/aot/ApplicationContextAotInitializer.java @@ -16,25 +16,19 @@ package org.springframework.context.aot; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.BeanUtils; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.lang.Nullable; -import org.springframework.util.ClassUtils; /** * Initializes a {@link ConfigurableApplicationContext} using AOT optimizations. * * @author Stephane Nicoll * @since 6.0 + * @deprecated in favor of {@link AotApplicationContextInitializer} */ +@Deprecated(since = "6.0", forRemoval = true) public class ApplicationContextAotInitializer { - private static final Log logger = LogFactory.getLog(ApplicationContextAotInitializer.class); - /** * Initialize the specified application context using the specified * {@link ApplicationContextInitializer} class names. Each class is @@ -43,35 +37,7 @@ public class ApplicationContextAotInitializer { * @param initializerClassNames the application context initializer class names */ public void initialize(ConfigurableApplicationContext context, String... initializerClassNames) { - if (logger.isDebugEnabled()) { - logger.debug("Initializing ApplicationContext with AOT"); - } - for (String initializerClassName : initializerClassNames) { - if (logger.isTraceEnabled()) { - logger.trace("Applying " + initializerClassName); - } - loadInitializer(initializerClassName, context.getClassLoader()).initialize(context); - } - } - - @SuppressWarnings("unchecked") - private ApplicationContextInitializer loadInitializer( - String className, @Nullable ClassLoader classLoader) { - Object initializer = instantiate(className, classLoader); - if (!(initializer instanceof ApplicationContextInitializer)) { - throw new IllegalArgumentException("Not an ApplicationContextInitializer: " + className); - } - return (ApplicationContextInitializer) initializer; - } - - private static Object instantiate(String className, @Nullable ClassLoader classLoader) { - try { - Class type = ClassUtils.forName(className, classLoader); - return BeanUtils.instantiateClass(type); - } - catch (Exception ex) { - throw new IllegalArgumentException("Failed to instantiate ApplicationContextInitializer: " + className, ex); - } + AotApplicationContextInitializer.forInitializerClasses(initializerClassNames).initialize(context); } } diff --git a/spring-context/src/test/java/org/springframework/context/aot/AotApplicationContextInitializerTests.java b/spring-context/src/test/java/org/springframework/context/aot/AotApplicationContextInitializerTests.java new file mode 100644 index 0000000000..d4b455f401 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/aot/AotApplicationContextInitializerTests.java @@ -0,0 +1,119 @@ +/* + * 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.context.aot; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.support.GenericApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link AotApplicationContextInitializer}. + * + * @author Stephane Nicoll + */ +class AotApplicationContextInitializerTests { + + @Test + void initializeInvokesApplicationContextInitializer() { + GenericApplicationContext context = new GenericApplicationContext(); + AotApplicationContextInitializer.forInitializerClasses( + TestApplicationContextInitializer.class.getName()) + .initialize(context); + assertThat(context.getBeanDefinitionNames()).containsExactly("test"); + } + + @Test + void initializeInvokesApplicationContextInitializersInOrder() { + GenericApplicationContext context = new GenericApplicationContext(); + AotApplicationContextInitializer.forInitializerClasses( + AnotherApplicationContextInitializer.class.getName(), + TestApplicationContextInitializer.class.getName()) + .initialize(context); + assertThat(context.getBeanDefinitionNames()).containsExactly("another", "test"); + } + + @Test + void initializeWhenClassIsNotApplicationContextInitializerThrowsException() { + GenericApplicationContext context = new GenericApplicationContext(); + assertThatIllegalArgumentException() + .isThrownBy(() -> AotApplicationContextInitializer.forInitializerClasses("java.lang.String") + .initialize(context)) + .withMessageContaining("not assignable") + .withMessageContaining("ApplicationContextInitializer") + .withMessageContaining("java.lang.String"); + } + + @Test + void initializeWhenInitializerHasNoDefaultConstructorThrowsException() { + GenericApplicationContext context = new GenericApplicationContext(); + assertThatIllegalArgumentException() + .isThrownBy(() -> AotApplicationContextInitializer.forInitializerClasses( + ConfigurableApplicationContextInitializer.class.getName()).initialize(context)) + .withMessageContaining("Failed to instantiate ApplicationContextInitializer: ") + .withMessageContaining(ConfigurableApplicationContextInitializer.class.getName()); + } + + @Test + void getAotInitializersReturnsOnlyAotInitializers() { + ApplicationContextInitializer l1 = context -> { }; + ApplicationContextInitializer l2 = context -> { }; + AotApplicationContextInitializer a1 = context -> { }; + AotApplicationContextInitializer a2 = l2::initialize; + List> initializers = List.of(l1, l2, a1, a2); + List> aotInitializers = AotApplicationContextInitializer + .getAotInitializers(initializers); + assertThat(aotInitializers).containsExactly(a1, a2); + } + + + static class TestApplicationContextInitializer implements ApplicationContextInitializer { + + @Override + public void initialize(GenericApplicationContext applicationContext) { + applicationContext.registerBeanDefinition("test", new RootBeanDefinition()); + } + + } + + static class AnotherApplicationContextInitializer implements ApplicationContextInitializer { + + @Override + public void initialize(GenericApplicationContext applicationContext) { + applicationContext.registerBeanDefinition("another", new RootBeanDefinition()); + } + + } + + static class ConfigurableApplicationContextInitializer implements ApplicationContextInitializer { + + public ConfigurableApplicationContextInitializer(ClassLoader classLoader) { + } + + @Override + public void initialize(GenericApplicationContext applicationContext) { + + } + } + +} diff --git a/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotInitializerTests.java b/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotInitializerTests.java index a3ba5634b6..af0e2de2f8 100644 --- a/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotInitializerTests.java +++ b/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotInitializerTests.java @@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException * * @author Stephane Nicoll */ +@Deprecated class ApplicationContextAotInitializerTests { private final ApplicationContextAotInitializer initializer = new ApplicationContextAotInitializer(); @@ -54,7 +55,8 @@ class ApplicationContextAotInitializerTests { GenericApplicationContext context = new GenericApplicationContext(); assertThatIllegalArgumentException() .isThrownBy(() -> initializer.initialize(context, "java.lang.String")) - .withMessageContaining("Not an ApplicationContextInitializer: ") + .withMessageContaining("not assignable") + .withMessageContaining("ApplicationContextInitializer") .withMessageContaining("java.lang.String"); }