parent
9249dc3d37
commit
44c583392e
|
@ -10,6 +10,14 @@ configurations {
|
||||||
asciidoctorExtensions
|
asciidoctorExtensions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(":spring-context"))
|
||||||
|
api(project(":spring-web"))
|
||||||
|
|
||||||
|
implementation(project(":spring-core-test"))
|
||||||
|
implementation("org.assertj:assertj-core")
|
||||||
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
:chomp: default headers packages
|
||||||
:docs-site: https://docs.spring.io
|
:docs-site: https://docs.spring.io
|
||||||
// Spring Framework
|
// Spring Framework
|
||||||
:docs-spring-framework: {docs-site}/spring-framework/docs/{spring-version}
|
:docs-spring-framework: {docs-site}/spring-framework/docs/{spring-version}
|
||||||
|
@ -13,3 +14,4 @@
|
||||||
:gh-rsocket: https://github.com/rsocket
|
:gh-rsocket: https://github.com/rsocket
|
||||||
:gh-rsocket-extensions: {gh-rsocket}/rsocket/blob/master/Extensions
|
:gh-rsocket-extensions: {gh-rsocket}/rsocket/blob/master/Extensions
|
||||||
:gh-rsocket-java: {gh-rsocket}/rsocket-java
|
:gh-rsocket-java: {gh-rsocket}/rsocket-java
|
||||||
|
:doc-graalvm: https://www.graalvm.org/22.3/reference-manual
|
||||||
|
|
|
@ -52,27 +52,13 @@ An application context is created with any number of entry points, usually in th
|
||||||
|
|
||||||
Let's look at a basic example:
|
Let's look at a basic example:
|
||||||
|
|
||||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
include::code:AotProcessingSample[tag=myapplication]
|
||||||
.Java
|
|
||||||
----
|
|
||||||
@Configuration(proxyBeanMethods=false)
|
|
||||||
@ComponentScan
|
|
||||||
@Import({DataSourceConfiguration.class, ContainerConfiguration.class})
|
|
||||||
public class MyApplication {
|
|
||||||
}
|
|
||||||
----
|
|
||||||
|
|
||||||
Starting this application with the regular runtime involves a number of steps including classpath scanning, configuration class parsing, bean instantiation, and lifecycle callback handling.
|
Starting this application with the regular runtime involves a number of steps including classpath scanning, configuration class parsing, bean instantiation, and lifecycle callback handling.
|
||||||
Refresh for AOT processing only applies a subset of what happens with a <<beans-introduction,regular `refresh`>>.
|
Refresh for AOT processing only applies a subset of what happens with a <<beans-introduction,regular `refresh`>>.
|
||||||
AOT processing can be triggered as follows:
|
AOT processing can be triggered as follows:
|
||||||
|
|
||||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
include::code:AotProcessingSample[tag=aotcontext]
|
||||||
.Java
|
|
||||||
----
|
|
||||||
GenericApplicationContext applicationContext = new AnnotatedConfigApplicationContext();
|
|
||||||
context.register(MyApplication.class);
|
|
||||||
context.refreshForAotProcessing();
|
|
||||||
----
|
|
||||||
|
|
||||||
In this mode, <<beans-factory-extension-factory-postprocessors,`BeanFactoryPostProcessor` implementations>> are invoked as usual.
|
In this mode, <<beans-factory-extension-factory-postprocessors,`BeanFactoryPostProcessor` implementations>> are invoked as usual.
|
||||||
This includes configuration class parsing, import selectors, classpath scanning, etc.
|
This includes configuration class parsing, import selectors, classpath scanning, etc.
|
||||||
|
@ -229,24 +215,7 @@ A number of convenient annotations are also provided for common use cases.
|
||||||
Implementations of this interface can be registered using `@ImportRuntimeHints` on any Spring bean or `@Bean` factory method.
|
Implementations of this interface can be registered using `@ImportRuntimeHints` on any Spring bean or `@Bean` factory method.
|
||||||
`RuntimeHintsRegistrar` implementations are detected and invoked at build time.
|
`RuntimeHintsRegistrar` implementations are detected and invoked at build time.
|
||||||
|
|
||||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
include::code:SpellCheckService[]
|
||||||
.Java
|
|
||||||
----
|
|
||||||
@Component
|
|
||||||
@ImportRuntimeHints(MyComponentRuntimeHints.class)
|
|
||||||
public class MyComponent {
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
private static class MyComponentRuntimeHints implements RuntimeHintsRegistrar {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----
|
|
||||||
|
|
||||||
If at all possible, `@ImportRuntimeHints` should be used as close as possible to the component that requires the hints.
|
If at all possible, `@ImportRuntimeHints` should be used as close as possible to the component that requires the hints.
|
||||||
This way, if the component is not contributed to the `BeanFactory`, the hints won't be contributed either.
|
This way, if the component is not contributed to the `BeanFactory`, the hints won't be contributed either.
|
||||||
|
@ -290,3 +259,50 @@ The following example registers `Account` for serialization.
|
||||||
|
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[[core.aot.hints.testing]]
|
||||||
|
=== Testing Runtime Hints
|
||||||
|
|
||||||
|
Spring Core also ships `RuntimeHintsPredicates`, an utility for checking that existing hints match a particular use case.
|
||||||
|
This can be used in your own tests, for validating that a `RuntimeHintsRegistrar` has the expected result.
|
||||||
|
We can write a test for our `SpellCheckService` and ensure that we can load a dictionary at runtime:
|
||||||
|
|
||||||
|
include::code:SpellCheckServiceTests[tag=hintspredicates]
|
||||||
|
|
||||||
|
With `RuntimeHintsPredicates`, we can check for reflection, resource, serialization or proxy generation hints.
|
||||||
|
This approach works well for unit tests but implies that the runtime behavior of a component is well known.
|
||||||
|
|
||||||
|
You can learn more about the global runtime behavior of an application by running its test suite (or the app itself) with the {doc-graalvm}/native-image/metadata/AutomaticMetadataCollection/[GraalVM tracing agent].
|
||||||
|
This agent will record all relevant calls requiring GraalVM hints at runtime and write them out as JSON configuration files.
|
||||||
|
|
||||||
|
For more targeted discovery and testing, Spring Framework ships a dedicated module with core AOT testing utilities, `"org.springframework:spring-core-test"`.
|
||||||
|
This module contains the RuntimeHints Agent, a Java agent that all method invocations that are related to runtime hints and helps you to assert that a given `RuntimeHints` instance covers all recorded invocations.
|
||||||
|
Let's consider a piece of infrastructure for which we'd like to test the hints we're contributing during the AOT phase.
|
||||||
|
|
||||||
|
|
||||||
|
include::code:SampleReflection[]
|
||||||
|
|
||||||
|
We can then write a Java test (no native compilation required!) that checks our contributed hints:
|
||||||
|
|
||||||
|
include::code:SampleReflectionRuntimeHintsTests[]
|
||||||
|
|
||||||
|
If you forgot to contribute a hint, the test will fail and give some details on the invocation:
|
||||||
|
|
||||||
|
[source,txt,indent=0,subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
org.springframework.docs.core.aot.hints.testing.SampleReflection performReflection
|
||||||
|
INFO: Spring version:6.0.0-SNAPSHOT
|
||||||
|
|
||||||
|
Missing <"ReflectionHints"> for invocation <java.lang.Class#forName>
|
||||||
|
with arguments ["org.springframework.core.SpringVersion",
|
||||||
|
false,
|
||||||
|
jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7].
|
||||||
|
Stacktrace:
|
||||||
|
<"org.springframework.util.ClassUtils#forName, Line 284
|
||||||
|
io.spring.runtimehintstesting.SampleReflection#performReflection, Line 19
|
||||||
|
io.spring.runtimehintstesting.SampleReflectionRuntimeHintsTests#lambda$shouldRegisterReflectionHints$0, Line 25
|
||||||
|
----
|
||||||
|
|
||||||
|
There are various ways to configure this Java agent into your build, please refer to your build tool and test execution plugin documentation.
|
||||||
|
The agent itself can be configured to instrument some packages (by default, only `org.springframework` is instrumented).
|
||||||
|
You'll find more details in the https://github.com/spring-projects/spring-framework/blob/main/buildSrc/README.md[Spring Framework buildSrc README].
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* 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.docs.core.aot.hints.importruntimehints;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
|
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||||
|
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@ImportRuntimeHints(SpellCheckService.SpellCheckServiceRuntimeHints.class)
|
||||||
|
public class SpellCheckService {
|
||||||
|
|
||||||
|
public void loadDictionary(Locale locale) {
|
||||||
|
ClassPathResource resource = new ClassPathResource("dicts/" + locale.getLanguage() + ".txt");
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SpellCheckServiceRuntimeHints implements RuntimeHintsRegistrar {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||||
|
hints.resources().registerPattern("dicts/*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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.docs.core.aot.hints.testing;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
public class SampleReflection {
|
||||||
|
|
||||||
|
private final Log logger = LogFactory.getLog(SampleReflection.class);
|
||||||
|
|
||||||
|
public void performReflection() {
|
||||||
|
try {
|
||||||
|
Class<?> springVersion = ClassUtils.forName("org.springframework.core.SpringVersion", null);
|
||||||
|
Method getVersion = ClassUtils.getMethod(springVersion, "getVersion");
|
||||||
|
String version = (String) getVersion.invoke(null);
|
||||||
|
logger.info("Spring version:" + version);
|
||||||
|
}
|
||||||
|
catch (Exception exc) {
|
||||||
|
logger.error("reflection failed", exc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* 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.docs.core.aot.hints.testing;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.aot.hint.ExecutableMode;
|
||||||
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
|
import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent;
|
||||||
|
import org.springframework.aot.test.agent.RuntimeHintsInvocations;
|
||||||
|
import org.springframework.aot.test.agent.RuntimeHintsRecorder;
|
||||||
|
import org.springframework.core.SpringVersion;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
// This annotation conditions the execution of tests only if the agent is loaded in the current JVM
|
||||||
|
// it also tags tests with the "RuntimeHints" JUnit tag
|
||||||
|
@EnabledIfRuntimeHintsAgent
|
||||||
|
class SampleReflectionRuntimeHintsTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRegisterReflectionHints() {
|
||||||
|
RuntimeHints runtimeHints = new RuntimeHints();
|
||||||
|
// Call a RuntimeHintsRegistrar that contributes hints like:
|
||||||
|
runtimeHints.reflection().registerType(SpringVersion.class, typeHint -> {
|
||||||
|
typeHint.withMethod("getVersion", List.of(), ExecutableMode.INVOKE);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Invoke the relevant piece of code we want to test within a recording lambda
|
||||||
|
RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
|
||||||
|
SampleReflection sample = new SampleReflection();
|
||||||
|
sample.performReflection();
|
||||||
|
});
|
||||||
|
// assert that the recorded invocations are covered by the contributed hints
|
||||||
|
assertThat(invocations).match(runtimeHints);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.docs.core.aot.hints.testing;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
|
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||||
|
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class SpellCheckServiceTests {
|
||||||
|
|
||||||
|
// tag::hintspredicates[]
|
||||||
|
@Test
|
||||||
|
void shouldRegisterResourceHints() {
|
||||||
|
RuntimeHints hints = new RuntimeHints();
|
||||||
|
new SpellCheckServiceRuntimeHints().registerHints(hints, getClass().getClassLoader());
|
||||||
|
assertThat(RuntimeHintsPredicates.resource().forResource("dicts/en.txt"))
|
||||||
|
.accepts(hints);
|
||||||
|
}
|
||||||
|
// end::hintspredicates[]
|
||||||
|
|
||||||
|
// Copied here because it is package private in SpellCheckService
|
||||||
|
static class SpellCheckServiceRuntimeHints implements RuntimeHintsRegistrar {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||||
|
hints.resources().registerPattern("dicts/*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* 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.docs.core.aot.refresh;
|
||||||
|
|
||||||
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
public class AotProcessingSample {
|
||||||
|
|
||||||
|
public void createAotContext() {
|
||||||
|
// tag::aotcontext[]
|
||||||
|
RuntimeHints hints = new RuntimeHints();
|
||||||
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||||
|
context.register(MyApplication.class);
|
||||||
|
context.refreshForAotProcessing(hints);
|
||||||
|
// end::aotcontext[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// tag::myapplication[]
|
||||||
|
@Configuration(proxyBeanMethods=false)
|
||||||
|
@ComponentScan
|
||||||
|
@Import({DataSourceConfiguration.class, ContainerConfiguration.class})
|
||||||
|
public class MyApplication {
|
||||||
|
|
||||||
|
}
|
||||||
|
// end::myapplication[]
|
||||||
|
|
||||||
|
class DataSourceConfiguration {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerConfiguration {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue