Metaprogrammering i Groovy

1. Oversigt

Groovy er et dynamisk og kraftfuldt JVM-sprog, der har adskillige funktioner som lukninger og træk.

I denne vejledning undersøger vi begrebet metaprogramming i Groovy.

2. Hvad er metaprogrammering?

Metaprogramming er en programmeringsteknik til at skrive et program for at ændre sig selv eller et andet program ved hjælp af metadata.

I Groovy er det muligt at udføre metaprogrammering på både runtime og compile-time. Fremadrettet vil vi udforske et par bemærkelsesværdige funktioner i begge teknikker.

3. Metaprogrammering under kørsel

Runtime-metaprogrammering giver os mulighed for at ændre de eksisterende egenskaber og metoder i en klasse. Vi kan også vedhæfte nye egenskaber og metoder; alt sammen ved kørsel.

Groovy giver et par metoder og egenskaber, der hjælper med at ændre en klasses opførsel ved kørsel.

3.1. propertyMissing

Når vi forsøger at få adgang til en udefineret ejendom i en Groovy-klasse, kaster den en Mangler ejendomsundtagelse. For at undgå undtagelsen leverer Groovy propertyMissing metode.

Lad os først skrive en Medarbejder klasse med nogle egenskaber:

klasse Medarbejder {String firstName String lastName int age}

For det andet opretter vi en Medarbejder objekt og prøv at vise en udefineret egenskab adresse. Derfor vil det smide Mangler ejendomsundtagelse:

Medarbejder emp = ny medarbejder (fornavn: "Norman", efternavn: "Lewis") println emp.address 
groovy.lang.MissingPropertyException: Ingen sådan ejendom: adresse for klasse: com.baeldung.metaprogramming.Employee

Groovy leverer propertyMissing metode til at fange den manglende ejendomsanmodning. Derfor kan vi undgå en Mangler ejendomsundtagelse ved kørselstid.

For at fange en manglende ejendoms getter-metodeopkald definerer vi det med et enkelt argument for ejendomsnavnet:

def propertyMissing (String propertyName) {"property '$ propertyName' er ikke tilgængelig"}
hævde emp.address == "ejendomsadresse" er ikke tilgængelig "

Den samme metode kan også have det andet argument som ejendommens værdi for at fange en manglende egenskabs settermetodeopkald:

def propertyMissing (String propertyName, propertyValue) {println "kan ikke indstille $ propertyValue - ejendom '$ propertyName' er ikke tilgængelig"}

3.2. metodeMissing

Det metodeMissing metoden ligner propertyMissing. Imidlertid, metodeMissing aflytter et kald til enhver manglende metode og derved undgår MissingMethodException.

Lad os prøve at kalde getFullName metode på en Medarbejder objekt. Som getFullName mangler, vil udførelse kaste MissingMethodException ved kørselstid:

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

Så i stedet for at indpakke en metodeopkald i en prøve-fangst, kan vi definere metodeMissing:

def methodMissing (String methodName, def methodArgs) {"method '$ methodName' er ikke defineret"}
hævde emp.getFullName () == "metode 'getFullName' er ikke defineret"

3.3. ExpandoMetaClass

Groovy giver en metaklasse ejendom i alle sine klasser. Det metaklasse ejendom refererer til en forekomst af ExpandoMetaClass.

Det ExpandoMetaClass klasse giver adskillige måder at transformere en eksisterende klasse på runtime. For eksempel kan vi tilføje egenskaber, metoder eller konstruktører.

Lad os først tilføje de manglende adresse ejendom til Medarbejder klasse ved hjælp af metaklasse ejendom:

Employee.metaClass.address = ""
Medarbejder emp = ny medarbejder (fornavn: "Norman", efternavn: "Lewis", adresse: "US") hævder emp.address == "US"

Vi bevæger os videre, lad os tilføje de manglende getFullName metode til Medarbejder klasseobjekt ved kørselstid:

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

På samme måde kan vi tilføje en konstruktør til Medarbejder klasse ved runtime:

Employee.metaClass.constructor = {String firstName -> ny medarbejder (firstName: firstName)}
Medarbejder norman = ny medarbejder ("Norman") hævder norman.firstName == "Norman" hævder norman.lastName == null

Ligeledes kan vi tilføje statisk metoder ved hjælp af metaClass.static.

Det metaklasse egenskab er ikke kun praktisk til at ændre brugerdefinerede klasser, men også eksisterende Java-klasser ved kørsel.

Lad os f.eks. Tilføje en kapitalisere metode til Snor klasse:

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

3.4. Udvidelser

En udvidelse kan tilføje en metode til en klasse ved kørsel og gøre den tilgængelig globalt.

Metoderne defineret i en udvidelse skal altid være statiske med selv klasseobjekt som det første argument.

Lad os for eksempel skrive en BasicExtension klasse for at tilføje en getYearOfBirth metode til Medarbejder klasse:

klasse BasicExtensions {static int getYearOfBirth (Medarbejder selv) {return Year.now (). værdi - self.age}}

For at aktivere BasicExtensions, bliver vi nødt til at tilføje konfigurationsfilen i META-INF / tjenester katalog over vores projekt.

Så lad os tilføje org.codehaus.groovy.runtime.ExtensionModule fil med følgende konfiguration:

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

Lad os kontrollere getYearOfBirth metode tilføjet i Medarbejder klasse:

def alder = 28 def forventetYearOfBirth = Year.now () - alder Medarbejder emp = ny medarbejder (alder: alder) hævder emp.getYearOfBirth () == forventetYearOfBirth.value

Tilsvarende at tilføje statisk metoder i en klasse, skal vi definere en separat udvidelsesklasse.

Lad os f.eks. Tilføje en statisk metode getDefaultObj til vores Medarbejder klasse ved at definere Statisk medarbejderudvidelse klasse:

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

Derefter aktiverer vi Statisk medarbejderudvidelse ved at tilføje følgende konfiguration til ExtensionModule fil:

staticExtensionClasses = com.baeldung.metaprogramming.extension.StaticEmployeeExtension

Nu er alt, hvad vi har brug for, at teste vores statiskgetDefaultObj metode til Medarbejder klasse:

hævde Employee.getDefaultObj (). firstName == "firstName" hævde Employee.getDefaultObj (). lastName == "lastName" hævde Employee.getDefaultObj (). alder == 20

Tilsvarende ved hjælp af udvidelser kan vi tilføje en metode til præ-kompilerede Java-klasser synes godt om Heltal og Lang:

offentlig statisk ugyldig printCounter (heltal selv) {mens (selv> 0) {println selv selv -} returner selv} hævder 5.printCounter () == 0 
offentlig statisk Lang firkant (langt selv) {returner selv * selv} hævder 40l.square () == 1600l 

4. Kompileringstidsmetaprogrammering

Ved hjælp af specifikke kommentarer kan vi uden besvær ændre klassestrukturen på kompileringstidspunktet. Med andre ord, vi kan bruge kommentarer til at ændre klassens abstrakte syntaks-træ ved kompileringen.

Lad os diskutere nogle af kommentarerne, som er ret praktiske i Groovy for at reducere kedelpladekoden. Mange af dem er tilgængelige i groovy.transform pakke.

Hvis vi omhyggeligt analyserer, vil vi indse, at et par kommentarer indeholder funktioner, der ligner Java's Project Lombok.

4.1. @ToString

Det @ToString annotation tilføjer en standardimplementering af toString metode til en klasse ved kompileringstid. Alt, hvad vi har brug for, er at tilføje kommentaren til klassen.

Lad os f.eks. Tilføje @ToString kommentar til vores Medarbejder klasse:

@ToString klasse Medarbejder {lang id Streng fornavn Streng efternavn int alder}

Nu opretter vi et objekt af Medarbejder klasse og kontrollere den streng, der returneres af toString metode:

Medarbejdermedarbejder = ny medarbejder () medarbejder.id = 1 medarbejder.firstName = "norman" medarbejder.lastnavn = "lewis" medarbejder.alder = 28 hævder medarbejder.toString () == "com.baeldung.metaprogramming.Employee (1, Norman, Lewis, 28) "

