spring.factories

java

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.

Factory

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

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.

28 Mar 2019 #testing #spring #springmock