Metaprogrammering i Groovy

1. Oversikt

Groovy er et dynamisk og kraftig JVM-språk som har mange funksjoner som nedleggelser og egenskaper.

I denne opplæringen vil vi utforske konseptet metaprogramming i Groovy.

2. Hva er metaprogrammering?

Metaprogramming er en programmeringsteknikk for å skrive et program for å modifisere seg selv eller et annet program ved hjelp av metadata.

I Groovy er det mulig å utføre metaprogrammering både på kjøretid og kompileringstid. Fremover vil vi utforske noen bemerkelsesverdige trekk ved begge teknikkene.

3. Runtime-metaprogrammering

Runtime-metaprogrammering gjør at vi kan endre eksisterende egenskaper og metoder i en klasse. Vi kan også legge ved nye egenskaper og metoder; alt ved kjøretid.

Groovy gir noen få metoder og egenskaper som hjelper til med å endre oppførselen til en klasse ved kjøretid.

3.1. eiendomMissing

Når vi prøver å få tilgang til en udefinert eiendom fra en Groovy-klasse, kaster den a MissingPropertyException. For å unngå unntaket gir Groovy eiendomMissing metode.

La oss først skrive en Ansatt klasse med noen egenskaper:

class Employee {String firstName String lastName int age}

For det andre lager vi en Ansatt objekt og prøv å vise en udefinert eiendom adresse. Følgelig vil det kaste MissingPropertyException:

Medarbeider emp = ny ansatt (fornavn: "Norman", etternavn: "Lewis") println emp.address 
groovy.lang.MissingPropertyException: Ingen slik eiendom: adresse for klasse: com.baeldung.metaprogramming.Ansatt

Groovy sørger for eiendomMissing metode for å fange den manglende eiendomsforespørselen. Derfor kan vi unngå a MissingPropertyException ved kjøretid.

For å fange en manglende eiendoms getter-metoden, vil vi definere den med et enkelt argument for eiendomsnavnet:

def propertyMissing (String propertyName) {"property '$ propertyName' er ikke tilgjengelig"}
påstå emp.address == "eiendomsadresse" er ikke tilgjengelig "

Den samme metoden kan også ha det andre argumentet som verdien av eiendommen, for å fange en manglende eiendoms settermetodeanrop:

def propertyMissing (String propertyName, propertyValue) {println "kan ikke angi $ propertyValue - eiendom '$ propertyName' er ikke tilgjengelig"}

3.2. metodeMissing

De metodeMissing metoden ligner på eiendomMissing. Derimot, metodeMissing avlytter et kall for en hvilken som helst manglende metode, og unngår dermed MissingMethodException.

La oss prøve å kalle getFullName metode på en Ansatt gjenstand. Som getFullName mangler, vil henrettelsen kaste MissingMethodException ved kjøretid:

prøv {emp.getFullName ()} catch (MissingMethodException e) {println "metoden er ikke definert"}

Så i stedet for å pakke inn en metodeanrop i en prøvefangst, kan vi definere metodeMissing:

def methodMissing (String methodName, def methodArgs) {"method '$ methodName' er ikke definert"}
hevder emp.getFullName () == "metoden 'getFullName' er ikke definert"

3.3. ExpandoMetaClass

Groovy gir en metaklasse eiendom i alle sine klasser. De metaklasse eiendom refererer til en forekomst av ExpandoMetaClass.

De ExpandoMetaClass klasse gir mange måter å transformere en eksisterende klasse på kjøretid. For eksempel kan vi legge til egenskaper, metoder eller konstruktører.

La oss først legge til de manglende adresse eiendom til Ansatt klasse bruker metaklasse eiendom:

Employee.metaClass.address = ""
Ansattes emp = ny ansatt (fornavn: "Norman", etternavn: "Lewis", adresse: "USA") hevder emp.address == "USA"

La oss gå videre, la oss legge til de manglende getFullName metoden til Ansatt klasseobjekt ved kjøretid:

emp.metaClass.getFullName = {"$ lastName, $ firstName"}
hevder emp.getFullName () == "Lewis, Norman"

På samme måte kan vi legge til en konstruktør i Ansatt klasse ved kjøretid:

Employee.metaClass.constructor = {Streng fornavn -> ny ansatt (fornavn: fornavn)}
Ansatt norman = ny ansatt ("Norman") hevder norman.firstName == "Norman" hevder norman.lastName == null