Vi kan også erklære parametre som f.eks udelukker, inkluderer, inkludererPakke og ignorere Nulls med @ToString for at ændre outputstrengen.

Lad os for eksempel udelukke id og pakke fra strengen i medarbejderobjektet:

@ToString (includePackage = false, ekskluderer = ['id'])
hævde medarbejder.toString () == "Medarbejder (norman, lewis, 28)"

4.2. @TupleConstructor

Brug @TupleConstructor i Groovy for at tilføje en parametreret konstruktør i klassen. Denne kommentar opretter en konstruktør med en parameter for hver egenskab.

Lad os f.eks. Tilføje @TupleConstructor til Medarbejder klasse:

@TupleConstructor klasse Medarbejder {lang id Streng fornavn Streng efternavn int alder}

Nu kan vi skabe Medarbejder objekt, der passerer parametre i rækkefølgen af ​​egenskaber defineret i klassen.

Medarbejder norman = ny medarbejder (1, "norman", "lewis", 28) hævder norman.toString () == "Medarbejder (norman, lewis, 28)" 

Hvis vi ikke leverer værdier til egenskaberne, mens vi opretter objekter, vil Groovy overveje standardværdier:

Medarbejder snape = ny medarbejder (2, "snape") hævder snape.toString () == "Medarbejder (snape, null, 0)"

Svarende til @ToString, kan vi erklære parametre som f.eks udelukker, inkluderer og includeSuperProperties med @TupleConstructor for at ændre adfærden hos den tilknyttede konstruktør efter behov.

4.3. @EqualsAndHashCode

Vi kan bruge @EqualsAndHashCode for at generere standardimplementeringen af lige med og hashCode metoder på kompileringstidspunktet.

Lad os kontrollere adfærden for @EqualsAndHashCode ved at tilføje det til Medarbejder klasse:

Medarbejder normanCopy = ny medarbejder (1, "norman", "lewis", 28) hævder norman == normanCopy hævder norman.hashCode () == normanCopy.hashCode ()

4.4. @Canonical

@Canonical er en kombination af @ToString, @TupleConstructorog @EqualsAndHashCode kommentarer.

Bare ved at tilføje det kan vi nemt inkludere alle tre i en Groovy-klasse. Vi kan også erklære @Canonical med en af ​​de specifikke parametre for alle tre kommentarer.

4.5. @AutoClone

En hurtig og pålidelig måde at implementere på Klonabel interface er ved at tilføje @AutoClone kommentar.

Lad os kontrollere klon metode efter tilføjelse @AutoClone til Medarbejder klasse:

prøv {Medarbejder norman = ny medarbejder (1, "norman", "lewis", 28) def normanCopy = norman.clone () hævder norman == normanCopy} fangst (CloneNotSupportedException e) {e.printStackTrace ()}

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

For at tilføje logsupport til enhver Groovy-klasse er alt, hvad vi har brug for, at tilføje kommentarer, der er tilgængelige i groovy.util.logging pakke.

Lad os aktivere logning leveret af JDK ved at tilføje @Log kommentar til Medarbejder klasse. Derefter tilføjer vi logEmp metode:

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

Ringer til logEmp metode på en Medarbejder objektet viser logfilerne på konsollen:

Medarbejdermedarbejder = ny medarbejder (1, "Norman", "Lewis", 28) medarbejder.logEmp ()
INFO: Medarbejder: Lewis, Norman er 28 år gammel

Tilsvarende er @Commons kommentar er tilgængelig for at tilføje Apache Commons logning support. @ Log4j er tilgængelig til Apache Log4j 1.x logning support og @ Log4j2 til Apache Log4j 2.x. Endelig skal du bruge @ Slf4j for at tilføje Simple Logging Facade til Java-support.

5. Konklusion

I denne vejledning har vi udforsket begrebet metaprogrammering i Groovy.

Undervejs har vi set et par bemærkelsesværdige metaprogrammeringsfunktioner både til runtime og kompileringstid.

På samme tid har vi undersøgt yderligere praktiske kommentarer, der er tilgængelige i Groovy for renere og dynamisk kode.

Som normalt er kodeimplementeringerne til denne artikel tilgængelige på GitHub.


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