Metaprogramming di Groovy

1. Ikhtisar

Groovy adalah bahasa JVM yang dinamis dan kuat yang memiliki banyak fitur seperti closure dan ciri.

Dalam tutorial ini, kita akan menjelajahi konsep Metaprogramming di Groovy.

2. Apakah Metaprogramming itu?

Metaprogramming adalah teknik pemrograman menulis program untuk memodifikasi dirinya atau program lain dengan menggunakan metadata.

Di Groovy, dimungkinkan untuk melakukan metaprogramming pada runtime dan waktu kompilasi. Selanjutnya, kita akan menjelajahi beberapa fitur penting dari kedua teknik tersebut.

3. Pemrograman Metaplikasi Runtime

Metaprogramming runtime memungkinkan kita untuk mengubah properti dan metode kelas yang ada. Juga, kita dapat melampirkan properti dan metode baru; semua saat runtime.

Groovy menyediakan beberapa metode dan properti yang membantu mengubah perilaku kelas saat runtime.

3.1. propertyMissing

Saat kami mencoba mengakses properti tak terdefinisi dari kelas Groovy, ia melontarkan MissingPropertyException. Untuk menghindari pengecualian, Groovy menyediakan metode propertyMissing .

Pertama, mari kita tulis kelas Karyawan dengan beberapa properti:

class Employee { String firstName String lastName int age }

Kedua, kita akan membuat objek Employee dan mencoba menampilkan alamat properti yang tidak ditentukan . Akibatnya, ini akan memunculkan MissingPropertyException :

Employee emp = new Employee(firstName: "Norman", lastName: "Lewis") println emp.address 
groovy.lang.MissingPropertyException: No such property: address for class: com.baeldung.metaprogramming.Employee

Groovy menyediakan metode propertyMissing untuk menangkap permintaan properti yang hilang. Oleh karena itu, kita dapat menghindari MissingPropertyException saat runtime.

Untuk menangkap panggilan metode pengambil properti yang hilang, kita akan mendefinisikannya dengan satu argumen untuk nama properti:

def propertyMissing(String propertyName) { "property '$propertyName' is not available" }
assert emp.address == "property 'address' is not available"

Selain itu, metode yang sama dapat memiliki argumen kedua sebagai nilai properti, untuk menangkap panggilan metode penyetel properti yang hilang:

def propertyMissing(String propertyName, propertyValue) { println "cannot set $propertyValue - property '$propertyName' is not available" }

3.2. methodMissing

The methodMissing Metode ini mirip dengan propertyMissing . Namun, methodMissing mencegat panggilan untuk setiap metode yang hilang, sehingga menghindari MissingMethodException .

Mari kita coba memanggil metode getFullName pada objek Employee . Karena getFullName tidak ada, eksekusi akan menampilkan MissingMethodException saat runtime:

try { emp.getFullName() } catch (MissingMethodException e) { println "method is not defined" }

Jadi, alih-alih membungkus panggilan metode dalam try-catch , kita dapat mendefinisikan methodMissing :

def methodMissing(String methodName, def methodArgs) { "method '$methodName' is not defined" }
assert emp.getFullName() == "method 'getFullName' is not defined"

3.3. ExpandoMetaClass

Groovy menyediakan properti metaClass di semua kelasnya. The metaclass properti merujuk ke sebuah contoh dari ExpandoMetaClass .

Kelas ExpandoMetaClass menyediakan banyak cara untuk mengubah kelas yang ada saat runtime. Misalnya, kita dapat menambahkan properti, metode, atau konstruktor.

Pertama, mari tambahkan properti alamat yang hilang ke kelas Karyawan menggunakan properti metaClass :

Employee.metaClass.address = ""
Employee emp = new Employee(firstName: "Norman", lastName: "Lewis", address: "US") assert emp.address == "US"

Selanjutnya, mari tambahkan metode getFullName yang hilang ke objek kelas Karyawan saat runtime:

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

Demikian pula, kita dapat menambahkan konstruktor ke kelas Karyawan saat runtime:

Employee.metaClass.constructor = { String firstName -> new Employee(firstName: firstName) }
Employee norman = new Employee("Norman") assert norman.firstName == "Norman" assert norman.lastName == null

Demikian juga, kita dapat menambahkan metode statis menggunakan metaClass.static.

The metaclass properti tidak hanya berguna untuk memodifikasi user-defined kelas, tetapi kelas Java juga ada pada saat runtime.

Misalnya, mari tambahkan metode kapitalisasi ke kelas String :

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

3.4. Ekstensi

Ekstensi dapat menambahkan metode ke kelas pada waktu proses dan membuatnya dapat diakses secara global.

Metode yang ditentukan dalam ekstensi harus selalu statis, dengan objek kelas mandiri sebagai argumen pertama.

Misalnya, mari tulis kelas BasicExtension untuk menambahkan metode getYearOfBirth ke kelas Karyawan :

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

Untuk mengaktifkan BasicExtension s, kita perlu menambahkan file konfigurasi di direktori META-INF / services dari proyek kita.

Jadi, mari tambahkan file org.codehaus.groovy.runtime.ExtensionModule dengan konfigurasi berikut:

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