På samme måte kan vi legge til statisk metoder ved hjelp av metaClass.static.

De metaklasse egenskapen er ikke bare nyttig for å endre brukerdefinerte klasser, men også eksisterende Java-klasser ved kjøretid.

La oss for eksempel legge til en kapitalisere metoden til String klasse:

String.metaClass.capitalize = {String str -> str.substring (0, 1) .toUpperCase () + str.substring (1)}
hevder "norman" .capitalize () == "Norman"

3.4. Utvidelser

En utvidelse kan legge til en metode i en klasse ved kjøretid og gjøre den tilgjengelig globalt.

Metodene som er definert i en utvidelse skal alltid være statiske, med selv- klasseobjekt som det første argumentet.

La oss for eksempel skrive en BasicExtension klasse for å legge til en getYearOfBirth metoden til Ansatt klasse:

class BasicExtensions {static int getYearOfBirth (Employee self) {return Year.now (). value - self.age}}

For å aktivere BasicExtensions, må vi legge til konfigurasjonsfilen i META-INF / tjenester katalog over prosjektet vårt.

Så la oss legge til org.codehaus.groovy.runtime.ExtensionModule fil med følgende konfigurasjon:

moduleName = core-groovy-2 moduleVersion = 1.0-SNAPSHOT extensionClasses = com.baeldung.metaprogramming.extension.BasicExtensions

La oss bekrefte getYearOfBirth metoden lagt til i Ansatt klasse:

def alder = 28 def forventetYearOfBirth = Year.now () - alder Medarbeider emp = ny ansatt (alder: alder) hevder emp.getYearOfBirth () == forventetYearOfBirth.value

Tilsvarende å legge til statisk metoder i en klasse, må vi definere en egen utvidelsesklasse.

La oss for eksempel legge til en statisk metode getDefaultObj til vår Ansatt klasse ved å definere Statisk medarbeiderforlengelse klasse:

klasse StaticEmployeeExtension {static Employee getDefaultObj (Employee self) {return new Employee (firstName: "firstName", lastName: "lastName", age: 20)}}

Deretter aktiverer vi Statisk medarbeiderforlengelse ved å legge til følgende konfigurasjon i ExtensionModule fil:

staticExtensionClasses = com.baeldung.metaprogramming.extension.StaticEmployeeExtension

Nå er alt vi trenger å teste våre statiskgetDefaultObj metoden på Ansatt klasse:

hevde Employee.getDefaultObj (). firstName == "firstName" hevder Employee.getDefaultObj (). lastName == "lastName" hevder Employee.getDefaultObj (). age == 20

På samme måte, ved hjelp av utvidelser kan vi legge til en metode i forhåndskompilerte Java-klasser som Heltall og Lang:

public static void printCounter (Integer self) {while (self> 0) {println self self -} return self} assert 5.printCounter () == 0 
offentlig statisk Langt kvadrat (Langt selv) {retur selv * selv} hevder 40l. kvadrat () == 1600l 

4. Kompileringstidsmetaprogrammering

Ved å bruke spesifikke merknader kan vi enkelt endre klassestrukturen på kompileringstid. Med andre ord, vi kan bruke merknader for å endre det abstrakte syntaks-treet til klassen ved kompilering.

La oss diskutere noen av kommentarene som er ganske nyttige i Groovy for å redusere kjeleplatekoden. Mange av dem er tilgjengelige i groovy.transform pakke.

Hvis vi nøye analyserer, vil vi innse at noen få kommentarer gir funksjoner som ligner på Java's Project Lombok.

4.1. @ToString

De @ToString kommentar legger til en standardimplementering av toString metode til en klasse på kompileringstid. Alt vi trenger er å legge til merknaden i klassen.

La oss for eksempel legge til @ToString kommentar til vår Ansatt klasse:

@ToString klasse Ansatt {lang id Streng fornavn Streng etternavn int alder}

Nå skal vi lage et objekt av Ansatt klasse og verifisere strengen som returneres av toString metode:

Ansatt ansatt = ny ansatt () ansatt.id = 1 ansatt.firstName = "norman" ansatt.lastnavn = "lewis" ansatt.alder = 28 hevder ansatt.toString () == "com.baeldung.metaprogramming.Ansatt (1, norman, lewis, 28) "

