spring.factories
Have you ever wondered how it’s possible that spring-boot is able to pick up whatever you have on
the classpath and configure application context to your needs based on some conventions and bit of
black magic? In this post, I’m going to dig into spring.factories
file on which most of the
spring-boot power is based.
TL;DR
META-INF/spring.factories
file is a special file picked up by the spring framework in which you
can define how spring context will be customized.
Documentation
and
spring-boot’s
spring.factories.
But why?
In old days (before spring-boot project) one had to configure context by hand. You had to be aware
of what’s required for your application. What parts of the spring you’ll need, what database type
you’ll be using and for each of those prepare some xml configuration (java configuration was
introduced in spring 3.x). With spring-boot things has changed. The troublesome and laborious
process of configuring projects has been replaced by conventions and classpath scanning.
Implementing such a mechanism can be easy but only if you deliver your framework as one huge blob
that must be included with the project. Luckily (I’ve seen blobs…) spring-boot developers decided
that they’ll ship highly specialized modules and you’ll be free to include only what’s necessary for
you. With spring.factories
file they are able to achieve exactly that. Each module can provide
it’s own configuration which later will be merged and will comprise on spring context.
How?
Let’s start with spring itself. spring.factories
file support has been added in version 3.2 of the
framework (quite old feature) -
SpringFactoriesLoader
.
From version 4.2 it’s possible to define
ContextCustomizerFactory
which will be picked up during context initialization
(AbstractTestContextBootstrapper#getContextCustomizerFactories
).
In spring this mechanism is used only while setting up test context
(BootstrapWith
). It’s existence though opened a way to customize it
even further in spring-boot. Authors of spring-boot were able to create extension points which can
be added by convention and can be injected without extending framework code itself.
In case of the spring-boot usages of factories loading are spread in many places of the project, but
you can start looking from constructor of
SpringApplication
which loads
ApplicationContextInitializer
and
ApplicationListners
.
From there when the context is initialized other configurations and cusomzers are loaded. Most
automagical classes in spring-boot are loaded only if some specific classes are on the classpath.
This is achieved from
AutoConfigurationImportSelector#getCandidateConfigurations
which scans spring.factories
looking for
EnableAutoConfiguration
property values and from there those configurations unless excluded and enabled are loaded.
Possible entries in spring-core project
Possible entries in spring-boot project
org.springframework.boot.diagnostics.FailureAnalysisReporter
org.springframework.boot.autoconfigure.AutoConfigurationImportListener
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
org.springframework.boot.autoconfigure.EnableAutoConfiguration
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider
org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration
org.springframework.boot.test.context.DefaultTestExecutionListenersPostProcessor
What can we do?
To give you some idea for what it can be used let’s examine spring-mock library. It allows to inject Spock native mocks spring integration tests (written in Spock). In current Spock’s version basic support for this has already been added (library provides more configuration options), using the same approach.
When I was working on the spring-mock library I needed a way to tell spring that I’ll need to do
some extra work when booting up the context. In
spring.factories
I’ve configured support for custom annotations and registry that allowed to dynamically register
beans in test spring context. I didn’t want users to be aware of all of the internal that’s why I’ve
implemented
ContextCustomizerFactory
and
TestExecutionListener
and with this all internals are hidden from user.
TestExecutionListener
is a bit simpler so let’s start with it. As you probably already know Spock mocks are basically
bound to the test that is being executed. Before mock can be used it must be attached to the test
instance. And that’s basically what my test execution listener does. It attaches all registered
mocks and spies to test instance and once the test is done it detaches them from test instance.
// ...
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
final Object testInstance = testContext.getTestInstance();
if (!(testInstance instanceof Specification)) {
return;
}
final Specification specification = (Specification) testInstance;
final List<Object> mocks = new LinkedList<>();
final ApplicationContext applicationContext = testContext.getApplicationContext();
final DoubleRegistry doubleRegistry = applicationContext.getBean(DoubleRegistry.BEAN_NAME, DoubleRegistry.class);
for (DoubleDefinition doubleDefinition : doubleRegistry.doublesSearch()) {
final Optional<Object> doubleBean = tryToGetBean(applicationContext, doubleDefinition);
doubleBean.ifPresent(bean -> {
mocks.add(bean);
mockUtil.attachMock(bean, specification);
});
}
testContext.setAttribute(MOCKED_BEANS_NAMES, mocks);
}
@Override
public void afterTestMethod(TestContext testContext) throws Exception {
getMocksFromContext(testContext).forEach(mockUtil::detachMock);
}
// ...
Using
ContextCustomizerFactory
we can achieve much more. To allow users to inject mocks I’ve decided to use completely new
annotation (I didn’t want to cause any conflicts with existing mechanisms in spring-boot). So I had
to find all the fields annotated with my custom annotation, register those as standard spring-beans
so they can be injected into other beans and into the test. ContextCustomizer is something that
allows you to do things like that. You have access to ConfigurableApplicationContext and
MergedContextConfiguration from there you can do virtually anything with spring context.
public final void customizeContext(ConfigurableApplicationContext configurableApplicationContext, MergedContextConfiguration mergedContextConfiguration) {
final BeanDefinitionRegistry registry = (BeanDefinitionRegistry) configurableApplicationContext;
registerDoubleRegistry(registry);
registerDoubleFactory(configurableApplicationContext.getBeanFactory(), registry);
registerMockClassResolver(registry);
registerDoubleDefinitionRegisteringProcessor(registry);
registerSpyRegistrationPostProcessor(registry);
registerAdditionalBeanDefinitions(registry, additionalDefinitions);
}
Here I’m registering some internals of spring-mock library (registers for all mocks and spies used
in test context).
BeanFactoryPostProcessor
which will register mocks and spies as native spring beans.
The whole mechanism is a bit complex so I’m not going to go any deeper with it you are interested I encourage you to just clone the library and investigate it by yourself you already know what’s the entry point and where to start. Or if you are interested in spring boot internals just put a break point on SpringFactoriesLoader.
My project was focused mostly on testing but by looking on what’s been added to the spring-boot number of possible extension points is huge and more of those can be added without a lot of effort.
Summary
spring.factories
is a very powerful file which allows to customize spring-context. Knowledge about
this file might become very handy if you’ll ever need to extend spring capabilities (always check
google first, there is a chance someone already implemented it for you ;)). In most cases, you’ll
get away even without knowledge about this file but I think it’s good to know extension points of
the framework you are using for most of your applications and how to take advantage of the hooks.
If you've enjoyed or found this post useful you might also like:
28 Mar 2019 #testing #spring #springmock