Introduktion til Project Lombok

1. Undgå gentagne koder

Java er et godt sprog, men det bliver nogle gange for detaljeret til ting, du skal gøre i din kode til almindelige opgaver eller overholdelse af nogle rammepraksis. Disse giver meget ofte ingen reel værdi for forretningssiden af ​​dine programmer - og det er her Lombok er her for at gøre dit liv lykkeligere og dig selv mere produktiv.

Den måde, det fungerer på, er ved at tilslutte din byggeproces og automatisk generere Java-bytecode til din .klasse filer i henhold til et antal projektkommentarer, du introducerer i din kode.

At medtage det i dine builds, uanset hvilket system du bruger, er meget ligetil. Deres projektside har detaljerede instruktioner om detaljerne. De fleste af mine projekter er baserede, så jeg slipper typisk deres afhængighed i stillet til rådighed rækkevidde, og jeg er god til at gå:

 ... org.projectlombok lombok 1.18.10 forudsat ... 

Se efter den senest tilgængelige version her.

Bemærk, at det afhængigt af Lombok ikke gør brugere af din .krukkes afhænger også af det, da det er en ren buildafhængighed, ikke runtime.

2. Getters / Setters, konstruktører - Så gentagne

Indkapsling af objektegenskaber via offentlige getter- og settermetoder er sådan en almindelig praksis i Java-verdenen, og mange rammer bygger meget på dette “Java Bean” -mønster: en klasse med en tom konstruktør og få / sæt-metoder til “egenskaber”.

Dette er så almindeligt, at de fleste IDEs understøtter autogenererende kode til disse mønstre (og mere). Denne kode skal dog leve i dine kilder og vedligeholdes også, når f.eks. En ny ejendom tilføjes eller et felt omdøbes.

Lad os overveje denne klasse, som vi vil bruge som en JPA-enhed som et eksempel:

@Entity offentlig klasse Brugerimplementer Serialiserbar {privat @Id Lang id; // vil blive indstillet, når vedvarende privat streng fornavn; privat streng efternavn; privat int alder offentlig bruger () {} offentlig bruger (streng fornavn, streng efternavn, alder) {this.firstName = fornavn; this.lastName = efternavn; this.age = alder; } // getters og setters: ~ 30 ekstra kodelinjer}

Dette er en ret simpel klasse, men overvej stadig, hvis vi tilføjede den ekstra kode til getters og settere, ville vi ende med en definition, hvor vi ville have mere kedelplade nulværdikode end de relevante forretningsoplysninger: “en bruger har første og efternavne og alder. ”

Lad os nu Lombok-ize denne klasse:

@Entity @Getter @Setter @NoArgsConstructor // <--- DETTE er det offentlig klasse Bruger implementerer Serialiserbar {privat @Id Lang id; // indstilles, når vedvarende privat streng fornavn; privat streng efternavn; privat int alder offentlig bruger (streng fornavn, streng efternavn, int alder) {this.firstName = fornavn; this.lastName = efternavn; this.age = alder; }}

Ved at tilføje @Getter og @Setter kommentarer vi bad Lombok om, ja, generere disse til alle klassens felter. @NoArgsConstructor vil føre til en tom konstruktørgenerering.

Bemærk dette er hel klasse kode, jeg udelader ikke noget i modsætning til ovenstående version med // getters og setters kommentar. For en tre relevante attributter er dette en betydelig besparelse i kode!

Hvis du yderligere tilføjer attributter (egenskaber) til din Bruger klasse, vil det samme ske: du har anvendt kommentarerne på selve typen, så de husker alle felter som standard.

Hvad hvis du ønsker at forfine synligheden af ​​nogle ejendomme? For eksempel kan jeg godt lide at beholde mine enheder ' id feltmodifikatorer pakke eller beskyttet synlige, fordi de forventes at blive læst, men ikke udtrykkeligt indstillet af applikationskode. Brug bare en finere kornet @Setter for dette særlige felt:

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

3. Lazy Getter

Ofte skal applikationer udføre nogle dyre operationer og gemme resultaterne til efterfølgende brug.

Lad os for eksempel sige, at vi skal læse statiske data fra en fil eller en database. Det er generelt en god praksis at hente disse data en gang og derefter cache dem for at tillade læsning i hukommelsen i applikationen. Dette sparer applikationen fra at gentage den dyre operation.

Et andet almindeligt mønster er at Hent kun disse data, når det først er nødvendigt. Med andre ord, få kun dataene, når den tilsvarende getter kaldes første gang. Dette kaldes doven lastning.

Antag at disse data er cache som et felt inde i en klasse. Klassen skal nu sørge for, at enhver adgang til dette felt returnerer de cachelagrede data. En mulig måde at implementere en sådan klasse er at få getter-metoden til kun at hente dataene, hvis feltet er nul. Af denne grund, vi kalder dette en doven getter.

Lombok gør dette muligt med doven parameter i @Getter kommentar vi så ovenfor.

Overvej for eksempel denne enkle klasse:

offentlig klasse GetterLazy {@Getter (doven = sand) privat endelig Korttransaktioner = getTransactions (); 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]));}); retur cache; }}

