1. 概述
Groovy 是一种运行在 JVM 上的动态语言,具备强大的元编程能力。它支持运行时和编译时两种元编程方式,可以极大地提升代码的灵活性和开发效率。
本文将带你深入了解 Groovy 的元编程机制,包括运行时方法拦截、动态修改类结构、以及编译时注解处理等内容。文中会结合具体示例,帮助你快速掌握 Groovy 元编程的核心技巧。
2. 什么是元编程?
元编程(Metaprogramming)是一种通过程序自身来操作、修改程序结构或行为的编程技术。
在 Groovy 中,元编程可以分为两类:
- 运行时元编程(Runtime Metaprogramming):在程序运行过程中动态修改类或对象的行为。
- 编译时元编程(Compile-time Metaprogramming):在编译阶段通过注解修改类结构,生成额外代码。
这两种方式各有所长,结合使用可以写出更灵活、更简洁的代码。
3. 运行时元编程
Groovy 提供了多种机制,允许你在运行时动态修改类或对象的行为,包括拦截缺失属性、方法、使用 metaClass
扩展类等。
3.1. propertyMissing
当访问一个类中未定义的属性时,Groovy 会抛出 MissingPropertyException
。你可以通过定义 propertyMissing
方法来捕获这种访问,避免异常。
✅ 示例:
class Employee {
String firstName
String lastName
int age
}
尝试访问未定义的 address
属性:
Employee emp = new Employee(firstName: "Norman", lastName: "Lewis")
println emp.address
输出:
groovy.lang.MissingPropertyException: No such property: address for class: Employee
解决方法:
def propertyMissing(String propertyName) {
"property '$propertyName' is not available"
}
调用:
assert emp.address == "property 'address' is not available"
你也可以为 setter 提供实现:
def propertyMissing(String propertyName, propertyValue) {
"cannot set $propertyValue - property '$propertyName' is not available"
}
3.2. methodMissing
与 propertyMissing
类似,methodMissing
用于捕获未定义方法的调用,避免 MissingMethodException
。
✅ 示例:
def methodMissing(String methodName, def methodArgs) {
"method '$methodName' is not defined"
}
调用:
assert emp.getFullName() == "method 'getFullName' is not defined"
3.3. ExpandoMetaClass
metaClass
是 Groovy 类中自带的属性,指向 ExpandoMetaClass
实例。通过它,可以在运行时动态添加属性、方法甚至构造函数。
✅ 添加属性:
Employee.metaClass.address = ""
Employee emp = new Employee(firstName: "Norman", lastName: "Lewis", address: "US")
assert emp.address == "US"
✅ 添加方法:
emp.metaClass.getFullName = {
"$lastName, $firstName"
}
assert emp.getFullName() == "Lewis, Norman"
✅ 添加构造函数:
Employee.metaClass.constructor = { String firstName ->
new Employee(firstName: firstName)
}
Employee norman = new Employee("Norman")
assert norman.firstName == "Norman"
⚠️ 你甚至可以扩展 Java 原生类,比如给 String
添加方法:
String.metaClass.capitalize = { String str ->
str.substring(0, 1).toUpperCase() + str.substring(1)
}
assert "norman".capitalize() == "Norman"
3.4. 扩展(Extensions)
扩展允许你在运行时为类添加全局方法。方法必须是静态的,且第一个参数是当前类的实例。
✅ 示例:为 Employee
添加 getYearOfBirth
方法
class BasicExtensions {
static int getYearOfBirth(Employee self) {
return Year.now().value - self.age
}
}
在 META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
中配置:
moduleName=core-groovy-2
moduleVersion=1.0-SNAPSHOT
extensionClasses=com.baeldung.metaprogramming.extension.BasicExtensions
调用:
def age = 28
Employee emp = new Employee(age: age)
assert emp.getYearOfBirth() == Year.now().value - age
✅ 添加静态方法:
class StaticEmployeeExtension {
static Employee getDefaultObj(Employee self) {
return new Employee(firstName: "firstName", lastName: "lastName", age: 20)
}
}
配置:
staticExtensionClasses=com.baeldung.metaprogramming.extension.StaticEmployeeExtension
调用:
assert Employee.getDefaultObj().firstName == "firstName"
✅ 扩展 Java 类:
public static void printCounter(Integer self) {
while (self > 0) {
println self
self--
}
}
assert 5.printCounter() == 0
public static Long square(Long self) {
return self * self
}
assert 40l.square() == 1600l
4. 编译时元编程
Groovy 支持通过注解在编译阶段修改类结构,这些注解通常位于 groovy.transform
包中,功能类似于 Java 的 Lombok。
4.1. @ToString
@ToString
会为类自动生成 toString()
方法。
✅ 示例:
@ToString
class Employee {
long id
String firstName
String lastName
int age
}
调用:
Employee employee = new Employee(id: 1, firstName: "norman", lastName: "lewis", age: 28)
assert employee.toString() == "com.baeldung.metaprogramming.Employee(1, norman, lewis, 28)"
控制输出内容:
@ToString(includePackage = false, excludes = ['id'])
assert employee.toString() == "Employee(norman, lewis, 28)"
4.2. @TupleConstructor
@TupleConstructor
自动生成按属性顺序的构造函数。
✅ 示例:
@TupleConstructor
class Employee {
long id
String firstName
String lastName
int age
}
调用:
Employee norman = new Employee(1, "norman", "lewis", 28)
assert norman.toString() == "Employee(norman, lewis, 28)"
默认值处理:
Employee snape = new Employee(2, "snape")
assert snape.toString() == "Employee(snape, null, 0)"
4.3. @EqualsAndHashCode
@EqualsAndHashCode
自动生成 equals()
和 hashCode()
方法。
✅ 示例:
Employee norman = new Employee(1, "norman", "lewis", 28)
Employee normanCopy = new Employee(1, "norman", "lewis", 28)
assert norman == normanCopy
assert norman.hashCode() == normanCopy.hashCode()
4.4. @Canonical
@Canonical
是 @ToString
、@TupleConstructor
和 @EqualsAndHashCode
的组合注解。
✅ 示例:
@Canonical
class Employee {
long id
String firstName
String lastName
int age
}
4.5. @AutoClone
@AutoClone
用于自动生成 clone()
方法,避免手动实现 Cloneable
接口。
✅ 示例:
Employee norman = new Employee(1, "norman", "lewis", 28)
def normanCopy = norman.clone()
assert norman == normanCopy
4.6. 日志支持注解
Groovy 提供多种日志注解,简化日志引入过程。
@Log
:JDK 自带日志@Commons
:Apache Commons Logging@Log4j
/@Log4j2
:Log4j 系列@Slf4j
:SLF4J + Logback
✅ 示例:
@Log
class Employee {
String lastName
String firstName
int age
def logEmp() {
log.info "Employee: $lastName, $firstName is of $age years age"
}
}
调用:
Employee employee = new Employee(1, "Norman", "Lewis", 28)
employee.logEmp()
输出:
INFO: Employee: Lewis, Norman is of 28 years age
5. 总结
Groovy 的元编程能力非常强大,无论是运行时动态修改类行为,还是编译时通过注解减少样板代码,都极大地提升了开发效率和代码灵活性。
本文介绍了以下核心内容:
技术 | 说明 |
---|---|
propertyMissing |
拦截未定义属性访问 |
methodMissing |
拦截未定义方法调用 |
ExpandoMetaClass |
动态扩展类行为 |
Extensions | 全局扩展方法 |
@ToString / @TupleConstructor / @EqualsAndHashCode / @Canonical / @AutoClone |
编译时生成方法 |
日志注解 | 快速引入日志功能 |
掌握这些技巧,能让你写出更简洁、更灵活、更具表现力的 Groovy 代码。