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: 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: Den samme metoden kan også ha det andre argumentet som verdien av eiendommen, for å fange en manglende eiendoms settermetodeanrop: 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: Så i stedet for å pakke inn en metodeanrop i en prøvefangst, kan vi definere metodeMissing: 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: La oss gå videre, la oss legge til de manglende getFullName metoden til Ansatt klasseobjekt ved kjøretid: På samme måte kan vi legge til en konstruktør i Ansatt klasse ved kjøretid: 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: 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: 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: La oss bekrefte getYearOfBirth metoden lagt til i Ansatt klasse: 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: Deretter aktiverer vi Statisk medarbeiderforlengelse ved å legge til følgende konfigurasjon i ExtensionModule fil: Nå er alt vi trenger å teste våre statiskgetDefaultObj metoden på Ansatt klasse: På samme måte, ved hjelp av utvidelser kan vi legge til en metode i forhåndskompilerte Java-klasser som Heltall og Lang: 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. 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: Nå skal vi lage et objekt av Ansatt klasse og verifisere strengen som returneres av toString metode: 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: 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: Nå kan vi lage Ansatt objekt som passerer parametere i rekkefølgen på egenskaper definert i klassen. Hvis vi ikke gir verdier til egenskapene mens vi oppretter objekter, vil Groovy vurdere standardverdier: 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. 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: @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. 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: 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: Ringer til logEmp metode på en Ansatt objektet vil vise loggene på konsollen: 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. 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.Medarbeider emp = ny ansatt (fornavn: "Norman", etternavn: "Lewis") println emp.address
groovy.lang.MissingPropertyException: Ingen slik eiendom: adresse for klasse: com.baeldung.metaprogramming.Ansatt
def propertyMissing (String propertyName) {"property '$ propertyName' er ikke tilgjengelig"}
påstå emp.address == "eiendomsadresse" er ikke tilgjengelig "
def propertyMissing (String propertyName, propertyValue) {println "kan ikke angi $ propertyValue - eiendom '$ propertyName' er ikke tilgjengelig"}
3.2. metodeMissing
prøv {emp.getFullName ()} catch (MissingMethodException e) {println "metoden er ikke definert"}
def methodMissing (String methodName, def methodArgs) {"method '$ methodName' er ikke definert"}
hevder emp.getFullName () == "metoden 'getFullName' er ikke definert"
3.3. ExpandoMetaClass
Employee.metaClass.address = ""
Ansattes emp = ny ansatt (fornavn: "Norman", etternavn: "Lewis", adresse: "USA") hevder emp.address == "USA"
emp.metaClass.getFullName = {"$ lastName, $ firstName"}
hevder emp.getFullName () == "Lewis, Norman"
Employee.metaClass.constructor = {Streng fornavn -> ny ansatt (fornavn: fornavn)}
Ansatt norman = ny ansatt ("Norman") hevder norman.firstName == "Norman" hevder norman.lastName == null
String.metaClass.capitalize = {String str -> str.substring (0, 1) .toUpperCase () + str.substring (1)}
hevder "norman" .capitalize () == "Norman"
3.4. Utvidelser
class BasicExtensions {static int getYearOfBirth (Employee self) {return Year.now (). value - self.age}}
moduleName = core-groovy-2 moduleVersion = 1.0-SNAPSHOT extensionClasses = com.baeldung.metaprogramming.extension.BasicExtensions
def alder = 28 def forventetYearOfBirth = Year.now () - alder Medarbeider emp = ny ansatt (alder: alder) hevder emp.getYearOfBirth () == forventetYearOfBirth.value
klasse StaticEmployeeExtension {static Employee getDefaultObj (Employee self) {return new Employee (firstName: "firstName", lastName: "lastName", age: 20)}}
staticExtensionClasses = com.baeldung.metaprogramming.extension.StaticEmployeeExtension
hevde Employee.getDefaultObj (). firstName == "firstName" hevder Employee.getDefaultObj (). lastName == "lastName" hevder Employee.getDefaultObj (). age == 20
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
4.1. @ToString
@ToString klasse Ansatt {lang id Streng fornavn Streng etternavn int alder}
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) "
@ToString (includePackage = false, ekskluderer = ['id'])
hevder medarbeider.toString () == "Ansatt (norman, lewis, 28)"
4.2. @TupleConstructor
@TupleConstructor-klasse Ansatt {lang id Streng fornavn Streng etternavn int alder}
Ansatt norman = ny ansatt (1, "norman", "lewis", 28) hevder norman.toString () == "Medarbeider (norman, lewis, 28)"
Ansatt snape = ny ansatt (2, "snape") hevder snape.toString () == "Ansatt (snape, null, 0)"
4.3. @EqualsAndHashCode
Ansatt normanCopy = ny ansatt (1, "norman", "lewis", 28) hevder norman == normanCopy hevder norman.hashCode () == normanCopy.hashCode ()
4.4. @Canonical
4.5. @AutoClone
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
def logEmp () {log.info "Ansatt: $ lastName, $ firstName er på $ age år"}
Ansatt ansatt = ny ansatt (1, "Norman", "Lewis", 28) ansatte.logEmp ()
INFO: Ansatt: Lewis, Norman er 28 år gammel
5. Konklusjon