Lombok – jedni go kochają, drudzy nienawidzą, trzeci o nim nie wiedzą. Tak jak inne biblioteki/frameworki tak i opisywana biblioteka użyta w niewłaściwy sposób może spowodować więcej złego niż dobrego. Niektórzy decydują się nie korzystać z niej w projekcie lub użyć innego podobnego rozwiązania np. Google AutoValue. Jednak w tym artykule nie zamierzam rozstrzygać słuszności jednego rozwiązania nad drugim. Tutaj chcę przedstawić dość przydatną funkcję wprowadzoną w wersji v1.18.4 pozwalającą na kopiowanie adnotacji do wygenerowanego kodu.

Zanim zaczniemy krótkie wprowadzenie, a więc co to jest projekt lombok? Jest to darmowa biblioteka do Javy eliminująca boilerplate code np. gettery, konstruktory, toString itp. Automatyzuje również używanie loggerów oraz wiele innych rzeczy. Aby auto generacja mogła się wykonać musimy zadbać o to aby:

  • W classpath znajdowała się biblioteka lombok.jar ( np. poprzez dodanie zależności do maven/gradle lub ręcznie )
  • Nasze IDE miało włączony processor adnotacji

Wstrzykiwanie zależności przez konstruktor

Od wydania na świat Springa w wersji 4.3 jeśli klasa posiada tylko jeden konstruktor, to przy wstrzykiwaniu zależności przez konstruktor adnotacja @Autowired nie jest wymagana. Jeżeli jednak mamy więcej niż jeden konstruktor wtedy musimy opatrzyć wyżej wymienioną adnotacją któryś z nich. Wstrzykiwanie zależności w taki sposób jest zalecane przez twórców Springa i posiada wiele zalet mianowicie:

  • Od razu widać, kiedy przesadzamy z liczbą zależności.
  • Nie utrudnia testowania
  • Zmienne mogą być immutable poprzez zadeklarowanie ich jako final
  • Tworzony obiekt już na starcie jest “Pełny” czyli ma wstrzyknięte wszystkie zależności

Generowanie konstruktora lombok a adnotacje @Qualifier / @Value

Ten kto nigdy nie miał konfliktu podczas wstrzykiwania beana mającego więcej niż jedną implementację niech pierwszy rzuci kamień. 🙂 Wydawać mogło by się, że taki problem to nie problem wystarczy chociażby przy argumencie konstruktora dodać adnotację @Qualifier określającą która implementacja interfejsu powinna zostać wstrzyknięta. Sprawa jednak się nieco komplikuje, gdy korzystamy z automatycznej generacji za pomocą biblioteki lombok. Fizycznie nie widzimy wygenerowanego konstruktora dlatego też nie możemy wskazać z której konkretnie implementacji chcemy skorzystać.

Przykładowo mając 2 implementacje interfejsu NewsService przy uruchamianiu programu dostaniemy błąd :

@RequiredArgsConstructor
@RequestMapping(value = "api/news")
@RestController
public class NewsResource {

   private final NewsService newsService;
   
}

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in pl.com.sk.newspot.news.NewsResource required a single bean, but 2 were found:
	- anotherNewsServiceImpl: defined in file [C:\cupofcodes\lombok\target\classes\pl\com\sk\newspot\news\AnotherNewsServiceImpl.class]
	- newsServiceImpl: defined in file [C:\cupofcodes\lombok\target\classes\pl\com\sk\newspot\news\NewsServiceImpl.class]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed


Process finished with exit code 1

Podczas kompilacji adnotacja @RequiredArgsConstructor jest obsługiwana i wygenerowana klasa zostaje wzbogacona o konstruktor i wygląda tak jak poniżej:

@RequestMapping({"api/news"})
@RestController
public class NewsResource {

    private final NewsService newsService;
    
    public NewsResource(NewsService newsService) {
        this.newsService = newsService;
    }
}

Od wersji lomboka v1.18.4 z pomocą przychodzi plik konfiguracyjny lombok.config a dokładniej jeden z jego propertisów lombok.copyableAnnotations. Dzięki niemu możemy wskazać listę adnotacji do skopiowania wraz ze zmienną np. @Qualifier.

Plik lombok.config powinien znajdować się na poziomie głównego katalogu aplikacji np, tam gdzie plik pom.xml. Przykładowa zawartość jest widoczna poniżej.

lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier

Teraz możemy dodać adnotację @Qualifier, która automatycznie zostanie skopiowana razem z newsService do konstruktora co rozwiąże wcześniej występujący błąd:

@RequiredArgsConstructor
@RestController
public class NewsResource {

   @Qualifier("anotherNewsServiceImpl")
   private final NewsService newsService;
}

Podczas kompilacji biblioteka lombok sprawdzi czy adnotacje nad aktualnie przetwarzaną zmienną znajdują się w liście adnotacji do skopiowania, jeśli tak to je skopiuje i wkleja do wygenerowanego kodu. W naszym przypadku powstaje poniższy konstruktor

@RequestMapping({"api/news"})
@RestController
public class NewsResource {
    @Qualifier("anotherNewsServiceImpl")
    private final NewsService newsService;
    
    public NewsResource(@Qualifier("anotherNewsServiceImpl") NewsService newsService) {
        this.newsService = newsService;
    }
}

Zabieg ten pomoże również korzystać z innych adnotacji np @Value , pozwala ona na wstrzyknięcie odpowiedniej wartości z załadowanych propertisów. W pliku lombok.config możemy zarejestrować więcej niż jedną adnotację do skopiowania co widać na poniższym przykładzie.

lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Value

Podsumowanie

Łącząc lomboka ze Springiem prędzej czy później każdy spotka się z podobną sytuacją jak opisywana powyżej, mam nadzieje że po przeczytaniu niniejszego artykułu już wiesz jak sobie z nią poradzić. 🙂 Zachęcam do pisania w komentarzach czy znacie jeszcze inne sposoby na rozwiązanie tego przypadku, oczywiście poza ręcznym napisaniem konstruktora. 🙂