A while back I wrote a post on
So This time around, I didn't wanted to make the same mistake, which brings us back to this post.
Let's start with a simple exercise for people familiar with lombok
and dependency injection
. What do you think should be the generated code from the following block
@RequiredArgsConstructor(onConstructor = @__(@Inject))
public class Test {
@NonNull @Named("testString") private final String testString;
}
I think most people would guess (who have seen generated code by lombok) that this would translate to
public class Test {
@Generated
@Inject
Test(@NonNull @Named("testString") String testString) {
if (testString == null) {
throw new NullPointerException("testString is marked non-null but is null");
} else {
this.testString = testString;
}
}
}
But if it would have been the case, then we wouldn't be here, would we...
So lets see what it actually generates
public class Test {
@Generated
@Inject
Test(@NonNull String testString) {
if (testString == null) {
throw new NullPointerException("testString is marked non-null but is null");
} else {
this.testString = testString;
}
}
}
But that's strange, I can clearly see that I have annotated my field with @Named, and so how would it even find the correct dependency to inject, if it will not have the name discovery during dependency injection.
Well as it turns out, lombok doesn't respects all annotations, since it gets too complicated to implement that behaviour. You can go through the github issues for details 1 2
So now that we know that this is the case, the question is how to resolve this.
- in newer versions of lombok one could add
lombok.copyableAnnotations += com.google.inject.name.Named
inlombok.config
file and lombok would try and copy them when it generates the constructor. - Go with our old fashioned constructor based injection and write
public class Test {
@Inject
Test(@NonNull @Named("testString") final String testString) {
this.testString = testString;
}
}
I prefer 2, because of couple of reasons
- Putting a conf in lombok.config that determines what gets copied hides too much information and is frankly just an accident waiting to happen, when someone see this code sample and tries to copy it and it somehow works because there was no collision.
- It only copies the annotation which have copyable implemented for them, so in case we come across something that is not copyable, we are back to implementing 2 anyways.
- I like the 2nd one since its more expressive, and I like writing codes that are more expressive since eventually we are writing them for humans, and its better if we do not hide too many things and keep things simple so that the person reading code can focus more on business logic rather than spending time understanding why or why not their injection works.
Top comments (0)