Spring dependency management — A new approach to wire beans

Yağız Ünsal
4 min readDec 27, 2020

--

Spring is in many ways a great library. At the core of the Spring lies the concept of dependency injection. Basically, it means, we identify our dependencies to Spring, and then when we need one, it is created and supplied for us. This is achieved so easily, by using @Autowired annotation above a field, setter method or constructor.

While this technique is quite handy, it may result in bad code blocks in poorly designed large projects. In many enterprise companies, projects start with a little piece of code. But as the time goes on, projects get bigger and bigger. Inevitably, god classes occur everywhere in the project. More and more dependencies are needed and injected.

This is an example of a bad controller class in a Spring Boot project. 13 dependencies are injected to the class. Of course, if you have so much dependencies, you cannot (should not) use constructor injection. Compulsorily, field injection is used. Autowired annotation is placed over each field. This is actually a small class compared to real world examples. I have seen classes that inject more than 50 dependencies!

This is bad programming of course. Spring has no fault. It is very likely that you come across these problems in enterprise companies. In real world, people often do not volunteer to fix these problems in large-scale projects, as it is seen too risky and discouraged. However, the first (and best) solution would be to decompose this god class into smaller pieces so that each piece has minimal dependencies which is easy to maintain. But if this is not an option, I have another solution to overcome this problem.

In this article, I will propose a new approach to inject Spring beans to our classes. Only a Spring Boot project is necessary. We’ll also use Lombok to avoid the necessity of getter and setter methods. When properly applied, we will get rid of all @Autowired annotations (we’ll only use it once — yes, once!).

So, let’s get into work…

First, let’s create a package in our project named “bean”. In this package, we create a class:

This is an abstract class. We’ll use it to distinguish our bean providers from other Spring beans. 2 event methods are added in case there is a need. We’ll use onAfterLoad() method. But, onBeforeLoad() is added as well.

Next, we create 3 bean provider classes. Of course, the number of bean providers is completely up to you.

These classes are annotated as @Component. Which means, these are Spring beans. Getter methods will be created by Lombok. Also, Lombok will create setter methods as well, but setter methods will have protected as access modifier. This is to prevent the classes from other packages to change our beans.

As you can clearly see, in bean provider classes, we list the beans we need. Thanks to Lombok, we don’t write getter and setter methods. We only declare the dependency we need, and do nothing more! We’ll do the binding later. Also, notice that with this approach, we can group our related beans in different bean providers. And, last but not least, it is worth to mention that a bean can be declared in several bean providers (meaning, there is no limitation such as a bean can only be defined in one provider).

In all bean providers, onAfterLoad() method is overridden. This method will be invoked by us later, when all of the beans defined in the provider are loaded. In this method, we assign the bean providers to the field of same class with name Beans. The definition of this class is followed:

This is the class where we declare all our bean providers and create their public static getter methods. The class is meant for static methods, so the constructor is private.

So far, we created bean provider classes, and a global Beans class that can be used to access bean providers statically from anywhere in the project. But, how are we going to load these beans? Let’s do the last and most important part of this technique.

Let’s proceed step by step. We create a class named BeanInitializer:

We declare the class as @Component, to ensure that is instantiated and executed at the very beginning of the application. Class has 2 fields, one to hold bean providers in a list, the other to hold a ListableBeanFactory. ListableBeanFactory is a class to access all Spring beans and create one if needed. We autowire this object in constructor. Note that, this is the only place where we use @Autowired annotation ever!

We create init() method and annotate as @PostConstruct. It will be invoked right after the instance is created.

Now, we create a marker annotation. This annotation can be used in bean providers to skip the initialization of a field. Let’s continue coding on BeanInitializer class:

After collecting all bean providers, it is time to load the beans declared in those providers. For that, we examine the fields of bean providers.

This method examines a field in bean provider. Then, if a suitable bean is found, it is created to assign to that field.

These are the final codes of our new approach. We assign the bean created to the bean provider field via (prefferably) setter method invoke or field set with reflection.

Conclusion

This may be a long way, I know :)

But this code works perfectly. The question is this: “What are the advantages we gained with this approach?”

Let’s go back to the PurchaseController we saw in the beginning:

Now, we can access our beans using static getter methods. No autowiring! We used @Autowired annotation only at the BeanInitializer class. We got rid of all the field injections.

Simple, isn’t it?

What do you think of this technique? Is it a practical approach? Has it been done before? Or does it have some downsides that I can’t see?

Stay with Java.

P.S. Here is the project link in case you want to check it out: GitHub

--

--