spring-framework
spring-framework copied to clipboard
Revisit SpringFactoriesLoader vs. ServiceLoader infrastructure
SpringFactoriesLoader
has been added in Spring Framework 3.2 (see 988f376) as a simple and internal factory loading mechanism. At the time, the 3.x generation had a Java 1.5 baseline and could not require java.util.ServiceLoader
, introduced in Java 1.6.
Over the years, this mechanism has been used more and more by many Spring projects for loading factories. Its contract has also been extended in #15158 for loading factory names and possibly instantiating them "manually" using reflection. Third party projects are also expected to use the same infrastructure if they want to provide their own support (for example, auto-configurations).
With this issue, the Spring Framework team should discuss whether we should reconsider the current situation, given that:
- Spring Framework 6 will have a Java 17 baseline
- The Java Platform Module System (JPMS) adds new constraints in this field
- The AOT engine (currently in Spring Native, to be ported to Spring Framework) requires specific support for that
Java 17 baseline
ServiceLoader
is now mature and has been evolved over the years. The ServiceLoader::stream()
method (added in Java 9) allows to iterate over the types without instantiating them. With that, we could probably replace SpringFactoriesLoader
.
The Java Platform Module System (JPMS)
Again with Java 9, new mechanisms were added to ServiceLoader
to help with the newly introduced module system.
With the JPMS, modules must declare what they export and what they require; without that, they cannot access classes (reflectively or not) if they're not explicitly required by the module descriptor.
With ServiceLoader
, any module can declare service implementations. By definition, service clients cannot be aware of all of them and are not in a position to declare them in their module descriptor. ServiceLoader
has been improved with new methods and its own provides ... with ...
and uses ...
keywords in the module descriptor.
SpringFactoriesLoader
will hit the JPMS limitations and doesn't currently have any mechanism to address it.
The Spring AOT engine
Spring Native has implemented an AOT engine that pre-processes application configuration and generates Java source files. It greatly helps the GraalVM native compilation phase by making the application startup more explicit and leaner.
Because dynamic class loading and reflection are well-known limitations with native compilation, Spring Native added a specific processing of Spring Factories at build time. This process is listing all factories and their implementations available at build time, and writes a Java source file that explicitly references them so that the GraalVM native static analysis phase discovers them.
Depending on the choice made here, the AOT engine will need to have a SpringFactoriesLoader
-specific mechanism or possibly extend the existing ServiceLoader
support in GraalVM native.
@jhoeller, @sdeleuze and I have discussed this and came up with the following plan:
- The
SpringFactoriesLoader#loadFactoryNames
contract is just too broad and permissive for AOT processing and enables use cases which are outside of the original scope of this class. We're going to deprecate this method in 6.0.x and remove it completely in the 6.x timeline. See #27954 -
SpringFactoriesLoader
should be enhanced to allow for static registrations of factories, skipping the loading ofspring.factories
file and reflection. This should statically populate a "cache" that holds references to factory types and suppliers that instantiate factories implementations. See #27955
As part of this issue, we can revisit SpringFactoriesLoader
to use ServiceLoader
mechanism as one of the supported mechanisms to load factories (static loading for AOT, ServiceLoader
and regular spring.factories
). This should allow developers to use SpringFactoriesLoader
in a JPMS scenario. Note that this issue does not consider moving the existing spring.factories
declarations to ServiceLoader metadata.
Closing this for the time being since we have no immediate need for closer alignment with the ServiceLoader mechanism.
From a core framework perspective, SpringFactoriesLoader
is a rather specific mechanism for certain rarely used SPIs (such as BeanInfoFactory
) that are meant to be local to the current deployment arrangement.
Broader appeal of SpringFactoriesLoader
, potentially with cross-module semantics, depends on usage scenarios outside of the core framework - none of which are currently known to benefit from ServiceLoader alignment.