Introduksjon til Project Lombok

1. Unngå repeterende koder

Java er et flott språk, men noen ganger blir det for ordentlig for ting du må gjøre i koden din for vanlige oppgaver eller etterlevelse av noen rammepraksis. Disse gir ofte ingen reell verdi for forretningssiden av programmene dine - og det er her Lombok er her for å gjøre livet lykkeligere og deg selv mer produktiv.

Måten det fungerer på er å koble til byggeprosessen din og automatisk generere Java bytecode til din .klasse filer i henhold til et antall prosjektkommentarer du introduserer i koden din.

Å inkludere det i byggene dine, uansett hvilket system du bruker, er veldig rett frem. Prosjektsiden deres har detaljerte instruksjoner om detaljene. De fleste av prosjektene mine er baserte, så jeg slipper bare deres avhengighet i sørget for omfang og jeg er god å gå:

 ... org.projektlombok lombok 1.18.10 gitt ... 

Se etter den nyeste tilgjengelige versjonen her.

Merk at avhengig av Lombok ikke vil gjøre brukere av din .krukkeDet er også avhengig av det, da det er en ren byggeavhengighet, ikke kjøretid.

2. Getters / Setters, Constructors - So Repetitive

Innkapsling av objektegenskaper via offentlige getter- og settermetoder er en så vanlig praksis i Java-verdenen, og mange rammer er avhengige av dette "Java Bean" -mønsteret: en klasse med en tom konstruktør og få / sette metoder for "egenskaper".

Dette er så vanlig at de fleste IDEs støtter autogenererende kode for disse mønstrene (og mer). Denne koden må imidlertid leve i kildene dine og også vedlikeholdes når for eksempel en ny eiendom blir lagt til eller et felt omdøpt.

La oss vurdere denne klassen vi vil bruke som en JPA-enhet som et eksempel:

@Entity offentlig klasse Bruker implementerer Serialiserbar {privat @Id Lang id; // vil bli satt når vedvarende privat streng fornavn; privat streng etternavn; privat alder; offentlig bruker () {} offentlig bruker (streng fornavn, streng etternavn, int alder) {this.firstName = fornavn; this.lastName = etternavn; this.age = alder; } // getters and setters: ~ 30 ekstra kodelinjer}

Dette er en ganske enkel klasse, men vurder fortsatt om vi la til den ekstra koden for getters og settere, vil vi ende opp med en definisjon der vi ville ha mer kjeleplatens nullverdikode enn relevant forretningsinformasjon: "en bruker har første og etternavn og alder. ”

La oss nå Lombok-ize denne klassen:

@Entity @Getter @Setter @NoArgsConstructor // <--- DETTE er det offentlig klasse Bruker implementerer Serialiserbar {privat @Id Lang id; // vil bli satt når vedvarende privat streng fornavn; privat streng etternavn; privat alder; offentlig bruker (streng fornavn, streng etternavn, int alder) {this.firstName = fornavn; this.lastName = etternavn; this.age = alder; }}

Ved å legge til @Getter og @Setter kommentarer vi ba Lombok om å, vel, generere disse for alle feltene i klassen. @NoArgsConstructor vil føre til en tom konstruktørgenerasjon.

Merk at dette er hel klassekode, jeg utelater ikke noe i motsetning til versjonen ovenfor med // getters og setters kommentar. For en tre relevante attributtklasser er dette en betydelig besparelse i kode!

Hvis du videre legger til attributter (egenskaper) til din Bruker klasse, vil det samme skje: du brukte merknadene på selve typen, slik at de vil huske alle feltene som standard.

Hva om du ville avgrense synligheten til noen eiendommer? For eksempel liker jeg å beholde enhetene mine id feltmodifikatorer pakke eller beskyttet synlig fordi de forventes å bli lest, men ikke eksplisitt angitt av applikasjonskode. Bare bruk en finere korn @Setter for dette bestemte feltet:

private @Id @Setter (AccessLevel.PROTECTED) Lang id;

3. Lazy Getter

Ofte må applikasjoner utføre noen dyre operasjoner og lagre resultatene for senere bruk.

La oss for eksempel si at vi trenger å lese statiske data fra en fil eller en database. Det er generelt en god praksis å hente disse dataene en gang og deretter cache dem for å tillate lesing i minnet i applikasjonen. Dette sparer applikasjonen fra å gjenta den kostbare operasjonen.

Et annet vanlig mønster er å hente disse dataene bare når det er nødvendig. Med andre ord, bare få data når den tilsvarende getter blir kalt første gang. Dette kalles lat-lasting.

Anta at disse dataene er bufret som et felt i en klasse. Klassen må nå sørge for at tilgang til dette feltet returnerer hurtigbufrede data. En mulig måte å implementere en slik klasse på er å få getter-metoden til å hente dataene bare hvis feltet er det null. Av denne grunn, vi kaller dette en lat getter.

Lombok gjør dette mulig med lat parameter i @Getter kommentar vi så ovenfor.

Tenk for eksempel på denne enkle klassen:

offentlig klasse GetterLazy {@Getter (lat = sann) privat slutt Karttransaksjoner = getTransaksjoner (); private Map getTransactions () {final Map cache = new HashMap (); Liste txnRows = readTxnListFromFile (); txnRows.forEach (s -> {String [] txnIdValueTuple = s.split (DELIMETER); cache.put (txnIdValueTuple [0], Long.parseLong (txnIdValueTuple [1]));}); returbuffer; }}

Dette leser noen transaksjoner fra en fil til en Kart. Siden dataene i filen ikke endres, cacher vi dem en gang og gir tilgang via en getter.

Hvis vi nå ser på den sammensatte koden til denne klassen, ser vi a getter-metoden som oppdaterer hurtigbufferen hvis den var null og returnerer deretter hurtigbufrede data:

offentlig klasse GetterLazy {privat slutt AtomicReference transaksjoner = nye AtomicReference (); public GetterLazy () {} // andre metoder public Map getTransactions () {Object value = this.transactions.get (); hvis (verdi == null) {synkronisert (dette.transaksjoner) {verdi = dette.transaksjoner.get (); hvis (verdi == null) {Map actualValue = this.readTxnsFromFile (); verdi = faktisk verdi == null? this.transactions: actualValue; this.transactions.set (verdi); }}} returner (Kart) ((Kart) (verdi == dette.transaksjoner? null: verdi)); }}

Det er interessant å påpeke det Lombok pakket datafeltet inn i en AtomicReference.Dette sikrer at atomoppdateringer til transaksjoner felt. De getTransactions () metoden sørger også for å lese filen hvis transaksjoner er null.

Bruken av AtomicReference-transaksjoner feltet direkte fra klassen er motløs. Det anbefales å bruke getTransactions () metode for å få tilgang til feltet.

Av denne grunn, hvis vi bruker en annen Lombok-kommentar som ToString i samme klasse, den vil bruke getTransactions () i stedet for direkte tilgang til feltet.

4. Verdiklasser / DTO-er

Det er mange situasjoner der vi ønsker å definere en datatype med det ene formål å representere komplekse "verdier" eller som "Data Transfer Objects", mesteparten av tiden i form av uforanderlige datastrukturer vi bygger en gang og aldri vil endre .

Vi designer en klasse for å representere en vellykket påloggingsoperasjon. Vi vil at alle felt skal være null og at objekter skal uforanderlige, slik at vi trygt kan få tilgang til egenskapene:

offentlig klasse LoginResult {private final Instant loginTs; privat finale String authToken; privat slutt Varighet tokenValiditet; privat endelig URL tokenRefreshUrl; // konstruktør tar hvert felt og sjekker null // skrivebeskyttet tilgang, ikke nødvendigvis som get * () form}

Igjen, mengden kode vi måtte skrive for de kommenterte delene, ville ha et mye større volum som informasjonen vi ønsker å kapsle inn, og som har reell verdi for oss. Vi kan bruke Lombok igjen for å forbedre dette:

@RequiredArgsConstructor @Accessors (fluent = true) @Getter public class LoginResult {private final @NonNull Instant loginTs; privat finale @NonNull String authToken; privat finale @NonNull Varighet tokenValidity; privat finale @NonNull URL tokenRefreshUrl; }

Bare legg til @RequiredArgsConstructor kommentar og du vil få en konstruktør for alle de siste feltene i klassen, akkurat som du erklærte dem. Legger til @NonNull til attributter får konstruktøren vår til å se om nullitet og kast NullPointerExceptions tilsvarende. Dette ville også skje hvis feltene ikke var endelige, og vi la til @Setter for dem.

Vil du ikke kjedelig gammel få*() skjema for eiendommene dine? Fordi vi la til @Accessors (flytende = sant) i dette eksemplet ville "getters" ha samme metodenavn som egenskapene: getAuthToken () rett og slett blir authToken ().

Dette "flytende" skjemaet vil gjelde for ikke-endelige felt for attributtoppsett og også tillate kjedede samtaler:

// Tenk deg at felt ikke lenger var endelige, og returner nå nye LoginResult () .loginTs (Instant.now ()) .authToken ("asdasd"). // og så videre

5. Core Java Boilerplate

En annen situasjon der vi ender med å skrive kode vi trenger å opprettholde, er når vi genererer toString (), er lik() og hashCode () metoder. IDE prøver å hjelpe med maler for autogenerering av disse når det gjelder klasseattributtene våre.

Vi kan automatisere dette ved hjelp av andre merknader på Lombok-klassenivå:

  • @ToString: vil generere en toString () metode inkludert alle klasseattributter. Ingen grunn til å skrive en selv og vedlikeholde den når vi beriker datamodellen vår.
  • @EqualsAndHashCode: vil generere begge deler er lik() og hashCode () metoder som standard med tanke på alle relevante felt, og i henhold til veldig godt om semantikk.

Disse generatorene leverer veldig praktiske konfigurasjonsalternativer. For eksempel, hvis de merkede klassene dine tar del i et hierarki, kan du bare bruke callSuper = sant parameter- og overordnede resultater vil bli vurdert når du genererer metodens kode.

Mer om dette: si at vi hadde vår Bruker JPA-enhetseksempel inkluderer en referanse til hendelser knyttet til denne brukeren:

@OneToMany (mappedBy = "bruker") private listehendelser;

Vi ønsker ikke å dumpe hele listen over hendelser når vi ringer toString () metoden til brukeren vår, bare fordi vi brukte @ToString kommentar. Ikke noe problem: bare parametriser det slik: @ToString (ekskluder = {“events”}), og det vil ikke skje. Dette er også nyttig for å unngå sirkulære referanser hvis for eksempel UserEvents hadde en referanse til a Bruker.

For Logg innResultat for eksempel kan det være lurt å definere likhet og beregning av hash-kode bare når det gjelder selve tokenet og ikke de andre endelige attributtene i klassen vår. Så skriv bare noe sånt som @EqualsAndHashCode (of = {“authToken”}).

Bonus: hvis du likte funksjonene fra kommentarene vi har gjennomgått så langt, vil du kanskje undersøke dem @Data og @Verdi merknader når de oppfører seg som om et sett av dem hadde blitt brukt på klassene våre. Tross alt er disse diskuterte bruken veldig ofte satt sammen i mange tilfeller.

5.1. (Ikke) Bruke @EqualsAndHashCode Med JPA-enheter

Om du vil bruke standard er lik() og hashCode () metoder eller lage egendefinerte for JPA-enhetene, er et ofte diskutert tema blant utviklere. Det er flere tilnærminger vi kan følge; hver har sine fordeler og ulemper.

Som standard, @EqualsAndHashCode inkluderer alle ikke-endelige egenskaper til enhetsklassen. Vi kan prøve å "fikse" dette ved å bruke onlyExplicitlyIncluded attributt til @EqualsAndHashCode for å få Lombok til å bare bruke enhetens primære nøkkel. Likevel, men den genererte er lik() metoden kan forårsake noen problemer. Thorben Janssen forklarer dette scenariet mer detaljert i et av blogginnleggene sine.

Generelt, vi bør unngå å bruke Lombok til å generere er lik() og hashCode () metoder for våre JPA-enheter!

6. Byggmønsteret

Følgende kan gi en eksempelkonfigurasjonsklasse for en REST API-klient:

offentlig klasse ApiClientConfiguration {privat strengvert; privat int port; privat boolsk brukHttps; privat lang connectTimeout; privat lang readTimeout; privat streng brukernavn; privat strengpassord; // Uansett hvilke andre alternativer du måtte tenke deg. // Tom konstruktør? Alle kombinasjoner? // getters ... og settere? }

Vi kan ha en innledende tilnærming basert på å bruke klassens standard tomme konstruktør og tilby settermetoder for hvert felt. Imidlertid vil vi helst at konfigurasjoner ikke skal gjentassett når de er bygget (instantiert), gjør dem effektivt uforanderlige. Vi vil derfor unngå settere, men å skrive en så potensielt lang args-konstruktør er et antimønster.

I stedet kan vi fortelle verktøyet å generere en bygger mønster, og hindrer oss i å skrive et ekstra Bygger klasse og tilhørende flytende setterlignende metoder ved ganske enkelt å legge til @Builder-merknaden til vår ApiClientConfiguration.

@Builder public class ApiClientConfiguration {// ... alt annet forblir det samme}

Forlater klassedefinisjonen ovenfor som sådan (ingen erklærer konstruktører eller setter + @Bygger) vi kan ende opp med å bruke den som:

ApiClientConfiguration config = ApiClientConfiguration.builder () .host ("api.server.com") .port (443) .useHttps (true) .connectTimeout (15_000L) .readTimeout (5_000L) .brukernavn ("mitt brukernavn") .passord (" hemmelig ") .build ();

7. Kontrollerte unntaksbyrder

Mange Java API-er er utformet slik at de kan kaste et antall avmerkede unntak klientkode blir tvunget til å enten å fange eller erklære til kaster. Hvor mange ganger har du gjort disse unntakene du vet ikke vil skje til noe slikt?

public String resourceAsString () {try (InputStream is = this.getClass (). getResourceAsStream ("sure_in_my_jar.txt")) {BufferedReader br = new BufferedReader (new InputStreamReader (is, "UTF-8"))); returner br.lines (). collect (Collectors.joining ("\ n")); } catch (IOException | UnsupportedCharsetException ex) {// Hvis dette noen gang skjer, er det en feil. kaste nye RuntimeException (ex); <--- innkapslet til en Runtime-eks. }}

Hvis du vil unngå disse kodemønstrene fordi kompilatoren ellers ikke vil være fornøyd (og tross alt du vet de avmerkede feilene kan ikke skje), bruk det passende navnet @SneakyThrows:

@SneakyThrows public String resourceAsString () {try (InputStream is = this.getClass (). GetResourceAsStream ("sure_in_my_jar.txt")) {BufferedReader br = new BufferedReader (new InputStreamReader (is, "UTF-8")); returner br.lines (). samle (Collectors.joining ("\ n")); }}

8. Sørg for at ressursene dine frigjøres

Java 7 introduserte prøve-med-ressursblokken for å sikre at ressursene dine holdes av forekomster av noe som implementerer java.lang.Kan lukkes automatisk frigjøres når du går ut.

Lombok gir en alternativ måte å oppnå dette på, og mer fleksibelt via @Cleanup. Bruk den til en hvilken som helst lokal variabel hvis ressurser du vil forsikre deg om er frigitt. Du trenger ikke å implementere et bestemt grensesnitt, du får bare det Lukk() metoden som kalles.

@Cleanup InputStream er = this.getClass (). GetResourceAsStream ("res.txt");

Utgivelsesmetoden din har et annet navn? Ikke noe problem, bare tilpasse kommentaren:

@Cleanup ("disponere") JFrame mainFrame = new JFrame ("Main Window");

9. Kommenter klassen din for å få en logger

Mange av oss legger sparsomt til logginguttalelser ved å lage en forekomst av en Logger fra vårt rammeverk. Si, SLF4J:

offentlig klasse ApiClientConfiguration {private static Logger LOG = LoggerFactory.getLogger (ApiClientConfiguration.class); // LOG.debug (), LOG.info (), ...}

Dette er et så vanlig mønster at Lombok-utviklere har brydd seg om å forenkle det for oss:

@ Slf4j // eller: @Log @CommonsLog @ Log4j @ Log4j2 @ XSlf4j ApiClientConfiguration offentlig klasse {// log.debug (), log.info (), ...}

Mange loggingsrammer støttes, og selvfølgelig kan du tilpasse forekomsten navn, emne, etc.

10. Skriv trådsikre metoder

I Java kan du bruke synkronisert nøkkelord for å implementere kritiske seksjoner. Dette er imidlertid ikke en 100% sikker tilnærming: annen klientkode kan til slutt også synkroniseres på din forekomst, noe som potensielt kan føre til uventede låsing.

Dette er hvor @Synchronized kommer inn: kommenter metodene dine (både forekomst og statisk) med den, og du får et autogenerert privat, ueksponert felt implementeringen din vil bruke til å låse:

@Synchronized public / * better than: synchronized * / void putValueInCache (Streng key, Object value) {// uansett hva som her vil være trådsikker kode}

11. Automatiser objektsammensetning

Java har ikke språknivåkonstruksjoner for å utjevne en "favoriseringskomposisjonsarv" -tilnærming. Andre språk har innebygde konsepter som Egenskaper eller Mixins for å oppnå dette.

Lomboks @Delegate er veldig nyttig når du vil bruke dette programmeringsmønsteret. La oss vurdere et eksempel:

  • Vi vil Brukers og Kundes for å dele noen vanlige attributter for navngivning og telefonnummer
  • Vi definerer både et grensesnitt og en adapterklasse for disse feltene
  • Vi får modellene våre til å implementere grensesnittet og @Delegat til adapteren, effektivt komponere dem med vår kontaktinformasjon

La oss først definere et grensesnitt:

offentlig grensesnitt HasContactInformation {String getFirstName (); ugyldig setFirstName (streng fornavn); String getFullName (); Streng getLastName (); ugyldig setLastName (streng etternavn); String getPhoneNr (); ugyldig settPhoneNr (streng telefonNr); }

Og nå en adapter som en Brukerstøtte klasse:

@Data offentlig klasse ContactInformationSupport implementerer HasContactInformation {private String firstName; privat streng etternavn; privat streng telefonNr; @ Override public String getFullName () {return getFirstName () + "" + getLastName (); }}

Den interessante delen kommer nå, se hvor enkelt det er å nå komponere kontaktinformasjon i begge modellklasser:

offentlig klasse Bruker implementerer HasContactInformation {// Hvilke andre brukerspesifikke attributter @Delegate (types = {HasContactInformation.class}) private final ContactInformationSupport contactInformation = new ContactInformationSupport (); // Brukeren selv vil implementere all kontaktinformasjon etter delegasjon}

Saken for Kunde ville være så like at vi ville utelate prøven for kortfattethet.

12. Rullende Lombok Back?

Kort svar: Ikke i det hele tatt egentlig.

Du kan være bekymret for at det er en sjanse for at du bruker Lombok i et av prosjektene dine, men senere vil tilbakestille den beslutningen. Du vil da ha et kanskje stort antall klasser kommentert for det ... hva kan du gjøre?

Jeg har aldri angret på dette, men hvem vet for deg, teamet ditt eller organisasjonen din. For disse tilfellene blir du dekket takket være delombok verktøy fra samme prosjekt.

Av delombok-ing koden din vil du få autogenerert Java-kildekode med nøyaktig de samme funksjonene fra bytekoden Lombok bygget. Så da kan du bare erstatte den opprinnelige merkede koden med disse nye delomboked filer og er ikke lenger avhengig av det.

Dette er noe du kan integrere i bygningen din, og jeg har gjort dette tidligere for å bare studere den genererte koden eller for å integrere Lombok med et annet Java-kildekodebasert verktøy.

13. Konklusjon

Det er noen andre funksjoner vi ikke har presentert i denne artikkelen, jeg vil oppfordre deg til å ta et dypere dykk inn i funksjonsoversikten for mer informasjon og brukssaker.

Også de fleste funksjonene vi har vist, har en rekke tilpasningsalternativer som du kan finne nyttige for å få verktøyet til å generere ting som er mest i samsvar med teampraksis for navngivning osv. Det tilgjengelige innebygde konfigurasjonssystemet kan også hjelpe deg med det.

Jeg håper du har funnet motivasjonen for å gi Lombok en sjanse til å komme inn i Java-utviklingsverktøyet ditt. Prøv det og øk produktiviteten din!

Eksempelkoden finnes i GitHub-prosjektet.