Lombok – Cum sa fii lenes si elegant

Project Lombok a fost construit cu un singur scop: sa scape de partea verbose din codul Java. Cunosc multi programatori care nu-si pot inchipui viata fara Lombok – il adauga in lista de dependinte core pe care o folosesc la pornirea oricarui proiect nou. Echipe intregi au petrecut luni de zile refactorizand cod legacy, avand ca prioritate introducerea acestui modul.

Nivel Dificultate: Mediu

Daca este intr-adevar atat de util – tu decizi! Sa vedem ce ofera acest proiect.

Project Lombok – Maven

Pentru a incepe sa folosesti adnotarile Lombok, primul pas este acela de a-l adauga ca dependinta in fisierul pom:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
    <scope>provided</scope>
</dependency>

Un maven clean install iti va aduce cele necesare pentru a incepe.

Project Lombok – Eleganta entitatilor

Daca ai lucrat pana acum la un proiect Java Enterprise, sunt convins ca urmatorul exemplu de clasa iti este familiar:

@Entity
@Table(name = "notifications")
public class Notification {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long userId;
    private String message;
    private String title;
    private boolean seen;
    private Timestamp time;
    
    public Notification(){}

    public Notification(Long userId, String message, String title){
       this.userId = userId;
       this.message = message;
       this.title = title;
    }


    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public boolean isSeen() {
        return seen;
    }

    public void setSeen(boolean seen) {
        this.seen = seen;
    }

    public Timestamp getTime() {
        return time;
    }

    public void setTime(Timestamp time) {
        this.time = time;
    }
}

Aceasta este o clasa care reprezinta o notificare in cadrul unui proiect web. Aceasta este adnotata cu @Entity pentru a fi interpretata de JPA, de unde tragem concluzia ca nu vom avea logica de business – este un model standard de entitate care urmeaza a fi plimbata din baza de date catre backend de cateva mii de ori pe ora.

Pssst…: Daca vrei sa inveti Java de la 0, sau vrei sa iti aprofundezi cunostintele, am cursul perfect pentru tine – vezi aici

Observam ca o mare parte din aceasta clasa este ocupata de getteri, setteri si constructor. Pentru o persoana de business (cum le place lor sa te intituleze), codul de mai sus se poate rezuma in „O notificare are un titlu, un mesaj, un id de utilizator si o data la care a fost generata” – tot ce este pe langa aceste informatii este considerat boilerplate code si sare din ecuatie daca folosim Lombok.

Asa ar arata versiunea refactorizata a acestei clase, folosindu-ne de adnotarile noi:

@Entity
@Table(name = "notifications")
@Getter @Setter @NoArgsConstructor
public class Notification {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long userId;
    private String message;
    private String title;
    private boolean seen;
    private Timestamp time;

    public Notification(){}
    
    public Notification(Long userId, String message, String title){
       this.userId = userId;
       this.message = message;
       this.title = title;
    }
}

Dupa cum vezi, am scurtat codul cu 48 de linii, folosind cele 3 noi adnotari:

  • @Getter – care va genera automat cate o metoda de tip getter pentru fiecare camp introdus.
  • @Setter – care va genera automat cate o metoda de tip setter pentru fiecare camp introdus.
  • @NoArgsConstructor – care va genera un constructor fara parametrii, pentru a nu-l pierde atunci cand il facem pe cel cu parametri din exemplu de mai sus.

Lombok face aceste adaugiri in momentul build-ului, injectand cod nou Java in ByteCode-ul proiectului tau, ajungand in fisierele .class acelasi cod pentru ambele exemple prezentate.

Ce facem in cazul in care vrem sa schimba modificatorul de acces pentru un anumit camp? Simplu:

private @Getter(AccessLevel.PRIVATE) double ratingScore;

Aceasta proprietate se poate adauga si la adnotarea @Setter.

Ce facem daca vrem sa eliminam doar un subset de campuri dintr-o entitate pentru care vrem ca Lombok sa ne genereze getteri si setteri? Simplu:

private @Getter(AccessLevel.NONE) int age;

Aceasta proprietate se poate adauga si la adnotarea @Setter.

Vrei sa adaugi constrangeri de null campurilor tale? Poti folosi adnotarea @NonNull pe care o poti folosi atat la definirea variabilelor:

private final @NonNull String name;

… care va adauga verificari de nulitate peste acest camp atunci cand va fi accesat, sau in lista de parametri ai unei metode:

public void computeSum(@NonNull Number firstNumber, @NonNull Number secondNumber)

… caz in care Lombok va adauga o verificare de nulitate care va arunca un NullPointerException in cazul in care il vei trimite null.

Putem uita si de autogenerarea metodelor toString() si equals() / hashCode() folosind adnotarile corespunzatoare:

  • @ToString – care va oferi implementarea default a metodei toString() care include toate campurile clasei.
  • @EqualsAndHashCode – care ofera o implementare desteapta a metodelor equals() si hashCode() ce tine cont de campurile pe care el le considera potrivita.

In cazul in care dorim generarea setterilor fluenti, adica sa activam constructii fluente ale instantierii claselor, avem doua optiuni:

Putem folosi adnotarea @Accessors cu atributul fluent=true care va genera setteri fluenti (in loc de setAge(), va genera age()):

@Accessors(fluent = true) @Getter @Setter
public class Record {
   private int value;
   private String name;
}

… care ne va permite sa construim obiectul astfel:

Record record = new Record()
      .value(100)
      .name("Example Record");

Sau putem folosi adnotarea @Builder ce ne permite sa construim un Builder mult mai usor:

@Builder
public class Record {
   private int value;
   private String name;
}

… si ne permite sa folosim:

Record record = Record.builder()
   .value(100)
   .name("Example Record")
   .build();

Un alt capitol la care ne ajuta Lombok sa scapam de linii de cod inutile (si aici sunt 100% de acord) este logging-ul. Decat sa tot initializam loggerul in fiecare clasa unde avem nevoie de el, putem folosi implementarea Slf4j disponibila in Lombok astfel:

@Slf4j
private class RecordService{
   public void testLogging(){
      log.debug("Logging works!");
}}

Sau, in cazul in care nu vrei sa folosesti Slf4j, Lombok ofera suport si adnotarilor: @Log, @Log4j, @XSlf4j.

Daca vrei sa iti creezi clase imutabile, poti folosi adnotarea @Value care va autogenera codul din clasa respectiva pentru a se asigura ca, intr-adevar, este imutabila. Ai grija cum o folosesti – prea multe clase imutabile, intr-un proiect enterprise, pot fi o sursa de risc in domeniul performantei acesteia.

Project Lombok – Concluzie

Pentru cei dintre voi care credeti ca sunteti mai productivi daca nu scrieti atat cod, aceasta librarie este ideala. Aveti insa grija la problemele de performanta si securitate pe care le introduce folosirea adnotarilor Lombok – ele pot fi ascunse in clase foarte mici si greu sesizabile datorita lipsei de verbose in clasele noastre. Un exemplu concret ar fi folosirea @Value atunci cand nu este cazul.

Eu nu folosesc si nici nu sunt mare fan – vorbeste omul care adauga inclusiv atributele cu valori default din cadrul Spring pentru a ma asigura ca, in viitor, cand se va face un version bump la vreo librarie pe care o folosesc, sa nu ma trezesc cu acele valori default schimbate si cu cod care sa nu mai functioneze complete.

Nu am acoperit toate adnotarile si metodele oferite de Lombok, insa, pentru a le citi in intregime, poti intra aici.

In rest, spor la joaca!

Lasă un comentariu

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *