Belajar Groovy - Metaprogramming & AST Transformations
Episode 15 of 23

Belajar Groovy - Metaprogramming & AST Transformations

Episode ini membahas metaprogramming Groovy: dynamic methods, propertyMissing, dan methodMissing. Kalian juga akan membandingkan runtime metaprogramming dengan compile-time AST transformations, serta mempelajari contoh dekorator dan dynamic proxy pattern.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Pendahuluan

Inilah pembeda terbesar Groovy: metaprogramming. Kemampuan mengubah perilaku program saat runtime membuat Groovy mampu melakukan hal-hal yang membutuhkan framework besar di bahasa lain.

Dynamic Methods dengan methodMissing

Menangkap Method yang Tidak Ada

Menangkap method tidak dikenal
class Dinamis {
    def methodMissing(String nama, args) {
        println "Memanggil method ${nama} dengan argumen ${args}"
    }
}
 
def obj = new Dinamis()
obj.halo()
obj.kirim("pesan", 123)

obj.halo() memanggil method yang tidak ada — Groovy meneruskannya ke methodMissing. def methodMissing(String nama, args) menerima nama method dan argumen, sehingga kelas bisa merespons pemanggilan apa pun secara dinamis.

methodMissing vs invokeMethod

  • invokeMethod: dipanggil untuk semua method, termasuk yang terdefinisi.
  • methodMissing: dipanggil hanya untuk method yang tidak terdefinisi.

Gunakan methodMissing untuk perilaku fallback, karena invokeMethod menambah overhead pada setiap pemanggilan.

propertyMissing

Property Dinamis

Property dinamis
class Konfigurasi {
    def propertyMissing(String nama) {
        println "Property ${nama} tidak ditemukan, mengembalikan default"
        return null
    }
}
 
def config = new Konfigurasi()
println config.tidakAda

config.tidakAda memicu propertyMissing karena property tersebut tidak didefinisikan. def propertyMissing(String nama) menerima nama property dan mengembalikan nilai pengganti — pola yang berguna untuk konfigurasi dengan fallback.

Override Getter dan Setter

Property setter dinamis
class Store {
    def storage = [:]
 
    def propertyMissing(String nama, value) {
        storage[nama] = value
    }
}
 
def store = new Store()
store.nama = "Arman"
println store.storage

store.nama = "Arman" memicu propertyMissing(String nama, value) dan menyimpan nilai ke map internal. def propertyMissing(String nama, value) adalah varian dua argumen untuk menangani assignment.

ExpandoMetaClass

Memperluas Kelas saat Runtime

Memperluas String dengan ExpandoMetaClass
String.metaClass.isPalindrome = {
    delegate == delegate.reverse()
}
 
println "kodok".isPalindrome()
println "groovy".isPalindrome()

String.metaClass.isPalindrome = { ... } menambahkan method baru ke class String. delegate di dalam closure merujuk ke instance string, sehingga "kodok".isPalindrome() bisa langsung dipanggil.

Memberikan Method ke Semua Instance

Method tambahan untuk List
List.metaClass.rataRata = {
    delegate.isEmpty() ? 0 : delegate.sum() / delegate.size()
}
 
println [1, 2, 3, 4].rataRata()

List.metaClass.rataRata = { ... } membuat method rataRata tersedia untuk semua list. delegate.isEmpty() ? 0 : delegate.sum() / delegate.size() menggunakan Elvis untuk menangani list kosong.

Runtime vs Compile-Time

Perbandingan Dua Pendekatan

  • Runtime metaprogramming: methodMissing, propertyMissing, ExpandoMetaClass — semuanya berjalan saat program dieksekusi.
  • Compile-time AST transformations: anotasi seperti @Canonical dan @Builder mengubah kode saat kompilasi.

Pendekatan runtime paling fleksibel tapi lebih lambat; pendekatan compile-time menghasilkan kode yang cepat dan bisa diinspeksi oleh IDE.

Kapan Memakai Masing-masing

Pilih runtime metaprogramming untuk DSL dinamis dan fallback, karena perilakunya bisa berubah berdasarkan input. Pilih AST transformations untuk kode yang strukturnya sudah pasti, karena hasilnya lebih cepat dan type-safe.

Contoh Pola Dekorator dan Proxy

Dynamic Proxy Sederhana

Proxy logging dengan methodMissing
class LoggingProxy {
    Object target
 
    def methodMissing(String nama, args) {
        println "Memanggil ${nama} dengan ${args}"
        def hasil = target."$nama"(*args)
        println "Hasil: ${hasil}"
        hasil
    }
}
 
class Layanan {
    String sapa(String nama) {
        "Halo, ${nama}"
    }
}
 
def proxy = new LoggingProxy(target: new Layanan())
println proxy.sapa("Arman")

proxy.sapa("Arman") memicu methodMissing, yang mencatat log, memanggil method asli dengan target."$nama"(*args), lalu mengembalikan hasil. target."$nama"(*args) memanggil method dinamis dengan spread operator — inti pola dynamic proxy.

Dekorator dengan AST

Dekorator memoization
import groovy.transform.Memoized
 
class Faktorial {
    @Memoized
    long hitung(int n) {
        n <= 1 ? 1 : n * hitung(n - 1)
    }
}
 
println new Faktorial().hitung(20)

@Memoized adalah AST transformation yang membungkus method dengan cache hasil. n <= 1 ? 1 : n * hitung(n - 1) memanfaatkan closure kondisi untuk rekursi, dan hasilnya di-cache sehingga komputasi berulang terhindarkan.

Penutup

Episode 15 membuka dunia metaprogramming Groovy: methodMissing dan propertyMissing untuk respons dinamis, ExpandoMetaClass untuk memperluas class saat runtime, serta perbandingan pendekatan runtime dan compile-time lewat AST transformations.

Inti yang harus dibawa pulang:

  • methodMissing menangkap panggilan method yang tidak terdefinisi.
  • propertyMissing menangkap akses property yang tidak ada.
  • ExpandoMetaClass memperluas class Java maupun Groovy saat runtime.
  • Runtime metaprogramming fleksibel; AST transformations cepat.
  • Dynamic proxy dibuat ringkas dengan methodMissing dan spread *args.
  • @Memoized menambahkan cache hasil method secara otomatis.

Di episode 16 selanjutnya kita akan membahas performance dan optimization — menilai trade-off dinamis versus statis, mengoptimalkan script dengan @CompileStatic, profiling JVM, dan meminimalkan overhead runtime.

Belajar Groovy - Metaprogramming & AST Transformations | Belajar Groovy