Spring + Lombok + @Qualifier or injection just became a bit easier (part 1 of 2)

The State of Dependency Injection

“Field injection is evil”

It is not a widely known fact, but the de-facto modern standards in dependency injection, as far Java and Spring are concerned, were born and solidified by the rather famous Spring blog post “How not to hate Spring in 2016” and also in this article by Oliver Gierke: “Why field injection is evil“. This is when we were first officially advised that we should:

Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies.

If you haven’t read these blog posts yet and don’t know the reason why things are how they are nowadays, I highly recommend you to do so.

Some history on Antipatterns

The code of many Java developers using Spring in 2014 looked something like this:

@Service
public class SomeService {
@Autowired private SomeDependency someDependency;
@Autowired private SomeOtherDependency someOtherDependency;
//..code
}

view raw
SomeService.java
hosted with ❤ by GitHub

That was very iffy, and it was obvious why when we went into testing:

@RunWith(MockitoJUnitRunner.class)
public class SomeServiceTest {
@Mock private SomeDependency someDependency;
@Mock private SomeOtherDependency someOtherDependency;
@InjectMocks private SomeService someService;
@Test
public void testSomething() {
//.. code
}
}

view raw
SomeServiceTest.java
hosted with ❤ by GitHub

This code has an inherent problem: it is not obvious that SomeService actually depends on both SomeDependency and SomeOtherDependency.

If you were to remove the dependency on either, for example, while refactoring, the test would still be valid. So you, as a developer have no compile time indication that your SomeService class doesn’t depend anymore on two dependencies but one, so you’d never think to refactor the test and remove the extra dependency. Also, as mentioned in Ted Vinke’s blog: “Why you should never use injectmocks“, you should really never ever use injectMocks and why your dependencies should never be private:

If you’re doing TDD or not (and we are able to change the test first) – clients of this code don’t know about an additional dependency, because it’s completely hidden. You shouldn’t use InjectMocks to deal with injecting private fields (err..or at all) , because this kind of Dependency Injection is evil – and signals you should change your design.

On to the modern stuff

You already know these things and are probably asking yourself:

As a modern Spring/Java developer, you have learned the evils of field injection and know to have your dependencies explicitly listed in the constructor as final fields (cause you don’t want to give the people the incentive/possibility to change them), leading to code like this:

@Service
public class SomeService {
private final SomeDependency someDependency;
private final SomeOtherDependency someOtherDependency;
@Autowired
public SomeService(SomeDependency someDependency, SomeOtherDependency someOtherDependency) {
this.someDependency = someDependency;
this.someOtherDependency = someOtherDependency;
}
//..code
}

view raw
SomeService.java
hosted with ❤ by GitHub

This makes the test and the class much easier to refactor, easy to see at a glance what the dependencies are, but is so damn ugly and verbose.

The rise of Lombok

Enter Lombok, the wonderful library that allows code like the code above to become the more concise:

@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class SomeService {
private final SomeDependency someDependency;
private final SomeOtherDependency someOtherDependency;
//..code
}

view raw
SomeService.java
hosted with ❤ by GitHub

and in modern versions of Spring you can ditch the onConstructor ugliness. (Spring is smart enough to do DI based on the constructor and order and type of arguments, provided you have declared only one constructor)

@Service
@RequiredArgsConstructor
public class SomeService {
private final SomeDependency someDependency;
private final SomeOtherDependency someOtherDependency;
//..code
}

view raw
SomeService.java
hosted with ❤ by GitHub

@Service
@RequiredArgsConstructor
public class SomeService {
private final SomeDependency someDependency;
private final SomeOtherDependency someOtherDependency;
//..code
}

view raw
SomeService.java
hosted with ❤ by GitHub

Oliver Gierke mentions about lombok:

Admittedly I’ve been turned off by the amount of code to be written for constructor injection in the first place as well. This is clearly a shortcoming of Java as a languages. Unfortunately a lot of good OO practices like value objects, favoring delegation over inheritance and constructor DI are significantly easier to implement in languages like Scala.

However, Project Lombok is a really awesome helper to reduce the amount of boilerplate you have to write to do “the right things” (™).

The problem with @Qualifier

However, in the past, some things and use cases were not possible to be achieved so painlessly, and involved writing a lot of boilerplate. Consider a class where you have many dependencies of the same type, for example, destinations in a routing application, or different converters for a payload of a message. You’d naively write your class like this:

@Service
@RequiredArgsConstructor
public class SomeRouterService {
@NonNull private final DispatcherService dispatcherService;
@NonNull private final SomeDestination someDestination1;
@NonNull private final SomeDestination someDestination2;
public void onMessage(Message message) {
//..some code to route stuff based on something to either destination1 or destination2
}
}

And then…

The above code would not work because Spring doesn’t have any idea what differentiates the destination1 from the destination2 argument. Therefore you end up using @Qualifier and (oh, so naively) write this code:

@Service
@RequiredArgsConstructor
public class SomeRouterService {
@NonNull private final DispatcherService dispatcherService;
@Qualifier("someDestination1") @NonNull private final SomeDestination someDestination1;
@Qualifier("someDestination2") @NonNull private final SomeDestination someDestination2;
public void onMessage(Message message) {
//..some code to route stuff based on something to either destination1 or destination2
}
}

This code also wouldn’t work cause the generated constructor doesn’t actually transpose the @Qualifier annotations from the private field declaration to each constructor parameter on the generated constructor.

So, after some fighting with your consciousness, googling around, and begging your teammates for forgiveness, you end up writing the following ugly mess of a code:

@Service
public class SomeRouterService {
private final DispatcherService dispatcherService;
private final SomeDestination someDestination1;
private final SomeDestination someDestination2;
@Autowired
public SomeRouterService (
@NonNull DispatcherService dispatcherService,
@Qualifier("someDestination1") @NonNull SomeDestination someDestination1,
@Qualifier("someDestination2") @NonNull SomeDestination someDestination2) {
this.dispatcherService = dispatcherService;
this.someDestination1 = someDestination1;
this.someDestination2 = someDestination2;
}
public void onMessage(Message message) {
//..some code to route stuff based on something to either destination1 or destination2
}
}

Basically, only because you have to use @Qualifier, you are throwing away all the boilerplate reduction capabilities of lombok, and you end up writing your constructors like a moron. Your reaction to this, especially if all your other DI code doesn’t involve all this @Qualifier bullshit, should be something along the lines of:

In the second part of this blog post, I’d show you how we in Relay42 managed to resolve this problem for the whole Java/Spring/Lombok community so we can all continue writing simple code where DI is not so intrusive, and throw away all the boilerplate using lombok.

One thought on “Spring + Lombok + @Qualifier or injection just became a bit easier (part 1 of 2)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.