Mari verifikasi metode getYearOfBirth yang ditambahkan di kelas Karyawan :

def age = 28 def expectedYearOfBirth = Year.now() - age Employee emp = new Employee(age: age) assert emp.getYearOfBirth() == expectedYearOfBirth.value

Demikian pula, untuk menambahkan metode statis di kelas, kita perlu mendefinisikan kelas ekstensi terpisah.

Misalnya, mari tambahkan metode statis getDefaultObj ke kelas Karyawan kita dengan menentukan kelas StaticEmployeeExtension :

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

Kemudian, kami mengaktifkan StaticEmployeeExtension dengan menambahkan konfigurasi berikut ke file ExtensionModule :

staticExtensionClasses=com.baeldung.metaprogramming.extension.StaticEmployeeExtension

Sekarang, yang kita butuhkan hanyalah menguji metode getDefaultObj statis kita pada kelas Karyawan :

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

Demikian pula, dengan menggunakan ekstensi, kita dapat menambahkan metode ke kelas Java yang telah dikompilasi sebelumnya seperti Integer dan Long :

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

4. Metaprogramming waktu kompilasi

Dengan menggunakan anotasi khusus, kami dapat dengan mudah mengubah struktur kelas pada waktu kompilasi. Dengan kata lain, kita dapat menggunakan anotasi untuk mengubah pohon sintaksis abstrak kelas pada kompilasi .

Mari kita bahas beberapa anotasi yang cukup berguna di Groovy untuk mengurangi kode boilerplate. Banyak dari mereka tersedia dalam paket groovy.transform .

Jika kita menganalisis dengan cermat, kita akan menyadari beberapa anotasi menyediakan fitur yang mirip dengan Project Lombok di Java.

4.1. @ToRing

The @ToString penjelasan menambahkan implementasi default dari toString metode untuk kelas pada saat kompilasi. Yang kita butuhkan hanyalah menambahkan anotasi ke kelas.

For instance, let's add the @ToString annotation to our Employee class:

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

Now, we'll create an object of the Employee class and verify the string returned by the toString method:

Employee employee = new Employee() employee.id = 1 employee.firstName = "norman" employee.lastName = "lewis" employee.age = 28 assert employee.toString() == "com.baeldung.metaprogramming.Employee(1, norman, lewis, 28)"

We can also declare parameters such as excludes, includes, includePackage and ignoreNulls with @ToString to modify the output string.

For example, let's exclude id and package from the string of the Employee object:

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

4.2. @TupleConstructor

Use @TupleConstructor in Groovy to add a parameterized constructor in the class. This annotation creates a constructor with a parameter for each property.

For example, let's add @TupleConstructor to the Employee class:

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

Now, we can create Employee object passing parameters in the order of properties defined in the class.

Employee norman = new Employee(1, "norman", "lewis", 28) assert norman.toString() == "Employee(norman, lewis, 28)" 

If we don't provide values to the properties while creating objects, Groovy will consider default values:

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

Similar to @ToString, we can declare parameters such as excludes, includes and includeSuperProperties with @TupleConstructor to alter the behavior of its associated constructor as needed.

4.3. @EqualsAndHashCode

We can use @EqualsAndHashCode to generate the default implementation of equals and hashCode methods at compile time.

Let's verify the behavior of @EqualsAndHashCode by adding it to the Employee class:

Employee normanCopy = new Employee(1, "norman", "lewis", 28) assert norman == normanCopy assert norman.hashCode() == normanCopy.hashCode()

4.4. @Canonical

@Canonical is a combination of @ToString, @TupleConstructor, and @EqualsAndHashCode annotations.

Just by adding it, we can easily include all three to a Groovy class. Also, we can declare @Canonical with any of the specific parameters of all three annotations.

4.5. @AutoClone

A quick and reliable way to implement Cloneable interface is by adding the @AutoClone annotation.

Let's verify the clone method after adding @AutoClone to the Employee class:

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

4.6. Logging Support With @Log, @Commons, @Log4j, @Log4j2, and @Slf4j

To add logging support to any Groovy class, all we need is to add annotations available in groovy.util.logging package.

Let's enable the logging provided by JDK by adding the @Log annotation to the Employee class. Afterward, we'll add the logEmp method:

def logEmp() { log.info "Employee: $lastName, $firstName is of $age years age" }

Calling the logEmp method on an Employee object will show the logs on the console:

Employee employee = new Employee(1, "Norman", "Lewis", 28) employee.logEmp()
INFO: Employee: Lewis, Norman is of 28 years age

Similarly, the @Commons annotation is available to add Apache Commons logging support. @Log4j is available for Apache Log4j 1.x logging support and @Log4j2 for Apache Log4j 2.x. Finally, use @Slf4j to add Simple Logging Facade for Java support.

5. Conclusion

In this tutorial, we've explored the concept of metaprogramming in Groovy.

Along the way, we've seen a few notable metaprogramming features both for runtime and compile-time.

At the same time, we've explored additional handy annotations available in Groovy for cleaner and dynamic code.

As usual, the code implementations for this article are available over on GitHub.