Uvod u projekt Lombok

1. Izbjegavajte ponavljajući kod

Java je izvrstan jezik, ali ponekad postaje previše opširan za stvari koje morate učiniti u svom kodu za uobičajene zadatke ili usklađenost s nekim okvirnim praksama. Oni vrlo često ne donose stvarnu vrijednost poslovnoj strani vaših programa - i ovdje je Lombok ovdje kako bi vaš život učinio sretnijim, a sebe produktivnijim.

Način na koji to radi je tako što se uključi u vaš postupak gradnje i automatski generira Java bajt kod u vaš .razred datoteke prema brojnim napomenama o projektu koje unesete u svoj kod.

Uključivanje u vaše gradnje, koji god sustav da koristite, vrlo je jednostavno. Njihova stranica projekta sadrži detaljne upute o pojedinostima. Većina mojih projekata temelje se na Mavenu, tako da obično ukažem na njihovu ovisnost pod uvjetom opseg i dobro je krenuti:

 ... org.projectlombok lombok 1.18.10 pod uvjetom ... 

Ovdje potražite najnoviju dostupnu verziju.

Imajte na umu da ovisno o Lomboku korisnici neće učiniti vašim .jarOvisi i o njemu, jer je to čista ovisnost o gradnji, a ne vrijeme izvođenja.

2. Dobavljači / postavljači, konstruktori - tako se ponavljaju

Inkapsuliranje svojstava objekata putem javnih metoda dobivanja i postavljanja tako je uobičajena praksa u svijetu Jave, a puno se okvira opsežno oslanja na ovaj obrazac "Java Bean": klasa s praznim konstruktorom i get / set metode za "svojstva".

To je toliko uobičajeno da većina IDE-a podržava automatsko generiranje koda za ove uzorke (i više). Međutim, ovaj kôd mora živjeti u vašim izvorima i također ga treba održavati kada se, recimo, doda novo svojstvo ili se preimenuje polje.

Razmotrimo kao primjer ovu klasu koju želimo koristiti kao JPA entitet:

