Metaprogrammering in Groovy

1. Overzicht

Groovy is een dynamische en krachtige JVM-taal die tal van functies heeft, zoals sluitingen en eigenschappen.

In deze tutorial verkennen we het concept van metaprogrammering in Groovy.

2. Wat is metaprogrammering?

Metaprogrammering is een programmeertechniek waarbij een programma wordt geschreven om zichzelf of een ander programma te wijzigen met behulp van metagegevens.

In Groovy is het mogelijk om metaprogrammering uit te voeren tijdens zowel runtime als compilatie. In de toekomst zullen we enkele opvallende kenmerken van beide technieken onderzoeken.

3. Runtime-metaprogrammering

Runtime-metaprogrammering stelt ons in staat om de bestaande eigenschappen en methoden van een klasse te wijzigen. We kunnen ook nieuwe eigenschappen en methoden toevoegen; allemaal tijdens runtime.

Groovy biedt een aantal methoden en eigenschappen die helpen om het gedrag van een klasse tijdens runtime te veranderen.

3.1. eigenschap ontbreekt

Wanneer we proberen toegang te krijgen tot een ongedefinieerde eigenschap van een Groovy-klasse, wordt een MissingPropertyException. Om de uitzondering te voorkomen, biedt Groovy de eigenschap ontbreekt methode.

Laten we eerst een Werknemer klasse met enkele eigenschappen:

class Employee {String firstName String lastName int age}

Ten tweede maken we een Werknemer object en probeer een ongedefinieerde eigenschap weer te geven adres. Bijgevolg zal het de MissingPropertyException:

Werknemer emp = nieuwe werknemer (voornaam: "Norman", achternaam: "Lewis") println emp.adres 
groovy.lang.MissingPropertyException: geen eigenschap: adres voor klasse: com.baeldung.metaprogramming.Employee

Groovy biedt de eigenschap ontbreekt methode om het ontbrekende eigenschapsverzoek op te vangen. Daarom kunnen we een MissingPropertyException tijdens runtime.

Om de aanroep van de gettermethode van een ontbrekende eigenschap op te vangen, zullen we deze definiëren met een enkel argument voor de eigenschapnaam:

def propertyMissing (String propertyName) {"property '$ propertyName' is niet beschikbaar"}
assert emp.address == "eigenschap 'adres' is niet beschikbaar"

Ook kan dezelfde methode het tweede argument hebben als de waarde van de eigenschap, om de aanroep van de setter-methode van een ontbrekende eigenschap op te vangen:

def propertyMissing (String propertyName, propertyValue) {println "kan $ propertyValue niet instellen - eigenschap '$ propertyName' is niet beschikbaar"}

3.2. methodeMissing

De methodeMissing methode is vergelijkbaar met eigenschap ontbreekt. Echter, methodeMissing onderschept een oproep voor een ontbrekende methode, waardoor de MissingMethodException.

Laten we proberen het getFullName methode op een Werknemer voorwerp. Net zo getFullName ontbreekt, zal de uitvoering de MissingMethodException tijdens runtime:

probeer {emp.getFullName ()} catch (MissingMethodException e) {println "methode is niet gedefinieerd"}

Dus in plaats van een methodeaanroep in een proberen te vangenkunnen we definiëren methodeMissing:

def methodMissing (String methodName, def methodArgs) {"methode '$ methodName' is niet gedefinieerd"}
assert emp.getFullName () == "methode 'getFullName' is niet gedefinieerd"

3.3. ExpandoMetaClass

Groovy biedt een metaClass onroerend goed in al zijn klassen. De metaClass eigenschap verwijst naar een instantie van de ExpandoMetaClass.

De ExpandoMetaClass class biedt talloze manieren om een ​​bestaande class tijdens runtime te transformeren. We kunnen bijvoorbeeld eigenschappen, methoden of constructors toevoegen.

Laten we eerst de ontbrekende adres eigendom aan de Werknemer klasse gebruiken metaClass eigendom:

Employee.metaClass.address = ""
Werknemer emp = nieuwe werknemer (voornaam: "Norman", achternaam: "Lewis", adres: "US") assert emp.address == "US"

Laten we verder gaan en de ontbrekende toevoegen getFullName methode naar de Werknemer class-object tijdens runtime:

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

Evenzo kunnen we een constructor toevoegen aan het Werknemer klasse tijdens runtime:

Employee.metaClass.constructor = {String firstName -> nieuwe medewerker (firstName: firstName)}
Werknemer norman = nieuwe Werknemer ("Norman") beweert norman.firstName == "Norman" beweert norman.lastName == null

Evenzo kunnen we toevoegen statisch methoden gebruiken metaClass.static.

De metaClass property is niet alleen handig om door de gebruiker gedefinieerde klassen te wijzigen, maar ook bestaande Java-klassen tijdens runtime.

Laten we bijvoorbeeld een kapitaliseren methode naar de Draad klasse:

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

3.4. Extensies

Een extensie kan tijdens runtime een methode aan een klasse toevoegen en deze wereldwijd toegankelijk maken.

De methoden die in een extensie zijn gedefinieerd, moeten altijd statisch zijn, met de extensie zelf class-object als het eerste argument.

Laten we bijvoorbeeld een Basisuitbreiding class om een getYearOfBirth methode naar de Werknemer klasse:

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

Om het Basisuitbreidings, moeten we het configuratiebestand toevoegen aan het META-INF / diensten directory van ons project.

Dus laten we de org.codehaus.groovy.runtime.ExtensionModule bestand met de volgende configuratie:

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

Laten we de getYearOfBirth methode toegevoegd in de Werknemer klasse:

def leeftijd = 28 def verwachtYearOfBirth = Year.now () - leeftijd Werknemer emp = nieuwe werknemer (leeftijd: leeftijd) assert emp.getYearOfBirth () == verwachtYearOfBirth.value

Evenzo om toe te voegen statisch methoden in een klasse, moeten we een afzonderlijke extensieklasse definiëren.

Laten we bijvoorbeeld een statisch methode getDefaultObj naar onze Werknemer klasse door te definiëren StaticEmployeeExtension klasse:

class StaticEmployeeExtension {static Employee getDefaultObj (Employee self) {retourneer nieuwe medewerker (firstName: "firstName", lastName: "lastName", leeftijd: 20)}}

Vervolgens schakelen we het StaticEmployeeExtension door de volgende configuratie toe te voegen aan het ExtensionModule het dossier:

staticExtensionClasses = com.baeldung.metaprogramming.extension.StaticEmployeeExtension

Nu is alles wat we nodig hebben om onze te testen statischgetDefaultObj methode op de Werknemer klasse:

assert Employee.getDefaultObj (). firstName == "firstName" assert Employee.getDefaultObj (). lastName == "lastName" assert Employee.getDefaultObj (). leeftijd == 20

Evenzo Met behulp van extensies kunnen we een methode toevoegen aan vooraf gecompileerde Java-klassen Leuk vinden Geheel getal en Lang:

public static void printCounter (Integer self) {while (self> 0) {println self self--} return self} assert 5.printCounter () == 0 
openbare statische Long square (Long self) {return self * self} assert 40l.square () == 1600l 

4. Metaprogrammering tijdens het compileren

Met behulp van specifieke annotaties kunnen we de klassenstructuur tijdens het compileren moeiteloos wijzigen. Met andere woorden, we kunnen annotaties gebruiken om de abstracte syntaxisboom van de klasse bij de compilatie te wijzigen.

Laten we enkele van de annotaties bespreken die best handig zijn in Groovy om standaardcode te verminderen. Velen van hen zijn beschikbaar in de groovy.transform pakket.

Als we zorgvuldig analyseren, zullen we ons realiseren dat een paar annotaties functies bieden die vergelijkbaar zijn met Java's Project Lombok.

4.1. @Tomvangrieken

De @Tomvangrieken annotatie voegt een standaardimplementatie toe van de toString methode naar een klasse tijdens het compileren. Het enige dat we nodig hebben, is de annotatie aan de klas toevoegen.

Laten we bijvoorbeeld de @Tomvangrieken annotatie bij onze Werknemer klasse:

@ToString class Employee {long id String firstName String lastName int age}

Nu gaan we een object maken van de Werknemer class en verifieer de string die wordt geretourneerd door de toString methode:

Werknemer werknemer = nieuwe werknemer () werknemer.id = 1 werknemer.firstName = "norman" werknemer.lastName = "lewis" werknemer.age = 28 beweren werknemer.toString () == "com.baeldung.metaprogramming.Employee (1, norman, lewis, 28) "

We kunnen ook parameters declareren zoals sluit uit, omvat, omvattenPakket en ignoreNulls met @Tomvangrieken om de outputstring te wijzigen.

Laten we bijvoorbeeld uitsluiten ID kaart en pakket uit de string van het Employee-object:

@ToString (includePackage = false, exclusief = ['id'])
assert employee.toString () == "Employee (norman, lewis, 28)"

4.2. @TupleConstructor

Gebruik @TupleConstructor in Groovy om een ​​constructor met parameters in de klasse toe te voegen. Deze annotatie maakt een constructor met een parameter voor elke eigenschap.

Laten we bijvoorbeeld toevoegen @TupleConstructor naar de Werknemer klasse:

@TupleConstructor class Employee {long id String firstName String lastName int age}

Nu kunnen we creëren Werknemer objecten die parameters doorgeven in de volgorde van de eigenschappen die in de klasse zijn gedefinieerd.

Werknemer norman = nieuwe werknemer (1, "norman", "lewis", 28) assert norman.toString () == "Werknemer (norman, lewis, 28)" 

Als we geen waarden aan de eigenschappen geven tijdens het maken van objecten, zal Groovy de standaardwaarden in overweging nemen:

Employee snape = new Employee (2, "snape") assert snape.toString () == "Employee (snape, null, 0)"

Gelijkwaardig aan @Tomvangriekenkunnen we parameters declareren zoals sluit uit, omvat en includeSuperProperties met @TupleConstructor om het gedrag van de bijbehorende constructor naar behoefte te wijzigen.

4.3. @EqualsAndHashCode

We kunnen gebruiken @EqualsAndHashCode om de standaardimplementatie van is gelijk aan en hashCode methoden tijdens het compileren.

Laten we het gedrag van @EqualsAndHashCode door het toe te voegen aan het Werknemer klasse:

Werknemer normanCopy = nieuwe werknemer (1, "norman", "lewis", 28) beweren norman == normanCopy beweren norman.hashCode () == normanCopy.hashCode ()

4.4. @Canoniek

@Canoniek is een combinatie van @Tomvangrieken, @TupleConstructor, en @EqualsAndHashCode annotaties.

Door het gewoon toe te voegen, kunnen we ze alle drie gemakkelijk opnemen in een Groovy-klas. We kunnen ook aangeven @Canoniek met een van de specifieke parameters van alle drie de annotaties.

4.5. @AutoClone

Een snelle en betrouwbare manier om te implementeren Te klonen interface is door het toevoegen van de @AutoClone annotatie.

Laten we de kloon methode na het toevoegen @AutoClone naar de Werknemer klasse:

probeer {Werknemer norman = nieuwe werknemer (1, "norman", "lewis", 28) def normanCopy = norman.clone () assert norman == normanCopy} vangst (CloneNotSupportedException e) {e.printStackTrace ()}

4.6. Logging-ondersteuning met @Log, @Commons, @ Log4j, @ Log4j2, en @ Slf4j

Om logboekondersteuning aan een Groovy-klasse toe te voegen, hoeven we alleen maar annotaties toe te voegen die beschikbaar zijn in groovy.util.logging pakket.

Laten we de logboekregistratie van JDK inschakelen door de extensie @Log annotatie bij de Werknemer klasse. Daarna voegen we de logEmp methode:

def logEmp () {log.info "Werknemer: $ lastName, $ firstName is $ age jaar"}

Bellen met het logEmp methode op een Werknemer object toont de logboeken op de console:

Werknemer werknemer = nieuwe werknemer (1, "Norman", "Lewis", 28) werknemer.logEmp ()
INFO: Werknemer: Lewis, Norman is 28 jaar oud

Evenzo is het @Commons annotatie is beschikbaar om ondersteuning voor logboekregistratie van Apache Commons toe te voegen. @ Log4j is beschikbaar voor Apache Log4j 1.x logboekregistratie-ondersteuning en @ Log4j2 voor Apache Log4j 2.x. Gebruik ten slotte @ Slf4j om Simple Logging Facade toe te voegen voor Java-ondersteuning.

5. Conclusie

In deze tutorial hebben we het concept van metaprogrammering in Groovy onderzocht.

Onderweg hebben we een paar opmerkelijke functies voor metaprogrammering gezien, zowel voor runtime als compilatie.

Tegelijkertijd hebben we aanvullende handige annotaties onderzocht die beschikbaar zijn in Groovy voor schonere en dynamische code.

Zoals gewoonlijk zijn de code-implementaties voor dit artikel beschikbaar op GitHub.