Java: declaring beans with no annotations
- Published
- 7 min reading
The corporate banking project I am part of has to meet very different needs of many clients. This requires the use of a wide range of technologies. Isolating purely technical aspects from business ones is a necessity. Who wants to have a business domain dirty with annotations and dependencies to different frameworks? Such dependencies are a simple way to turn a flexible system into a legacy code.
However, a multi-layered system and the separation of the domain from the infrastructure is not something we can have just like that. In our project, in which we use the Spring framework, it was necessary to add a configuration in the infrastructure layer, turning regular Java classes into Spring components, later injected as dependencies.
A few years ago, at the beginning of this project, such a configuration could have been written in two different ways: using XML or Java classes containing bean configuration with Spring annotations. We decided to go with the latter. The sample code defining the components looked like this:
@Configuration
public class EventFactoryConfiguration {
@Bean
public AccountsBalancesSynchronizationEventFactory
accountsBalancesSynchronizationEventFactory(
final InterfaceNameResolver interfaceNameResolver,
final AccountRepository accountRepository,
final BalanceProvider balanceProvider) {
return new
AccountsBalancesSynchronizationEventFactory(interfaceNameResolver,
accountRepository, balanceProvider);
}
@Bean
public AccountSynchronizationEventFactory
accountSynchronizationEventFactory(final InterfaceNameResolver
interfaceNameResolver) {
return new AccountSynchronizationEventFactory(interfaceNameResolver);
}
@Bean
public StopAccountDispositionSynchronizationEventFactory
stopAccountDispositionSynchronizationEventFactory(
final InterfaceNameResolver interfaceNameResolver) {
return new
StopAccountDispositionSynchronizationEventFactory(interfaceNameResolver);
}
@Bean
public AccountBalancesSynchronizationEventFactory
accountBalancesSynchronizationEventFactory(
final InterfaceNameResolver interfaceNameResolver) {
return new
AccountBalancesSynchronizationEventFactory(interfaceNameResolver);
}
}
Doesn't this code look like a regular boilerplate? It does. And in 99% of cases it's something Spring could handle on its own. Still, being conscious developers, we decided not to let the @Component annotation into our domain.
An additional disadvantage of such declarations, apart from the fact that they take hundreds or even thousands of lines of code in a large project, is the fact that when we add a new constructor parameter in a class, it is also necessary to add it in the configuration class. And the developer often remembers about that only when a compilation error occurs.
So how do you live with that? Do you need to buy a comfortable mechanical keyboard and write hundreds of lines of a configuration code? Nah. Ever since version 5, Spring offers an alternative in the form of additional methods to register beans. The documentation shows an example of a bean definition using such a method:
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(Foo.class);
context.registerBean(Bar.class, () -> new
Bar(context.getBean(Foo.class))
);
All you have to do is specify which bean class you're registering, and Spring will take care of the rest. We don't have to pass on the dependencies directly. Well, unless we want to, then we can. A simple yet powerful tool. It opens up new possibilities of writing code in the functional paradigm. The combination of this approach and a language like Kotlin, for example, has resulted in the emergence of a special DSL for bean registration. Personally, I think this is the most effective way to work with Spring. But what if we can't use Kotlin for any reason?
In this case, it is still possible to use a functional way of declaring beans from the Java level, which will also remove a lot of configuration code. It can be even tens of thousands of lines of code, as it was in our case.
Now I would like to focus on such a case, and show you how to combine a functional way of configuration with the existing Java Spring Boot project.
Let's start by collecting components that we want to register. It is good to somehow group these declarations right away. I'll go with layer-first grouping, but nothing stands in the way of using feature-first. A class with bean definitions from the previous example will look like this:
public class EventFactoryBeans {
static List<Class<?>> eventFactories = Arrays.asList(
AccountsBalancesSynchronizationEventFactory.class,
AccountSynchronizationEventFactory.class,
StopAccountDispositionSynchronizationEventFactory.class,
AccountBalancesSynchronizationEventFactory.class
)
}
Isn't that a clearer record? Most of the code has become unnecessary. Note that I’m giving you a list of specific classes, not e.g. interfaces. Anyway, injections are still possible using the interface the class implements. However, to turn this list of classes into Springs beans in runtime, I still need to call the registerBean method somewhere. For this purpose, I will add the ApplicationContextInitializer interface implementation: ApplicationContextInitializer:
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.support.GenericApplicationContext;
public class BeanRegistrationContextInitializer implements
ApplicationContextInitializer<GenericApplicationContext> {
private Set<Class<?>> allBeans = Arrays.asList(
EventFactoryBeans.eventFactories
// miejsce na reszte deklaracji komponentów
);
@Override
public void initialize(GenericApplicationContext context) {
allBeans.forEach(bean -> context.registerBean(bean));
}
protected void overrideBean(Class<?> oldBean, Class<?> newBean) {
allBeans.remove(oldBean);
allBeans.add(newBean);
}
protected void register(Class<?> newBean) {
allBeans.add(newBean);
}
}
As you can see, registering beans comes down to calling the registerBean method. Spring no longer has to scan packages for annotations, it is us who decide what components to create. This will also reduce the time it takes to run the application.
Finally, the question remains, why did the overrideBean and register methods appear in the above code? They will make our life easier when we create a product for several different clients, and work in one branch. In such a case, when we need to customize a client (let's call it X), we can create an additional initializer:
public class BeanRegistrationContextInitializerX extends
BeanRegistrationContextInitializer {
public BeanRegistrationContextInitializerX() {
overrideBean(AccountFileRepository.class, AccountFileRepositoryX.class);
register(CustomerTypeToCustomerConverterX.class);
register(CustomerHttpEndpointX.class);
}
}
As you can see, we can overwrite any part of business processes with a customer-specific code. The question remains how to notify Spring Boot about the launch of the initializer:
public class Application {
public static void main(final String[] args) {
new SpringApplicationBuilder(Application.class)
.initializers(new BeanRegistrationContextInitializer())
.run(args);
}
}
In the integration tests we can use the @ContextConfiguration(initializers = BeanRegistrationContextInitializer.class) annotation.
An additional advantage of the above configuration method is that we can introduce it gradually. The code containing the configuration using the @Bean annotation will continue to run, and we can still use it. If there is still a lot of code in your project where you declare components using the @Bean annotation, I encourage you to try an alternative in the form of DSL in the Kotlin language, or in the form of a simple mechanism that I’ve presented.
Kamil Lolo, Java Developer/Designer, Comarch