diff --git a/framework-docs/modules/ROOT/pages/core/aot.adoc b/framework-docs/modules/ROOT/pages/core/aot.adoc index 69ac6a5a03f..d949caf9ac4 100644 --- a/framework-docs/modules/ROOT/pages/core/aot.adoc +++ b/framework-docs/modules/ROOT/pages/core/aot.adoc @@ -271,7 +271,7 @@ build time. While your application may interact with an interface that a bean implements, it is still very important to declare the most precise type. The AOT engine performs additional checks on the bean type, such as detecting the presence of `@Autowired` members or lifecycle callback methods. -For `@Configuration` classes, make sure that the return type of a `@Bean` factory method is as precise as possible. +For `@Configuration` classes, make sure that the return type of an `@Bean` factory method is as precise as possible. Consider the following example: [tabs] @@ -602,9 +602,12 @@ A number of convenient annotations are also provided for common use cases. [[aot.hints.import-runtime-hints]] === `@ImportRuntimeHints` -`RuntimeHintsRegistrar` implementations allow you to get a callback to the `RuntimeHints` instance managed by the AOT engine. -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. +{spring-framework-api}/aot/hint/RuntimeHintsRegistrar.html[`RuntimeHintsRegistrar`] +implementations allow you to get a callback to the `RuntimeHints` instance managed by the +AOT engine. Implementations of this interface can be registered using +{spring-framework-api}/context/annotation/ImportRuntimeHints.html[`@ImportRuntimeHints`] +on any Spring bean or `@Bean` factory method. `RuntimeHintsRegistrar` implementations are +detected and invoked at build time. include-code::./SpellCheckService[] @@ -620,8 +623,10 @@ It is also possible to register an implementation statically by adding an entry {spring-framework-api}/aot/hint/annotation/Reflective.html[`@Reflective`] provides an idiomatic way to flag the need for reflection on an annotated element. For instance, `@EventListener` is meta-annotated with `@Reflective` since the underlying implementation invokes the annotated method using reflection. -Out-of-the-box, only Spring beans are considered, but you can opt-in for scanning using `@ReflectiveScan`. -In the example below, all types in the `com.example.app` package and its subpackages are considered: +Out-of-the-box, only Spring beans are considered, but you can opt-in for scanning using +{spring-framework-api}/context/annotation/ReflectiveScan.html[`@ReflectiveScan`]. In the +example below, all types in the `com.example.app` package and its subpackages are +considered: include-code::./MyConfiguration[] @@ -638,9 +643,9 @@ An example of such customization is covered in the next section. [[aot.hints.register-reflection]] === `@RegisterReflection` -{spring-framework-api}/aot/hint/annotation/RegisterReflection.html[`@RegisterReflection`] is a specialization of `@Reflective` that provides a declarative way of registering reflection for arbitrary types. +{spring-framework-api}/aot/hint/annotation/RegisterReflection.html[`@RegisterReflection`] is a specialization of `@Reflective` that provides a declarative way to register reflection for arbitrary types. -NOTE: As a specialization of `@Reflective`, this is also detected if you are using `@ReflectiveScan`. +NOTE: As a specialization of `@Reflective`, `@RegisterReflection` is also detected if you are using `@ReflectiveScan`. In the following example, public constructors and public methods can be invoked via reflection on `AccountService`: @@ -648,8 +653,8 @@ include-code::./MyConfiguration[tag=snippet,indent=0] `@RegisterReflection` can be applied to any target type at the class level, but it can also be applied directly to a method to better indicate where the hints are actually required. -`@RegisterReflection` can be used as a meta-annotation to provide more specific needs. -{spring-framework-api}/aot/hint/annotation/RegisterReflectionForBinding.html[`@RegisterReflectionForBinding`] is such composed annotation and registers the need for serializing arbitrary types. +`@RegisterReflection` can be used as a meta-annotation to support more specific needs. +{spring-framework-api}/aot/hint/annotation/RegisterReflectionForBinding.html[`@RegisterReflectionForBinding`] is a composed annotation that is meta-annotated with `@RegisterReflection` and registers the need for serializing arbitrary types. A typical use case is the use of DTOs that the container cannot infer, such as using a web client within a method body. The following example registers `Order` for serialization. @@ -660,11 +665,66 @@ This registers hints for constructors, fields, properties, and record components Hints are also registered for types transitively used on properties and record components. In other words, if `Order` exposes others types, hints are registered for those as well. +[[aot.hints.convention-based-conversion]] +=== Runtime Hints for Convention-based Conversion + +Although the core container provides built-in support for automatic conversion of many +common types (see xref:core/validation/convert.adoc[Spring Type Conversion]), some +conversions are supported via a convention-based algorithm that relies on reflection. + +Specifically, if there is no explicit `Converter` registered with the `ConversionService` +for a particular source → target type pair, the internal `ObjectToObjectConverter` +will attempt to use conventions to convert a source object to a target type by delegating +to a method on the source object or to a static factory method or constructor on the +target type. Since this convention-based algorithm can be applied to arbitrary types at +runtime, the core container is not able to infer the runtime hints necessary to support +such reflection. + +If you encounter convention-based conversion issues within a native image resulting from +lacking runtime hints, you can register the necessary hints programmatically. For +example, if your application requires a conversion from `java.time.Instant` to +`java.sql.Timestamp` and relies on `ObjectToObjectConverter` to invoke +`java.sql.Timestamp.from(Instant)` using reflection, you could implement a custom +`RuntimeHintsRegitrar` to support this use case within a native image, as demonstrated in +the following example. + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes"] +---- +public class TimestampConversionRuntimeHints implements RuntimeHintsRegistrar { + + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ReflectionHints reflectionHints = hints.reflection(); + + reflectionHints.registerTypeIfPresent(classLoader, "java.sql.Timestamp", hint -> hint + .withMethod("from", List.of(TypeReference.of(Instant.class)), ExecutableMode.INVOKE) + .onReachableType(TypeReference.of("java.sql.Timestamp"))); + } +} +---- +====== + +`TimestampConversionRuntimeHints` can then be registered declaratively via +<> or statically via a `META-INF/spring/aot.factories` +configuration file. + +[NOTE] +==== +The above `TimestampConversionRuntimeHints` class is a simplified version of the +`ObjectToObjectConverterRuntimeHints` class that is included in the framework and +registered by default. + +Thus, this specific `Instant`-to-`Timestamp` use case is already handled by the framework. +==== + [[aot.hints.testing]] === Testing Runtime Hints Spring Core also ships `RuntimeHintsPredicates`, a utility for checking that existing hints match a particular use case. -This can be used in your own tests to validate that a `RuntimeHintsRegistrar` contains the expected results. +This can be used in your own tests to validate that a `RuntimeHintsRegistrar` produces the expected results. We can write a test for our `SpellCheckService` and ensure that we will be able to load a dictionary at runtime: include-code::./SpellCheckServiceTests[tag=hintspredicates] diff --git a/framework-docs/modules/ROOT/pages/core/validation/convert.adoc b/framework-docs/modules/ROOT/pages/core/validation/convert.adoc index adf928eef7a..6b710babc96 100644 --- a/framework-docs/modules/ROOT/pages/core/validation/convert.adoc +++ b/framework-docs/modules/ROOT/pages/core/validation/convert.adoc @@ -72,9 +72,9 @@ When you need to centralize the conversion logic for an entire class hierarchy } ---- -Parameterize S to be the type you are converting from and R to be the base type defining +Parameterize `S` to be the type you are converting from and `R` to be the base type defining the __range__ of classes you can convert to. Then implement `getConverter(Class)`, -where T is a subclass of R. +where `T` is a subclass of `R`. Consider the `StringToEnumConverterFactory` as an example: @@ -107,13 +107,15 @@ Consider the `StringToEnumConverterFactory` as an example: [[core-convert-GenericConverter-SPI]] == Using `GenericConverter` -When you require a sophisticated `Converter` implementation, consider using the -`GenericConverter` interface. With a more flexible but less strongly typed signature -than `Converter`, a `GenericConverter` supports converting between multiple source and -target types. In addition, a `GenericConverter` makes available source and target field -context that you can use when you implement your conversion logic. Such context lets a -type conversion be driven by a field annotation or by generic information declared on a -field signature. The following listing shows the interface definition of `GenericConverter`: +When you require a more sophisticated `Converter` implementation, consider using the +`GenericConverter` interface. With a more flexible but less strongly typed signature than +`Converter`, a `GenericConverter` supports converting between multiple source and target +types. In addition, a `GenericConverter` is provided source and target type descriptors +that you can use when you implement your conversion logic. Such type descriptors enable +type conversion to be driven by an annotation on the source of the descriptor (such as a +field or method) or by generic information declared in a field signature, method +signature, etc. The following listing shows the definition of the `GenericConverter` +interface: [source,java,indent=0,subs="verbatim,quotes",chomp="-packages"] ---- @@ -128,16 +130,17 @@ field signature. The following listing shows the interface definition of `Generi ---- To implement a `GenericConverter`, have `getConvertibleTypes()` return the supported -source->target type pairs. Then implement `convert(Object, TypeDescriptor, +source → target type pairs. Then implement `convert(Object, TypeDescriptor, TypeDescriptor)` to contain your conversion logic. The source `TypeDescriptor` provides -access to the source field that holds the value being converted. The target `TypeDescriptor` -provides access to the target field where the converted value is to be set. +access to the source field or method that holds the value being converted. The target +`TypeDescriptor` provides access to the target field or method where the converted value +is to be set. A good example of a `GenericConverter` is a converter that converts between a Java array -and a collection. Such an `ArrayToCollectionConverter` introspects the field that declares -the target collection type to resolve the collection's element type. This lets each -element in the source array be converted to the collection element type before the -collection is set on the target field. +and a collection. Such an `ArrayToCollectionConverter` introspects the field or method +that declares the target collection type to resolve the collection's element type. This +lets each element in the source array be converted to the collection element type before +the collection is set on the target field or supplied to the target method or constructor. NOTE: Because `GenericConverter` is a more complex SPI interface, you should use it only when you need it. Favor `Converter` or `ConverterFactory` for basic type @@ -148,9 +151,9 @@ conversion needs. === Using `ConditionalGenericConverter` Sometimes, you want a `Converter` to run only if a specific condition holds true. For -example, you might want to run a `Converter` only if a specific annotation is present -on the target field, or you might want to run a `Converter` only if a specific method -(such as a `static valueOf` method) is defined on the target class. +example, you might want to run a `Converter` only if a specific annotation is present on +the target field or method, or you might want to run a `Converter` only if a specific +method (such as a `static valueOf` method) is defined on the target type. `ConditionalGenericConverter` is the union of the `GenericConverter` and `ConditionalConverter` interfaces that lets you define such custom matching criteria: @@ -212,7 +215,7 @@ creating common `ConversionService` configurations. A `ConversionService` is a stateless object designed to be instantiated at application startup and then shared between multiple threads. In a Spring application, you typically configure a `ConversionService` instance for each Spring container (or `ApplicationContext`). -Spring picks up that `ConversionService` and uses it whenever a type +Spring picks up that `ConversionService` and uses it whenever type conversion needs to be performed by the framework. You can also inject this `ConversionService` into any of your beans and invoke it directly. @@ -249,7 +252,8 @@ It is also common to use a `ConversionService` within a Spring MVC application. xref:web/webmvc/mvc-config/conversion.adoc[Conversion and Formatting] in the Spring MVC chapter. In certain situations, you may wish to apply formatting during conversion. See -xref:core/validation/format.adoc#format-FormatterRegistry-SPI[The `FormatterRegistry` SPI] for details on using `FormattingConversionServiceFactoryBean`. +xref:core/validation/format.adoc#format-FormatterRegistry-SPI[The `FormatterRegistry` SPI] +for details on using `FormattingConversionServiceFactoryBean`. @@ -338,7 +342,3 @@ method on the `DefaultConversionService` class. Converters for value types are reused for arrays and collections, so there is no need to create a specific converter to convert from a `Collection` of `S` to a `Collection` of `T`, assuming that standard collection handling is appropriate. - - - -