Korisnik javne klase @Entity implementira Serializable {private @Id Long id; // postavit će se kod trajnog privatnog niza firstName; private String lastName; privatno int doba; javni korisnik () {} javni korisnik (niz firstName, string lastName, int age) {this.firstName = firstName; this.lastName = lastName; this.age = dob; } // getteri i postavljači: ~ 30 dodatnih redaka koda}

Ovo je prilično jednostavna klasa, ali ipak razmislite ako bismo dodali dodatni kôd za getere i postavljače, dobili bismo definiciju u kojoj bismo imali više uzorka nultog koda od relevantnih poslovnih podataka: „Korisnik prvo ima i prezimena i dob. "

Pustite nas sada Lombok-ize ovaj sat:

@Entity @Getter @Setter @NoArgsConstructor // <--- OVO je li to javna klasa Korisnik implementira Serializable {private @Id Long id; // postavit će se kod trajnog privatnog niza firstName; private String lastName; privatno int doba; javni korisnik (String firstName, String lastName, int age) {this.firstName = firstName; this.lastName = lastName; this.age = dob; }}

Dodavanjem @ Dobij i @Seter napomene koje smo rekli Lomboku da ih, pa, generira za sva polja klase. @NoArgsConstructor dovest će do praznog stvaranja konstruktora.

Imajte na umu da je ovo cijela koda klase, ne izostavljam ništa za razliku od gornje verzije s // geteri i postavljači komentar. Za tri relevantne klase atributa ovo je značajna ušteda u kodu!

Ako dalje dodate atribute (svojstva) u svoj Korisnik klase, dogodit će se isto: primijenili ste napomene na sam tip, tako da će prema zadanim postavkama imati na umu sva polja.

Što ako želite poboljšati vidljivost nekih svojstava? Na primjer, volim zadržati svoje entitete iskaznica modifikatori polja paket ili zaštićen vidljivi jer se očekuje da će se čitati, ali nisu izričito postavljeni kodom aplikacije. Samo upotrijebite sitnije zrnaste @Seter za ovo određeno područje:

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

3. Lijeni Getter

Često aplikacije trebaju izvršiti neku skupu operaciju i spremiti rezultate za naknadnu upotrebu.

Na primjer, recimo da moramo čitati statičke podatke iz datoteke ili baze podataka. Općenito je dobra praksa jednom doći do tih podataka, a zatim ih predmemorirati kako bi se omogućilo čitanje u memoriji unutar aplikacije. To štedi aplikaciju od ponavljanja skupe operacije.

Drugi uobičajeni obrazac je da dohvatite ove podatke samo kad su prvi put potrebni. Drugim riječima, podatke dobiti samo kad se odgovarajući getter pozove prvi put. Ovo se zove lijeno natovarivanje.

Pretpostavimo da su ti podaci predmemorirani kao polje unutar klase. Klasa sada mora biti sigurna da svaki pristup ovom polju vraća predmemorirane podatke. Jedan od mogućih načina implementacije takve klase je natjerati getter metodu da preuzme podatke samo ako je polje null. Zbog ovog razloga, ovo nazivamo a lijen getter.

Lombok to omogućuje s lijen parametar u @Getter bilješka vidjeli smo gore.

Na primjer, razmotrite ovu jednostavnu klasu:

javna klasa GetterLazy {@Getter (lijeni = istiniti) privatni konačni Transakcije karte = getTransactions (); privatna karta getTransactions () {konačna predmemorija karte = nova HashMap (); Popis txnRows = readTxnListFromFile (); txnRows.forEach (s -> {String [] txnIdValueTuple = s.split (DELIMETER); cache.put (txnIdValueTuple [0], Long.parseLong (txnIdValueTuple [1]));}); povratna predmemorija; }}

Ovo čita neke transakcije iz datoteke u Karta. Budući da se podaci u datoteci ne mijenjaju, jednom ćemo ih predmemorirati i omogućiti pristup putem getera.

Ako sada pogledamo kompajlirani kod ove klase, vidjet ćemo a getter metoda koja ažurira predmemoriju ako jest null a zatim vraća predmemorirane podatke:

javna klasa GetterLazy {privatni konačni AtomicReference transakcije = novi AtomicReference (); public GetterLazy () {} // ostale metode public Map getTransactions () {Vrijednost objekta = this.transactions.get (); if (value == null) {sinkronizirano (this.transaction) {value = this.transactions.get (); if (value == null) {Map actualValue = this.readTxnsFromFile (); vrijednost = stvarna vrijednost == null? this.transaction: actualValue; this.transaction.set (value); }}} return (Map) ((Map) (value == this.transaction? null: value)); }}

Zanimljivo je to istaknuti Lombok je umotao polje podataka u AtomicReference.To osigurava atomska ažuriranja transakcije polje. The getTransactions () metoda također osigurava čitanje datoteke ako transakcije je null.

Korištenje Transakcije AtomicReference obeshrabruje se polje izravno iz razreda. Preporuča se koristiti getTransactions () metoda za pristup polju.

Iz tog razloga, ako koristimo drugu Lombok napomenu poput ToString u istom razredu, upotrijebit će getTransactions () umjesto izravnog pristupa polju.

4. Vrijednosne klase / DTO

Mnogo je situacija u kojima želimo definirati vrstu podataka s jedinom svrhom predstavljanja složenih „vrijednosti“ ili kao „Objekti za prijenos podataka“, većinu vremena u obliku nepromjenjivih struktura podataka koje jednom gradimo i koje nikada ne želimo mijenjati .

Dizajniramo klasu koja predstavlja uspješnu operaciju prijave. Želimo da sva polja nisu null, a objekti nepromjenjivi kako bismo mogli sigurno pristupiti njegovim svojstvima:

javna klasa LoginResult {privatni konačni Instant loginTs; privatni final String authToken; privatno konačno Trajanje tokenValidity; privatni završni URL tokenRefreshUrl; // konstruktor uzima svako polje i provjerava nule // pristup samo za čitanje, ne nužno kao obrazac get * ()}

Opet, količina koda koju bismo morali napisati za komentirane odjeljke bila bi puno veća od podataka koje želimo uvrstiti i koji za nas imaju stvarnu vrijednost. Možemo ponovno upotrijebiti Lombok da ovo poboljšamo:

@RequiredArgsConstructor @Accessors (fluent = true) @Getter javna klasa LoginResult {private final @NonNull Instant loginTs; privatni konačni @NonNull String authToken; privatno finale @NonNull Trajanje tokenValidity; privatni konačni @NonNull URL tokenRefreshUrl; }

Samo dodajte @RequiredArgsConstructor napomena i dobili biste konstruktor za sva završna polja u klasi, baš onako kako ste ih deklarirali. Dodavanje @NonNull to attributes čini naš konstruktor provjerom nullbilnosti i bacanja NullPointerExceptions prema tome. To bi se također dogodilo da polja nisu konačna, a mi bismo dodali @Seter za njih.

Zar ne želiš dosadno staro dobiti*() obrazac za vaše nekretnine? Jer smo dodali @ Pristupnici (tečno = točno) u ovom primjeru "getteri" bi imali isto ime metode kao i svojstva: getAuthToken () jednostavno postaje authToken ().

Ovaj "tečni" obrazac primjenjivao bi se na ne-konačna polja za postavljače atributa i omogućio bi lančane pozive:

// Zamislite da polja više nisu konačna, sada vraćaju novi LoginResult () .loginTs (Instant.now ()) .authToken ("asdasd"). // i tako dalje

5. Jezgra Java Boilerplate

Još jedna situacija u kojoj na kraju pišemo kod koji moramo održavati je prilikom generiranja toString (), jednako () i hashCode () metode. IDE-ovi pokušavaju pomoći s predlošcima za njihovo automatsko generiranje u smislu atributa naše klase.

To možemo automatizirati pomoću drugih bilješki na razini klase Lombok:

  • @ToString: generirat će a toString () metoda koja uključuje sve atribute klase. Nema potrebe da sami napišemo jedan i održavamo ga dok obogaćujemo naš model podataka.
  • @EqualsAndHashCode: generirat će oboje jednako () i hashCode () metode prema zadanim postavkama uzimajući u obzir sva relevantna polja i prema vrlo dobroj iako semantici.

Ovi generatori isporučuju vrlo praktične mogućnosti konfiguracije. Na primjer, ako vaše označene klase sudjeluju u hijerarhiji, možete jednostavno koristiti callSuper = true Rezultati parametra i roditelja bit će uzeti u obzir prilikom generiranja koda metode.

Više o ovome: recimo da smo imali svoje Korisnik Primjer JPA entiteta uključuje referencu na događaje povezane s ovim korisnikom:

@OneToMany (mappedBy = "user") privatni događaji s popisa;

Ne bismo voljeli da se cijeli popis događaja baci kad god nazovemo toString () metoda našeg korisnika, samo zato što smo koristili @ToString bilješka. Nema problema: samo parameterizirajte ovako: @ToString (izuzeti = {"događaji"}), a to se neće dogoditi. Ovo je također korisno za izbjegavanje kružnih referenci ako, na primjer, UserEvents imali referencu na Korisnik.

Za Rezultat prijave na primjer, možda ćemo htjeti definirati jednakost i izračun hash koda samo u smislu samog tokena, a ne ostalih konačnih atributa u našoj klasi. Zatim jednostavno napišite nešto poput @EqualsAndHashCode (od = {"authToken"}).

Bonus: ako su vam se svidjele značajke napomena koje smo do sada pregledali, možda ćete ih htjeti ispitati @Podaci i @Vrijednost bilješke dok se ponašaju kao da je skup njih primijenjen na naše predmete. Napokon, ove se rasprave o običajima vrlo često sastavljaju u mnogim slučajevima.

5.1. (Ne) Korištenje @EqualsAndHashCode S JPA entitetima

Treba li koristiti zadani jednako () i hashCode () metode ili stvoriti prilagođene za JPA entitete, tema je o kojoj se često raspravlja među programerima. Postoji više pristupa koje možemo slijediti; svaki ima svoje prednosti i nedostatke.

Prema zadanim postavkama, @EqualsAndHashCode uključuje sva ne-konačna svojstva klase entiteta. To možemo pokušati "popraviti" pomoću onlyExplicitlyIncluded atribut @EqualsAndHashCode kako bi Lombok koristio samo primarni ključ entiteta. Ipak, međutim, generirano jednako () metoda može uzrokovati neke probleme. Thorben Janssen detaljnije objašnjava ovaj scenarij u jednom od svojih postova na blogu.

Općenito, trebali bismo izbjegavati upotrebu Lomboka za generiranje jednako () i hashCode () metode za naše JPA entitete!

6. Uzorak graditelja

Sljedeće bi moglo napraviti uzorak klase konfiguracije za REST API klijenta:

javna klasa ApiClientConfiguration {host privatnog niza; privatna int luka; privatna logička upotrebaHttps; privatni long connectTimeout; privatno dugo čitanje vremena; privatno korisničko ime niza; privatna lozinka za niz; // Bez obzira na druge mogućnosti. // Prazan konstruktor? Sve kombinacije? // getteri ... i setteri? }

Mogli bismo imati inicijalni pristup zasnovan na korištenju zadanog praznog konstruktora klase i pružanju metoda postavljača za svako polje. Međutim, idealno bi bilo da se konfiguracije ne preispitujupostavljen nakon što su izgrađeni (instancirani), što ih čini nepromjenjivima. Stoga želimo izbjeći postavljače, ali pisanje tako potencijalno dugog args konstruktora je anti-obrazac.

Umjesto toga, možemo reći alatu da generira graditelj obrazac, sprečavajući nas da napišemo dodatak Graditelj klase i pridružene metode poput fluentnog postavljača jednostavnim dodavanjem bilješke @Builder u našu ApiClientConfiguration.

@Builder javna klasa ApiClientConfiguration {// ... sve ostalo ostaje isto}

Ostavljajući gornju definiciju klase kao takvu (bez deklariranja konstruktora niti postavljača + @Graditelj) možemo ga na kraju koristiti kao:

ApiClientConfiguration config = ApiClientConfiguration.builder () .host ("api.server.com") .port (443) .useHttps (true) .connectTimeout (15_000L) .readTimeout (5_000L) .username ("myusername") .password (" tajna ") .build ();

7. Provjereno opterećenje za iznimke

Mnogo Java API-ja je dizajnirano tako da mogu baciti brojne provjerene iznimke, a klijentski kod prisiljen je na bilo koji ulov ili izjaviti da baca. Koliko ste puta ove iznimke pretvorili u to da znate da se neće dogoditi u ovako nešto?

javni String resourceAsString () {try (InputStream is = this.getClass (). getResourceAsStream ("sure_in_my_jar.txt")) {BufferedReader br = new BufferedReader (novi InputStreamReader (je, "UTF-8")); return br.lines (). collect (Collectors.joining ("\ n")); } catch (IOException | UnsupportedCharsetException ex) {// Ako se to ikad dogodi, to je bug. baciti novi RuntimeException (ex); <--- enkapsulirati u runtime ex. }}

Ako želite izbjeći ovaj obrazac koda, jer prevoditelj inače neće biti sretan (i, uostalom, vi znati provjerene pogreške ne mogu dogoditi), upotrijebite odgovarajuće imenovane @SneakyThrows:

@SneakyThrows javni niz resourceAsString () {try (InputStream is = this.getClass (). GetResourceAsStream ("sure_in_my_jar.txt")) {BufferedReader br = new BufferedReader (novi InputStreamReader (je,); UTF-8 " return br.lines (). collect (Collectors.joining ("\ n")); }}

8. Osigurajte da su vaši resursi pušteni

Java 7 je uvela blok try-with-resources kako bi osigurala da vaše resurse drže instance bilo čega implementiranog java.lang.AutoCloseable se puštaju prilikom izlaska.

Lombok nudi alternativni način da se to postigne i fleksibilnije putem @Cleanup. Koristite ga za bilo koju lokalnu varijablu za čije resurse želite biti sigurni da su objavljeni. Nema potrebe da implementiraju neko određeno sučelje, samo ćete ga dobiti Zatvoriti() metoda nazvana.

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

Vaš način izdavanja ima drugo ime? Nema problema, samo prilagodite napomenu:

@Cleanup ("dispose") JFrame mainFrame = novi JFrame ("Glavni prozor");

9. Označite svoj razred da biste dobili drvosječu

Mnogi od nas u svoj kôd štedljivo dodaju zapisnike za bilježenje stvaranjem instance a Drvosječa iz našeg okvira izbora. Recimo, SLF4J:

javna klasa ApiClientConfiguration {privatni statički zapisnik LOG = LoggerFactory.getLogger (ApiClientConfiguration.class); // LOG.debug (), LOG.info (), ...}

Ovo je tako čest obrazac da su se programeri Lomboka brinuli da nam ga pojednostave:

@ Slf4j // ili: @Log @CommonsLog @ Log4j @ Log4j2 @ XSlf4j javna klasa ApiClientConfiguration {// log.debug (), log.info (), ...}

Podržani su mnogi okviri za evidentiranje i naravno možete prilagoditi naziv instance, temu itd.

10. Napišite metode sigurnije u navoju

U Javi možete koristiti sinkronizirano ključna riječ za implementaciju kritičnih odjeljaka. Međutim, ovo nije 100% siguran pristup: drugi klijentski kôd na kraju se također može sinkronizirati na vašoj instanci, što potencijalno dovodi do neočekivanih mrtvih točaka.

Ovo je gdje @ Sinkronizirano dolazi: označite njime svoje metode (i instance i statičke) i dobit ćete automatski generirano privatno, neeksponirano polje koje će vaša implementacija koristiti za zaključavanje:

@Sinhronizirani javni / * bolji od: sinkronizirani * / void putValueInCache (ključ niza, vrijednost objekta) {// sve što će ovdje biti kod zaštićen nitima}

11. Automatizirajte sastav objekata

Java nema konstrukcije na razini jezika kako bi izgladila pristup "favoriziranja nasljeđivanja kompozicije". Ostali jezici imaju ugrađene koncepte kao što su Osobine ili Mixini da se to postigne.

Lombokov @Delegate vrlo dobro dolazi kada želite koristiti ovaj obrazac programiranja. Razmotrimo primjer:

  • Mi želimo Korisniks i Kupacs da biste podijelili neke uobičajene atribute za imenovanje i telefonski broj
  • Za ta polja definiramo i sučelje i klasu adaptora
  • Naši modeli će implementirati sučelje i @Delegat na njihov adapter, učinkovito skladanje ih s našim kontakt podacima

Prvo definirajmo sučelje:

javno sučelje HasContactInformation {String getFirstName (); void setFirstName (String firstName); Niz getFullName (); Niz getLastName (); void setLastName (Niz lastName); Niz getPhoneNr (); void setPhoneNr (String phoneNr); }

A sada adapter kao podrška razred:

@Data javna klasa ContactInformationSupport implementira HasContactInformation {private String firstName; private String lastName; private String phoneNr; @Override javni niz getFullName () {return getFirstName () + "" + getLastName (); }}

Zanimljivi dio dolazi sada, pogledajte kako je lako sada sastaviti podatke za kontakt u obje klase modela:

javna klasa Korisnik implementira HasContactInformation {// Koji god drugi korisnički specifični atributi @Delegate (tipovi = {HasContactInformation.class}) privatni konačni ContactInformationSupport contactInformation = novi ContactInformationSupport (); // Korisnik će sam implementirati sve podatke za kontakt delegiranjem}

Slučaj za Kupac bio toliko sličan da bismo izostavili uzorak za kratkoću.

12. Vraćanje Lomboka natrag?

Kratki odgovor: Zapravo uopće nije.

Možda se brinete da postoji šansa da Lombok koristite u nekom od svojih projekata, ali kasnije želite povući tu odluku. Tada biste za to dodali možda veliki broj predavanja ... što biste mogli učiniti?

Nikad se zapravo nisam pokajao, ali tko zna za vas, vaš tim ili vašu organizaciju. Za ove slučajeve pokriveni ste zahvaljujući djelobok alat iz istog projekta.

Po djelobok-ing svoj kôd dobili biste automatski generirani Java izvorni kôd s potpuno istim značajkama iz izgrađenog bytecode-a Lombok. Pa onda možete jednostavno zamijeniti svoj izvorni označeni kod ovim novim djeloboked datoteke i više ne ovise o njemu.

To je nešto što možete integrirati u svoju gradnju, a to sam učinio u prošlosti kako bih samo proučio generirani kôd ili kako bih integrirao Lombok s nekim drugim alatom temeljenim na Java izvornom kodu.

13. Zaključak

Postoje neke druge značajke koje nismo predstavili u ovom članku, a ja bih vas savjetovao da dublje zaronite u pregled značajki za više detalja i slučajeve upotrebe.

Također većina funkcija koje smo prikazali imaju brojne mogućnosti prilagodbe koje bi vam mogle biti korisne kako bi alat generirao stvari koje su u skladu s praksama vašeg tima za imenovanje itd. Dostupni ugrađeni konfiguracijski sustav također vam može pomoći u tome.

Nadam se da ste pronašli motivaciju da Lomboku date priliku da uđe u vaš set alata za razvoj Jave. Isprobajte i povećajte svoju produktivnost!

Primjer koda može se naći u projektu GitHub.