Create better Spring libraries with @Enable… Annotations
You can find the sample code in the following repo: https://github.com/gorkemgok/enabler-annotation-example
Sometimes, we create libraries to make some functionalities reusable for multiple applications. When those libraries have some beans to be registered in the spring context, the developers tend to use @ComponentScan or @Import annotations in the application. By doing this, the application is made highly dependent on the library's internal structure and the author of the library loses control over the design.
Let’s say we have a spring rest application and we want to create a library to add some common headers (such as X-Host and X-Trace-Id) to the responses.
To do that we would have two request filters in our library as followings:
Consider that those two classes are in different jar files and (probably) in different packages than the user application. The users of this library have two options to use those filters in their applications. @Import them or scan the library package with @ComponentScan.
Both of those options take control from the author of the library. Any internal change in the library can break the build of the user application or even worse introduce a runtime error. Let’s say we introduce a dependency which is TraceIdGenerator in TraceIdHeaderFilter.
Then the user should also @Import that dependency.
Using @ComponentScan annotation brings other problems. Package names can change or other packages can be added to the library. Moreover, some classes might not be there to be scanned.
This makes the user application should know more about the implementation and internals of the library.
To avoid this problem, we will introduce @EnableCommonHeader annotation in our library:
So the users can directly use this annotation in their application:
Any internal change in the library is hidden. The users don’t have to handle any import or component scan process anymore.
Besides, the author of the library can have more control over the bean registration and add logic to it by using ImportSelector and ImportBeanDefinitionRegistrar.
The Import annotation accepts various interfaces for more advanced bean registration (Javadoc).
One of them is ImportSelector.
Let’s say we want to add some configuration to our library via @EnableCommonHeaders annotation.
So that we can configure which header is added to our responses.
To do that we create an implementation of the ImportSelector interface (Javadoc).
This implementation tells spring which classes should be imported.
And we import only this import selector in @EnableCommonHeaders annotation:
You can use the ImportBeanDefinitionRegistrar interface for more advanced bean registration (Javadoc).
@EnableCommonHeaders annotation is like an abstraction layer and the user’s only entry point to the library so that all the details of the library are hidden. That means you can refactor or change anything under the hood internally without breaking anything in the user application unless you change any external behavior.
By using this approach you can create more reusable, more robust, and backward-compatible spring libraries.