Inversion of Control is not only possible at the class level, but at the module level. OSGi has been doing it for a long time. However, there are IoC approaches directly available in Java, as well as in Spring.
Java Service Loader
Out-of-the-box, the Java API offers a specific form of Inversion of Control. It’s implemented by the Service Loader class. It is designed to locate implementation classes of an interface on the classpath. This way allows to discover which available implementations of an interface are available on the classpath at runtime, and thus paves the way for modules designed around a clean separation between an API module — i.e. JAR, and multiple implementation modules.
This is the path chosen by the logging framework, SLF4J. SLF4J itself is just the API, while different implementations are available (_e.g. Logback, Log4J, etc.). SLF4J clients only interact with the SLF4J API, while the implementation available on the classpath takes care of the nitty-gritty details at runtime.
It’s implemented as a file located in the META-INF/services
folder of a JAR. The name of the file is the fully qualified name of the interface, while its content is a list of qualified names of available implementations. For example, for an interface ch.frankel.blog.serviceloader.Foo
, there must be a file named META-INF/services/ch.frankel.blog.serviceloader.Foo
which content might look like this:
ch.frankel.blog.serviceloader.FooImpl1
ch.frankel.blog.serviceloader.FooImpl2
Note that the classes listed above must implement the ch.frankel.blog.serviceloader.Foo
interface.
From a code perspective, it’s very straightforward:
ServiceLoader<Foo> loader = ServiceLoader.load(Foo.class);
loader.iterator();
Service Loader Spring integration
Core Spring offers an integration with the above Service Loader via the factory beans. For example, the following code assumes there will be a least of candidate implementations:
@Configuration
public class ServiceConfiguration {
@Bean
public ServiceListFactoryBean serviceListFactoryBean() {
ServiceListFactoryBean serviceListFactoryBean = new ServiceListFactoryBean();
serviceListFactoryBean.setServiceType(Foo.class);
return serviceListFactoryBean;
}
}
Object object = serviceListFactoryBean.getObject();
Obviously, this requires further operations to get the data in the right form (hint: it’s a linked list).
Spring Factories Loader
In parallel to the Java Service Loader, Spring offers another Inversion of Control implementation. There’s only a single property file involved, it must be named spring.factories
and located under META-INF
. From a code perspective, the file is read through the SpringFactoriesLoader.loadFactories()
static method - yes, for Spring, it’s quite a shock.
Client code couldn’t get any simpler:
List<Foo> foos = SpringFactoriesLoader.loadFactories(Foo.class, null);
Note that the second argument is the optional class loader.
Compared to the Java Service Loader, the differences are two-fold:
- Whether one file format is better .i.e. more readable or more maintainable, than the other is a matter of personal taste.
- There’s no requirement in
spring.factories
for the key to be an interface and for the values to implement it. For example, Spring Boot uses this approach to handle auto-configuration beans: the key is an annotation i.e.org.springframework.boot.autoconfigure.EnableAutoConfiguration
while values are classes annotated with@Configuration
. This allows for a much more flexible design, if used wisely.
Sources for this article can be found on GitHub in Maven format.