Dette læser nogle transaktioner fra en fil ind i en Kort. Da dataene i filen ikke ændres, cacher vi dem en gang og giver adgang via en getter.

Hvis vi nu ser på den kompilerede kode for denne klasse, ser vi en getter-metode, der opdaterer cachen, hvis den var nul og returnerer derefter de cachelagrede data:

offentlig klasse GetterLazy {privat afsluttende AtomicReference-transaktioner = nye AtomicReference (); public GetterLazy () {} // andre metoder public Map getTransactions () {Object value = this.transactions.get (); hvis (værdi == null) {synkroniseret (dette.transaktioner) {værdi = dette.transaktioner.get (); hvis (værdi == null) {Map actualValue = this.readTxnsFromFile (); værdi = faktisk værdi == null? this.transactions: actualValue; this.transactions.set (værdi); }}} returner (Map) ((Map) (værdi == this.transactions? null: værdi)); }}

Det er interessant at påpege det Lombok indpakkede datafeltet i et AtomicReference.Dette sikrer atomopdateringer til transaktioner Mark. Det getTransactions () metode sørger også for at læse filen, hvis transaktioner er nul.

Brugen af AtomicReference-transaktioner felt direkte fra klassen frarådes. Det anbefales at bruge getTransactions () metode til at få adgang til feltet.

Af denne grund, hvis vi bruger en anden Lombok-kommentar som ToString i samme klasse, det vil bruge getTransactions () i stedet for direkte adgang til feltet.

4. Værdiklasser / DTO'er

Der er mange situationer, hvor vi ønsker at definere en datatype med det ene formål at repræsentere komplekse "værdier" eller som "Dataoverførselsobjekter", det meste af tiden i form af uforanderlige datastrukturer, vi bygger en gang og aldrig ønsker at ændre .

Vi designer en klasse til at repræsentere en vellykket loginoperation. Vi ønsker, at alle felter skal være ikke-nul, og objekter skal uforanderlige, så vi sikkert kan få adgang til dens egenskaber:

public class LoginResult {private final Instant loginTs; privat endelig String authToken; privat endelig Varighed tokenValidity; privat endelig URL tokenRefreshUrl; // konstruktør tager hvert felt og kontrollerer nuller // skrivebeskyttet accessor, ikke nødvendigvis som get * () form}

Igen ville den mængde kode, vi skulle skrive til de kommenterede sektioner, have et meget større volumen, som de oplysninger, vi ønsker at indkapsle, og som har reel værdi for os. Vi kan bruge Lombok igen til at forbedre dette:

@RequiredArgsConstructor @Accessors (flydende = sandt) @Getter offentlig klasse LoginResult {privat endelig @NonNull Øjeblikkelig loginTs; privat finale @NonNull String authToken; privat finale @NonNull Varighed tokenValidity; privat final @NonNull URL tokenRefreshUrl; }

Bare tilføj @RequiredArgsConstructor kommentar, og du får en konstruktør til alle de sidste felter i klassen, ligesom du erklærede dem. Tilføjer @NonNull til attributter får vores konstruktør til at kontrollere, om det er ugyldigt og kastet NullPointerExceptions derfor. Dette ville også ske, hvis felterne ikke var endelige, og vi tilføjede @Setter for dem.

Vil du ikke kedelig gammel få*() form til dine ejendomme? Fordi vi tilføjede @Accessors (flydende = sandt) i dette eksempel ville "getters" have samme metode navn som egenskaberne: getAuthToken () bliver simpelthen authToken ().

Denne "flydende" formular gælder for ikke-endelige felter for attributindstillere og tillader også kædede opkald:

// Forestil dig, at felter ikke længere var endelige, returnerer nu nye LoginResult () .loginTs (Instant.now ()) .authToken ("asdasd"). // og så videre

5. Core Java-kedelplade

En anden situation, hvor vi ender med at skrive kode, som vi skal vedligeholde, er når vi genererer toString (), lige med() og hashCode () metoder. IDE'er prøver at hjælpe med skabeloner til autogenerering af disse med hensyn til vores klasseattributter.

Vi kan automatisere dette ved hjælp af andre kommentarer fra Lombok-klasseniveau:

  • @ToString: genererer en toString () metode inklusive alle klasseattributter. Ingen grund til at skrive en selv og vedligeholde den, når vi beriger vores datamodel.
  • @EqualsAndHashCode: genererer begge dele lige med() og hashCode () metoder som standard i betragtning af alle relevante felter og i henhold til meget godt selvom semantik.

Disse generatorer leverer meget praktiske konfigurationsmuligheder. For eksempel, hvis dine kommenterede klasser deltager i et hierarki, kan du bare bruge callSuper = sandt parameter- og overordnede resultater vil blive overvejet, når man genererer metodens kode.

Mere om dette: sig, at vi havde vores Bruger JPA-enhedseksempel inkluderer en henvisning til begivenheder, der er knyttet til denne bruger:

@OneToMany (mappedBy = "bruger") private begivenheder på listen;

Vi vil ikke have dumpet hele listen over begivenheder, når vi kalder toString () metode til vores bruger, bare fordi vi brugte @ToString kommentar. Intet problem: parametriser det bare sådan her: @ToString (ekskluder = {“begivenheder”}), og det sker ikke. Dette er også nyttigt for at undgå cirkulære referencer, hvis f.eks. UserEvents havde en henvisning til en Bruger.

Til LoginResult for eksempel vil vi måske definere ligestilling og hashkodeberegning bare med hensyn til selve tokenet og ikke de andre endelige attributter i vores klasse. Derefter skal du bare skrive noget lignende @EqualsAndHashCode (af = {“authToken”}).

Bonus: hvis du kunne lide funktionerne fra de kommentarer, vi hidtil har gennemgået, kan du måske undersøge dem @Data og @Værdi bemærkninger, da de opfører sig som om et sæt af dem var blevet anvendt på vores klasser. Når alt kommer til alt er disse diskuterede anvendelser meget almindeligt sammensat i mange tilfælde.

5.1. (Ikke) Brug af @EqualsAndHashCode Med JPA-enheder

Om standard skal bruges lige med() og hashCode () metoder eller oprette brugerdefinerede dem til JPA-enhederne, er et ofte diskuteret emne blandt udviklere. Der er flere tilgange, vi kan følge; hver har sine fordele og ulemper.

Som standard, @EqualsAndHashCode inkluderer alle ikke-endelige egenskaber for enhedsklassen. Vi kan prøve at "rette" dette ved at bruge onlyExplicitlyIncluded attribut for @EqualsAndHashCode for at få Lombok til kun at bruge enhedens primære nøgle. Stadig dog genereret lige med() metode kan forårsage nogle problemer. Thorben Janssen forklarer dette scenarie mere detaljeret i et af sine blogindlæg.

Generelt, vi bør undgå at bruge Lombok til at generere lige med() og hashCode () metoder til vores JPA-enheder!

6. Byggemønsteret

Følgende kan skabe en eksempelkonfigurationsklasse for en REST API-klient:

offentlig klasse ApiClientConfiguration {privat streng vært; privat int port; privat boolsk brugHttps; privat lang connectTimeout; privat lang readTimeout; privat streng brugernavn; privat strengadgangskode; // Uanset hvilke andre muligheder du måtte have. // Tom konstruktør? Alle kombinationer? // getters ... og setters? }

Vi kunne have en indledende tilgang baseret på at bruge klassens standard tomme konstruktør og levere settermetoder til hvert felt. Vi vil dog helst have, at konfigurationer ikke gentagessæt når de først er blevet bygget (instantieret), hvilket effektivt gør dem uforanderlige. Vi vil derfor undgå settere, men at skrive en sådan potentielt lang args-konstruktør er et antimønster.

I stedet kan vi fortælle værktøjet at generere en Bygger mønster, der forhindrer os i at skrive et ekstra Bygger klasse og tilknyttede flydende setterlignende metoder ved blot at tilføje @Builder-kommentaren til vores ApiClientConfiguration.

@Builder public class ApiClientConfiguration {// ... alt andet forbliver det samme}

Forlader klassedefinitionen ovenfor som sådan (ingen erklærer konstruktører eller setter + @Bygger) vi kan ende med at bruge det som:

ApiClientConfiguration config = ApiClientConfiguration.builder () .host ("api.server.com") .port (443). UseHttps (true) .connectTimeout (15_000L) .readTimeout (5_000L). Brugernavn ("mit brugernavn"). Kodeord (" hemmelighed ") .build ();

7. Kontrollerede undtagelsesbyrder

Masser af Java API'er er designet, så de kan kaste et antal afkrydsede undtagelser, klientkode tvinges til enten fangst eller erklære til kaster. Hvor mange gange har du forvandlet disse undtagelser, som du ved, ikke vil ske til noget lignende?

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")); } fange (IOException | Ikke-understøttetCharsetException ex) {// Hvis dette nogensinde sker, er det en fejl. smid ny RuntimeException (ex); <--- indkapsles til en Runtime-eks. }}

Hvis du vil undgå dette kodemønster, fordi kompilatoren ellers ikke vil være lykkelig (og trods alt dig ved godt de kontrollerede fejl kan ikke ske), skal du bruge det passende navn @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")); returnere br.lines (). indsamle (Collectors.joining ("\ n")); }}

8. Sørg for, at dine ressourcer frigives

Java 7 introducerede forsøg-med-ressourceblokken for at sikre, at dine ressourcer opbevares af forekomster af noget, der implementeres java.lang.Kan lukkes automatisk frigives ved afslutning.

Lombok giver en alternativ måde at opnå dette på og mere fleksibelt via @Cleanup. Brug den til enhver lokal variabel, hvis ressourcer du vil sikre, at frigives. Intet behov for dem at implementere en bestemt grænseflade, du får bare dens tæt() metode kaldet.

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

Din frigivelsesmetode har et andet navn? Intet problem, bare tilpas kommentaren:

@Cleanup ("disponere") JFrame mainFrame = ny JFrame ("Hovedvindue");

9. Kommenter din klasse for at få en logger

Mange af os tilføjer loggeerklæringer til vores kode sparsomt ved at oprette en forekomst af en Logger fra vores valgramme. Sig, SLF4J:

offentlig klasse ApiClientConfiguration {privat statisk logger LOG = LoggerFactory.getLogger (ApiClientConfiguration.class); // LOG.debug (), LOG.info (), ...}

Dette er et så almindeligt mønster, at Lombok-udviklere har plejet at forenkle det for os:

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

Mange logningsrammer understøttes, og selvfølgelig kan du tilpasse forekomstens navn, emne osv.

10. Skriv trådsikre metoder

I Java kan du bruge synkroniseret nøgleord til implementering af kritiske sektioner. Dette er dog ikke en 100% sikker tilgang: anden klientkode kan i sidste ende også synkroniseres på din forekomst, hvilket potentielt kan føre til uventede blokeringer.

Det er her @Synchronized kommer ind: kommenter dine metoder (både forekomst og statisk) med det, og du får et autogenereret privat, ueksponeret felt, som din implementering vil bruge til at låse:

@Synchronized public / * better than: synchronized * / void putValueInCache (String key, Object value) {// uanset hvad der her vil være trådsikker kode}

11. Automatiser objektsammensætning

Java har ikke sprogniveaukonstruktioner til at udjævne en "favoritkompositionsarv" -tilgang. Andre sprog har indbyggede begreber som f.eks Træk eller Mixins for at opnå dette.

Lomboks @Delegate er meget praktisk, når du vil bruge dette programmeringsmønster. Lad os overveje et eksempel:

  • Vi vil have Brugers og Kundes for at dele nogle almindelige attributter til navngivning og telefonnummer
  • Vi definerer både en grænseflade og en adapterklasse til disse felter
  • Vi får vores modeller til at implementere grænsefladen og @Delegeret til deres adapter effektivt komponere dem med vores kontaktoplysninger

Lad os først definere en grænseflade:

offentlig grænseflade HasContactInformation {String getFirstName (); ugyldigt setFirstName (streng fornavn); String getFullName (); String getLastName (); ugyldigt setLastName (streng efternavn); String getPhoneNr (); ugyldigt sætPhoneNr (streng telefonNr); }

Og nu en adapter som en support klasse:

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

Den interessante del kommer nu, se hvor let det er nu at komponere kontaktoplysninger i begge modelklasser:

offentlig klasse Bruger implementerer HasContactInformation {// Hvilke andre brugerspecifikke attributter @Delegate (typer = {HasContactInformation.class}) privat endelig ContactInformationSupport contactInformation = ny ContactInformationSupport (); // Brugeren selv implementerer alle kontaktoplysninger ved delegation}

Sagen for Kunde ville være så ens, at vi ville udelade prøven for kortfattethed.

12. Rullende Lombok Back?

Kort svar: Slet ikke rigtig.

Du er måske bekymret for, at der er en chance for, at du bruger Lombok i et af dine projekter, men senere ønsker at tilbageføre denne beslutning. Du ville så få et måske stort antal klasser kommenteret til det ... hvad kunne du gøre?

Jeg har aldrig rigtig fortrudt dette, men hvem ved for dig, dit team eller din organisation. I disse tilfælde er du dækket takket være delombok værktøj fra det samme projekt.

Ved delombok-ing din kode ville du få autogenereret Java-kildekode med nøjagtigt de samme funktioner fra bytecode Lombok bygget. Så så kan du bare erstatte din originale kommenterede kode med disse nye delombokeret filer og afhænger ikke længere af det.

Dette er noget, du kan integrere i din build, og jeg har gjort det tidligere for bare at studere den genererede kode eller at integrere Lombok med et andet Java-kildekodebaseret værktøj.

13. Konklusion

Der er nogle andre funktioner, som vi ikke har præsenteret i denne artikel, jeg vil opfordre dig til at gå dybere ned i funktionsoversigten for at få flere detaljer og brugssager.

Også de fleste funktioner, vi har vist, har en række tilpasningsmuligheder, du kan finde praktisk for at få værktøjet til at generere ting, der er mest kompatible med dit teams praksis for navngivning osv. Det tilgængelige indbyggede konfigurationssystem kan også hjælpe dig med det.

Jeg håber, du har fundet motivationen til at give Lombok en chance for at komme ind i dit Java-udviklingsværktøjssæt. Prøv det og øg din produktivitet!

Eksempelkoden findes i GitHub-projektet.