Skip to content

Commit da8c4de

Browse files
committed
Add AotDetector to reliably opt-in for optimizations
This commit adds a central utility to figure out if the application must run with Ahead-Of-Time optimizations. This is mandatory for running in a native image but can be selected on the JVM using the "spring.aot.enabled" property. This commit also introduces a utility that can be used to initialize a context with generated artifacts. This represents the runtime counterpart of ApplicationContextAotGenerator. Closes gh-28474
1 parent 8af1496 commit da8c4de

File tree

4 files changed

+229
-1
lines changed

4 files changed

+229
-1
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.aot;
18+
19+
import org.apache.commons.logging.Log;
20+
import org.apache.commons.logging.LogFactory;
21+
22+
import org.springframework.beans.BeanUtils;
23+
import org.springframework.context.ApplicationContextInitializer;
24+
import org.springframework.context.ConfigurableApplicationContext;
25+
import org.springframework.lang.Nullable;
26+
import org.springframework.util.ClassUtils;
27+
28+
/**
29+
* Initializes a {@link ConfigurableApplicationContext} using AOT optimizations.
30+
*
31+
* @author Stephane Nicoll
32+
* @since 6.0
33+
*/
34+
public class ApplicationContextAotInitializer {
35+
36+
private static final Log logger = LogFactory.getLog(ApplicationContextAotInitializer.class);
37+
38+
/**
39+
* Initialize the specified application context using the specified
40+
* {@link ApplicationContextInitializer} class names. Each class name is
41+
* expected to have a default constructor.
42+
* @param context the context to initialize
43+
* @param initializerClassNames the application context initializer class names
44+
*/
45+
public void initialize(ConfigurableApplicationContext context, String... initializerClassNames) {
46+
if (logger.isDebugEnabled()) {
47+
logger.debug("Initializing ApplicationContext with AOT");
48+
}
49+
for (String initializerClassName : initializerClassNames) {
50+
if (logger.isTraceEnabled()) {
51+
logger.trace("Applying " + initializerClassName);
52+
}
53+
loadInitializer(initializerClassName, context.getClassLoader()).initialize(context);
54+
}
55+
}
56+
57+
@SuppressWarnings("unchecked")
58+
private ApplicationContextInitializer<ConfigurableApplicationContext> loadInitializer(
59+
String className, @Nullable ClassLoader classLoader) {
60+
Object initializer = instantiate(className, classLoader);
61+
if (!(initializer instanceof ApplicationContextInitializer)) {
62+
throw new IllegalArgumentException("Not an ApplicationContextInitializer: " + className);
63+
}
64+
return (ApplicationContextInitializer<ConfigurableApplicationContext>) initializer;
65+
}
66+
67+
private static Object instantiate(String className, @Nullable ClassLoader classLoader) {
68+
try {
69+
Class<?> type = ClassUtils.forName(className, classLoader);
70+
return BeanUtils.instantiateClass(type);
71+
}
72+
catch (Exception ex) {
73+
throw new IllegalArgumentException("Failed to instantiate ApplicationContextInitializer: " + className, ex);
74+
}
75+
}
76+
77+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.aot;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.beans.factory.support.RootBeanDefinition;
22+
import org.springframework.context.ApplicationContextInitializer;
23+
import org.springframework.context.support.GenericApplicationContext;
24+
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
27+
28+
/**
29+
* Tests for {@link ApplicationContextAotInitializer}.
30+
*
31+
* @author Stephane Nicoll
32+
*/
33+
class ApplicationContextAotInitializerTests {
34+
35+
private final ApplicationContextAotInitializer initializer = new ApplicationContextAotInitializer();
36+
37+
@Test
38+
void initializeInvokeApplicationContextInitializer() {
39+
GenericApplicationContext context = new GenericApplicationContext();
40+
initializer.initialize(context, TestApplicationContextInitializer.class.getName());
41+
assertThat(context.getBeanDefinitionNames()).containsExactly("test");
42+
}
43+
44+
@Test
45+
void initializeInvokeApplicationContextInitializersInOrder() {
46+
GenericApplicationContext context = new GenericApplicationContext();
47+
initializer.initialize(context, AnotherApplicationContextInitializer.class.getName(),
48+
TestApplicationContextInitializer.class.getName());
49+
assertThat(context.getBeanDefinitionNames()).containsExactly("another", "test");
50+
}
51+
52+
@Test
53+
void initializeFailWithNonApplicationContextInitializer() {
54+
GenericApplicationContext context = new GenericApplicationContext();
55+
assertThatIllegalArgumentException()
56+
.isThrownBy(() -> initializer.initialize(context, "java.lang.String"))
57+
.withMessageContaining("Not an ApplicationContextInitializer: ")
58+
.withMessageContaining("java.lang.String");
59+
}
60+
61+
@Test
62+
void initializeFailWithApplicationContextInitializerAndNonDefaultConstructor() {
63+
GenericApplicationContext context = new GenericApplicationContext();
64+
assertThatIllegalArgumentException()
65+
.isThrownBy(() -> initializer.initialize(context,
66+
ConfigurableApplicationContextInitializer.class.getName()))
67+
.withMessageContaining("Failed to instantiate ApplicationContextInitializer: ")
68+
.withMessageContaining(ConfigurableApplicationContextInitializer.class.getName());
69+
}
70+
71+
72+
static class TestApplicationContextInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
73+
74+
@Override
75+
public void initialize(GenericApplicationContext applicationContext) {
76+
applicationContext.registerBeanDefinition("test", new RootBeanDefinition());
77+
}
78+
79+
}
80+
81+
static class AnotherApplicationContextInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
82+
83+
@Override
84+
public void initialize(GenericApplicationContext applicationContext) {
85+
applicationContext.registerBeanDefinition("another", new RootBeanDefinition());
86+
}
87+
88+
}
89+
90+
static class ConfigurableApplicationContextInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
91+
92+
public ConfigurableApplicationContextInitializer(ClassLoader classLoader) {
93+
}
94+
95+
@Override
96+
public void initialize(GenericApplicationContext applicationContext) {
97+
98+
}
99+
}
100+
101+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aot;
18+
19+
import org.springframework.core.NativeDetector;
20+
import org.springframework.core.SpringProperties;
21+
22+
/**
23+
* Determine if AOT-processed optimizations must be used rather than the
24+
* regular runtime. Strictly for internal use within the framework.
25+
*
26+
* @author Stephane Nicoll
27+
* @since 6.0
28+
*/
29+
public abstract class AotDetector {
30+
31+
/**
32+
* System property that indicates the application should run with AOT
33+
* generated artifacts. If such optimizations are not available, it is
34+
* recommended to throw an exception rather than falling back to the
35+
* regular runtime behavior.
36+
*/
37+
public static final String AOT_ENABLED = "spring.aot.enabled";
38+
39+
/**
40+
* Return whether AOT optimizations must be considered at runtime. This
41+
* is mandatory in a native image but can be triggered on the JVM using
42+
* the {@value AOT_ENABLED} spring property.
43+
* @return whether AOT optimizations must be considered
44+
*/
45+
public static boolean useGeneratedArtifacts() {
46+
return (NativeDetector.inNativeImage() || SpringProperties.getFlag(AOT_ENABLED));
47+
}
48+
49+
}
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
Args = --initialize-at-build-time=org.springframework.core.NativeDetector \
1+
Args = --initialize-at-build-time=org.springframework.aot.AotDetector \
2+
--initialize-at-build-time=org.springframework.core.NativeDetector \
23
--initialize-at-build-time=org.springframework.util.unit.DataSize

0 commit comments

Comments
 (0)