DEV Community

Satyarth Agrahari
Satyarth Agrahari

Posted on • Updated on

Playing the generator game

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;
}
Enter fullscreen mode Exit fullscreen mode

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;   
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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;   
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

  1. in newer versions of lombok one could add lombok.copyableAnnotations += com.google.inject.name.Named in lombok.config file and lombok would try and copy them when it generates the constructor.
  2. Go with our old fashioned constructor based injection and write
public class Test {
    @Inject
    Test(@NonNull @Named("testString") final String testString) {
        this.testString = testString;
    }
}
Enter fullscreen mode Exit fullscreen mode

I prefer 2, because of couple of reasons

  1. 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.
  2. 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.
  3. 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)