Vi kan også erklære parametere som ekskluderer, inkluderer, inkludererPakke og ignorere Nulls med @ToString for å endre utgangsstrengen.

La oss for eksempel ekskludere id og pakke fra strengen til medarbeiderobjektet:

@ToString (includePackage = false, ekskluderer = ['id'])
hevder medarbeider.toString () == "Ansatt (norman, lewis, 28)"

4.2. @TupleConstructor

Bruk @TupleConstructor i Groovy for å legge til en parameterisert konstruktør i klassen. Denne kommentaren oppretter en konstruktør med en parameter for hver eiendom.

La oss for eksempel legge til @TupleConstructor til Ansatt klasse:

@TupleConstructor-klasse Ansatt {lang id Streng fornavn Streng etternavn int alder}

Nå kan vi lage Ansatt objekt som passerer parametere i rekkefølgen på egenskaper definert i klassen.

Ansatt norman = ny ansatt (1, "norman", "lewis", 28) hevder norman.toString () == "Medarbeider (norman, lewis, 28)" 

Hvis vi ikke gir verdier til egenskapene mens vi oppretter objekter, vil Groovy vurdere standardverdier:

Ansatt snape = ny ansatt (2, "snape") hevder snape.toString () == "Ansatt (snape, null, 0)"

Lik @ToString, kan vi erklære parametere som ekskluderer, inkluderer og includeSuperProperties med @TupleConstructor for å endre oppførselen til den tilknyttede konstruktøren etter behov.

4.3. @EqualsAndHashCode

Vi kan bruke @EqualsAndHashCode for å generere standardimplementeringen av er lik og hashCode metoder på kompileringstidspunktet.

La oss verifisere oppførselen til @EqualsAndHashCode ved å legge den til Ansatt klasse:

Ansatt normanCopy = ny ansatt (1, "norman", "lewis", 28) hevder norman == normanCopy hevder norman.hashCode () == normanCopy.hashCode ()

4.4. @Canonical

@Canonical er en kombinasjon av @ToString, @TupleConstructor, og @EqualsAndHashCode kommentarer.

Bare ved å legge den til, kan vi enkelt inkludere alle tre i en Groovy-klasse. Vi kan også erklære @Canonical med noen av de spesifikke parametrene for alle tre kommentarene.

4.5. @AutoClone

En rask og pålitelig måte å implementere på Klonbar grensesnittet er ved å legge til @AutoClone kommentar.

La oss bekrefte klone metode etter tilsetning @AutoClone til Ansatt klasse:

prøv {Employee norman = new Employee (1, "norman", "lewis", 28) def normanCopy = norman.clone () assert norman == normanCopy} catch (CloneNotSupportedException e) {e.printStackTrace ()}

4.6. Loggningsstøtte med @Log, @Commons, @ Log4j, @ Log4j2, og @ Slf4j

For å legge til loggstøtte til alle Groovy-klasser, er alt vi trenger å legge til merknader som er tilgjengelige i groovy.util.logging pakke.

La oss aktivere loggingen gitt av JDK ved å legge til @Logg kommentar til Ansatt klasse. Etterpå legger vi til logEmp metode:

def logEmp () {log.info "Ansatt: $ lastName, $ firstName er på $ age år"}

Ringer til logEmp metode på en Ansatt objektet vil vise loggene på konsollen:

Ansatt ansatt = ny ansatt (1, "Norman", "Lewis", 28) ansatte.logEmp ()
INFO: Ansatt: Lewis, Norman er 28 år gammel

Tilsvarende @Felles kommentar er tilgjengelig for å legge til støtte for logging av Apache Commons. @ Log4j er tilgjengelig for støtte for Apache Log4j 1.x logging og @ Log4j2 for Apache Log4j 2.x. Til slutt, bruk @ Slf4j for å legge til Simple Logging Facade for Java-støtte.

5. Konklusjon

I denne veiledningen har vi utforsket begrepet metaprogrammering i Groovy.

Underveis har vi sett noen bemerkelsesverdige metaprogrammeringsfunksjoner både for kjøretid og kompileringstid.

Samtidig har vi utforsket flere nyttige kommentarer som er tilgjengelige i Groovy for renere og dynamisk kode.

Som vanlig er kodeimplementeringene for denne artikkelen tilgjengelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found