1. Overview
1.概述
Groovy is a dynamic and powerful JVM language which has numerous features like closures and traits.
Groovy是一种动态的、强大的JVM语言,它有许多特性,如closures和traits。
In this tutorial, we’ll explore the concept of Metaprogramming in Groovy.
在本教程中,我们将探讨Groovy中元编程的概念。
2. What Is Metaprogramming?
2.什么是元编程?
Metaprogramming is a programming technique of writing a program to modify itself or another program using metadata.
元编程是一种编程技术,即编写一个程序,利用元数据修改自己或另一个程序。
In Groovy, it’s possible to perform metaprogramming at both runtime and compile-time. Going forward, we’ll explore a few notable features of both techniques.
在Groovy中,有可能在运行时和编译时都进行元编程。接下来,我们将探讨这两种技术的一些值得注意的特点。
3. Runtime Metaprogramming
3.运行时元编程
Runtime metaprogramming enables us to alter the existing properties and methods of a class. Also, we can attach new properties and methods; all at runtime.
运行时元编程使我们能够改变一个类的现有属性和方法。此外,我们还可以附加新的属性和方法;所有这些都在运行时进行。
Groovy provides a few methods and properties that help to alter the behavior of a class at runtime.
Groovy提供了一些方法和属性,有助于在运行时改变类的行为。
3.1. propertyMissing
3.1.propertyMissing(属性缺失
When we try to access an undefined property of a Groovy class, it throws a MissingPropertyException. To avoid the exception, Groovy provides the propertyMissing method.
当我们试图访问Groovy类的一个未定义的属性时,它会抛出一个MissingPropertyException.为了避免这个异常,Groovy提供了propertyMissing方法。
First, let’s write an Employee class with some properties:
首先,让我们写一个带有一些属性的Employee类。
class Employee {
String firstName
String lastName
int age
}
Second, we’ll create an Employee object and try to display an undefined property address. Consequently, it will throw the MissingPropertyException:
第二,我们将创建一个Employee对象,并尝试显示一个未定义的属性address.,因此,它将抛出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 provides the propertyMissing method to catch the missing property request. Therefore, we can avoid a MissingPropertyException at runtime.
Groovy提供了propertyMissing方法来捕捉丢失属性的请求。因此,我们可以避免在运行时出现MissingPropertyException。
To catch a missing property’s getter method call, we’ll define it with a single argument for the property name:
为了捕捉一个缺失的属性的getter方法调用,我们将用一个属性名称的单一参数来定义它。
def propertyMissing(String propertyName) {
"property '$propertyName' is not available"
}
assert emp.address == "property 'address' is not available"
Also, the same method can have the second argument as the value of the property, to catch a missing property’s setter method call:
另外,同一个方法的第二个参数可以作为属性的值,以捕捉一个丢失的属性的setter方法调用。
def propertyMissing(String propertyName, propertyValue) {
println "cannot set $propertyValue - property '$propertyName' is not available"
}
3.2. methodMissing
3.2.方法缺失
The methodMissing method is similar to propertyMissing. However, methodMissing intercepts a call for any missing method, thereby avoiding the MissingMethodException.
methodMissing方法与propertyMissing类似。然而,methodMissing拦截对任何缺失方法的调用,从而避免了MissingMethodException。
Let’s try to call the getFullName method on an Employee object. As getFullName is missing, execution will throw the MissingMethodException at runtime:
让我们试着在一个Employee对象上调用getFullName方法。由于getFullName缺失,执行时将在运行时抛出MissingMethodException。
try {
emp.getFullName()
} catch (MissingMethodException e) {
println "method is not defined"
}
So, instead of wrapping a method call in a try-catch, we can define methodMissing:
因此,我们可以定义methodMissing,而不是用try-catch来包装一个方法调用。
def methodMissing(String methodName, def methodArgs) {
"method '$methodName' is not defined"
}
assert emp.getFullName() == "method 'getFullName' is not defined"
3.3. ExpandoMetaClass
3.3.ExpandoMetaClass
Groovy provides a metaClass property in all its classes. The metaClass property refers to an instance of the ExpandoMetaClass.
Groovy在其所有的类中提供一个metaClass属性。metaClass属性是指ExpandoMetaClass的一个实例。
The ExpandoMetaClass class provides numerous ways to transform an existing class at runtime. For example, we can add properties, methods, or constructors.
ExpandoMetaClass类提供了许多方法来在运行时改造现有的类。例如,我们可以添加属性、方法或构造函数。
First, let’s add the missing address property to the Employee class using metaClass property:
首先,让我们使用metaClass属性向Employee类添加缺少的address属性。
Employee.metaClass.address = ""
Employee emp = new Employee(firstName: "Norman", lastName: "Lewis", address: "US")
assert emp.address == "US"
Moving further, let’s add the missing getFullName method to the Employee class object at runtime:
进一步,让我们在运行时向Employee类对象添加缺少的getFullName方法。
emp.metaClass.getFullName = {
"$lastName, $firstName"
}
assert emp.getFullName() == "Lewis, Norman"
Similarly, we can add a constructor to the Employee class at runtime:
类似地,我们可以在运行时向Employee类添加一个构造函数。
Employee.metaClass.constructor = { String firstName ->
new Employee(firstName: firstName)
}
Employee norman = new Employee("Norman")
assert norman.firstName == "Norman"
assert norman.lastName == null
Likewise, we can add static methods using metaClass.static.
同样,我们可以使用metaClass.static.添加静态方法。
The metaClass property is not only handy to modify user-defined classes, but also existing Java classes at runtime.
metaClass属性不仅可以方便地修改用户定义的类,也可以在运行时修改现有的Java类。
For example, let’s add a capitalize method to the String class:
例如,让我们给String类添加一个capitalize方法。
String.metaClass.capitalize = { String str ->
str.substring(0, 1).toUpperCase() + str.substring(1)
}
assert "norman".capitalize() == "Norman"
3.4. Extensions
3.4. 扩展
An extension can add a method to a class at runtime and make it accessible globally.
一个扩展可以在运行时向一个类添加一个方法,并使其可以在全球范围内访问。
The methods defined in an extension should always be static, with the self class object as the first argument.
在扩展中定义的方法应该总是静态的,以self类对象作为第一个参数。
For example, let’s write a BasicExtension class to add a getYearOfBirth method to the Employee class:
例如,让我们写一个BasicExtension类来给Employee类添加一个getYearOfBirth方法。
class BasicExtensions {
static int getYearOfBirth(Employee self) {
return Year.now().value - self.age
}
}
To enable the BasicExtensions, we’ll need to add the configuration file in the META-INF/services directory of our project.
为了启用BasicExtensions,我们需要在我们项目的META-INF/services目录下添加配置文件。
So, let’s add the org.codehaus.groovy.runtime.ExtensionModule file with the following configuration:
因此,让我们添加org.codehaus.groovy.runtime.ExtensionModule文件,配置如下。
moduleName=core-groovy-2
moduleVersion=1.0-SNAPSHOT
extensionClasses=com.baeldung.metaprogramming.extension.BasicExtensions
Let’s verify the getYearOfBirth method added in the Employee class:
让我们验证一下getYearOfBirth方法在Employee类中的添加。
def age = 28
def expectedYearOfBirth = Year.now() - age
Employee emp = new Employee(age: age)
assert emp.getYearOfBirth() == expectedYearOfBirth.value
Similarly, to add static methods in a class, we’ll need to define a separate extension class.
同样地,要在一个类中添加静态方法,我们需要定义一个单独的扩展类。
For instance, let’s add a static method getDefaultObj to our Employee class by defining StaticEmployeeExtension class:
例如,让我们通过定义StaticEmployeeExtension类,为我们的Employee类添加一个static方法getDefaultObj。
class StaticEmployeeExtension {
static Employee getDefaultObj(Employee self) {
return new Employee(firstName: "firstName", lastName: "lastName", age: 20)
}
}
Then, we enable the StaticEmployeeExtension by adding the following configuration to the ExtensionModule file:
然后,我们通过在ExtensionModule文件中添加以下配置,启用StaticEmployeeExtension。
staticExtensionClasses=com.baeldung.metaprogramming.extension.StaticEmployeeExtension
Now, all we need is to test our static getDefaultObj method on the Employee class:
现在,我们需要的是测试我们在Employee类上的staticgetDefaultObj>方法。
assert Employee.getDefaultObj().firstName == "firstName"
assert Employee.getDefaultObj().lastName == "lastName"
assert Employee.getDefaultObj().age == 20
Similarly, using extensions, we can add a method to pre-compiled Java classes like Integer and Long:
同样地,使用扩展,我们可以为预编译的Java类添加一个方法,如Integer 和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. Compile-time Metaprogramming
4.编译时的元编程
Using specific annotations, we can effortlessly alter the class structure at compile-time. In other words, we can use annotations to modify the abstract syntax tree of the class at the compilation.
使用特定的注解,我们可以毫不费力地在编译时改变类的结构。换句话说,我们可以在编译时使用注释来修改类的抽象语法树。
Let’s discuss some of the annotations which are quite handy in Groovy to reduce boilerplate code. Many of them are available in the groovy.transform package.
让我们来讨论一些注解,这些注解在Groovy中相当方便,可以减少模板代码。其中许多注释在groovy.transform包中都有。
If we carefully analyze, we’ll realize a few annotations provides features similar to Java’s Project Lombok.
如果我们仔细分析,就会发现有几个注解提供了与Java的Project Lombok类似的功能。
4.1. @ToString
4.1 @ToString
The @ToString annotation adds a default implementation of the toString method to a class at compile-time. All we need is to add the annotation to the class.
@ToString注解在编译时为类添加了toString方法的默认实现。我们所需要的就是将注解添加到类中。
For instance, let’s add the @ToString annotation to our Employee class:
例如,让我们把@ToString注解添加到我们的Employee类。
@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类的对象并验证toString方法返回的字符串。
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.
我们还可以用@ToString声明参数,如excludes、includes、includePackage和ignoreNulls来修改输出字符串。
For example, let’s exclude id and package from the string of the Employee object:
例如,让我们把id和package从Employee对象的字符串中排除。
@ToString(includePackage=false, excludes=['id'])
assert employee.toString() == "Employee(norman, lewis, 28)"
4.2. @TupleConstructor
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.
在Groovy中使用@TupleConstructor 来在类中添加一个参数化构造函数。该注解为每个属性创建一个带参数的构造函数。
For example, let’s add @TupleConstructor to the Employee class:
例如,让我们将@TupleConstructor添加到Employee类中。
@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对象,按照类中定义的属性顺序传递参数。
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:
如果我们在创建对象时没有为属性提供值,Groovy会考虑默认值。
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.
与@ToString类似,我们可以用@TupleConstructor声明诸如excludes、includes和includeSuperProperties等参数,以根据需要改变其相关构造器的行为。
4.3. @EqualsAndHashCode
4.3.@EqualsAndHashCode
We can use @EqualsAndHashCode to generate the default implementation of equals and hashCode methods at compile time.
我们可以使用@EqualsAndHashCode来在编译时生成equals和hashCode方法的默认实现。
Let’s verify the behavior of @EqualsAndHashCode by adding it to the Employee class:
让我们通过将@EqualsAndHashCode添加到Employee类中来验证它的行为。
Employee normanCopy = new Employee(1, "norman", "lewis", 28)
assert norman == normanCopy
assert norman.hashCode() == normanCopy.hashCode()
4.4. @Canonical
4.4.@Canonical
@Canonical is a combination of @ToString, @TupleConstructor, and @EqualsAndHashCode annotations.
@Canonical是@ToString、@TupleConstructor和@EqualsAndHashCode注解的组合。
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.
仅仅通过添加它,我们就可以很容易地将这三者包含到一个Groovy类中。另外,我们可以用这三个注解的任何一个具体参数来声明@Canonical。
4.5. @AutoClone
4.5.@AutoClone
A quick and reliable way to implement Cloneable interface is by adding the @AutoClone annotation.
实现Cloneable接口的一个快速而可靠的方法是添加@AutoClone注解。
Let’s verify the clone method after adding @AutoClone to the Employee class:
让我们验证一下在@AutoClone类中加入@AutoClone后的clone方法。
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
4.6.使用@Log、@Commons、@Log4j、@Log4j2、和@Slf4j的日志支持
To add logging support to any Groovy class, all we need is to add annotations available in groovy.util.logging package.
要给任何Groovy类添加日志支持,我们只需要添加groovy.util.logging包中的注解即可。
Let’s enable the logging provided by JDK by adding the @Log annotation to the Employee class. Afterward, we’ll add the logEmp method:
让我们通过向Employee类添加@Log注解来启用JDK提供的日志记录。之后,我们将添加logEmp方法。
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对象上调用logEmp方法将在控制台显示日志。
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.
同样地,@Commons注释可用于添加Apache Commons日志支持。@Log4j可用于Apache Log4j 1.x的日志支持,@Log4j2可用于Apache Log4j 2.x。最后,使用@Slf4j来添加Simple Logging Facade for Java支持。
5. Conclusion
5.总结
In this tutorial, we’ve explored the concept of metaprogramming in Groovy.
在本教程中,我们已经探讨了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.
同时,我们还探索了Groovy中可用的其他方便的注解,以使代码更加简洁和动态。
As usual, the code implementations for this article are available over on GitHub.
像往常一样,本文的代码实现可以在GitHub上获得超过。