preface
In the last article, I mainly explained Detailed explanation of Groovy classes, methods and closures, In this article, we will explain Groovy's dynamic features and metaprogramming in detail.
1. Groovy dynamic features
Speaking of dynamic characteristics, in the first article Understand Gradle and automated build In, it is just a brief introduction without detailed explanation. Here we should start with the difference between dynamic language and static language:
-
Dynamically typed language
Dynamic type language refers to the language that does data type checking only during operation, that is, when programming with dynamic type language, you don't need to specify the data type for the variable. The language will record the data type internally when you assign a value to the variable for the first time. Python and Ruby are typical dynamically typed languages -
Statically typed language
Static type language is just the opposite of dynamic type language. Its data types are checked during compilation, that is, the data types of all variables should be declared when writing programs. C/C + + is a typical representative of static type language. Other static type languages include c#, Java, etc.
Various types of languages
After a lot of theories are finished, let's go straight to the happy code rolling session
Let's first look at the familiar java code
public class JavaTest2 { static class User { String userName = "hqk"; int age; void setName(String name) { System.out.println("setName(String name)"); this.userName = name; } void setName(Object name) { System.out.println("setName(Object name)"); this.userName = name.toString(); } } public static void main(String[] args) { User user=new User(); user.setName(123); user.setName("zzz"); } }
This is a very simple piece of java code. We can see that two different types of data sources are passed in at the function entry. In order to ensure that the code does not report errors, the method setName is overloaded, but the final userName attribute type is String. In other words, when a variable is defined (before running), its type has been determined. If it is not satisfied, the compilation will not let you pass.
Then continue to look at Groovy we are learning now.
class User { def userName = 'hqk' String age void setName(String name) { println "setName(String name)" this.userName = name } void setName(Object name) { println "setName(Object name)" this.userName = name } } def user = new User() println "Before assignment" println user.userName.class println "\n" println "Assign as object type" user.userName = new Object() println user.userName.class println "\n" println "Assign as int type" user.userName = 123 println user.userName.class println "\n" println "Assign as User type" user.userName = new User() println user.userName.class println "\n"
We can see that the same logic is also implemented here in Groovy. After different values are assigned, the type of the variable is printed in turn. In this way, there is no error message when writing the code. Run to see the effect.
Before assignment class java.lang.String Assign as object type class java.lang.Object Assign as int type class java.lang.Integer Assign as User type class com.zee.gradle.groovy.User
Because Groovy can be directly assigned through the object. Variable, it is not assigned through the defined setName method for the time being. However, it can be seen that the userName variable defined here through def will change with the change of value type, and it is dynamic. Here is the result of direct assignment. Let's call the setName method just like the java method.
println "The following will be passed setName Method assignment\n" Object name = "hqk" println "definition Object Variable with value String" println name.class user.setName(name) println user.userName.class println "\n" println "definition Object Variable with value int" name = 123 println name.class user.setName(name) println user.userName.class
Here, a variable name of Object type is defined. First, the value of String type is assigned, and the corresponding value is assigned through the setName method; Then assign the variable name of Object type to the value of int type, and assign the corresponding value again through the setName method. Let's see how it works.
The following will be passed setName Method assignment definition Object Variable with value String class java.lang.String setName(String name) class java.lang.String definition Object Variable with value int class java.lang.Integer setName(Object name) class java.lang.Integer
Now we see that the value of a Object type is changed when the different values are assigned, and then different setName methods are invoked.
Now the question arises. Since variables can be changed at will in Groovy, is Groovy, like JS, a weakly typed variable? With this problem in mind, let's look at Groovy code again.
class User { def userName = 'hqk' String age void setName(String name) { println "setName(String name)" this.userName = name } void setName(Object name) { println "setName(Object name)" this.userName = name } } def user = new User() user.age = "123" println user.age.class user.age = 123 println user.age.class user.age=new Object() println user.age.class
Here, I define the age variable in Class, and the type is fully confirmed as String type. However, when assigning values below, I assign values of different types, and finally print the assigned variable type. Next, let's see the operation effect.
class java.lang.String class java.lang.String class java.lang.String
Here we can see that when the type is determined, no matter how the assignment type changes, it will not change. This shows that Groovy is a strongly typed variable. When a variable is defined by def and Object, its own type will be automatically changed to the type of the corresponding assignment Object, not a weakly typed variable.
Here's a summary of Groovy's dynamic features:
- Use def/Object to define variables, and the type is determined by the type class assigned to it at run time.
- You can use MOP for metaprogramming
MOP metaprogramming is mentioned here, so it is necessary to explain MOP metaprogramming in detail.
2. Groovy metaprogramming
Speaking of metaprogramming, let's talk about what pure Java can do:
In Java, you can dynamically obtain class properties, methods and other information at runtime through reflection, and then reflect the call. But you can't add attributes, methods, and behaviors directly into it. (dynamic bytecode technologies such as ASM and javassist are needed to dynamically modify classes)
In Groovy, we can directly use MOP for meta programming. We can dynamically add or change class methods and behaviors based on the current state of the application. For example, a method is not implemented in a Groovy class. The specific operation of this method is controlled by the server. Use metaprogramming to dynamically add methods for this class, or replace the original implementation, and then call it.
In a word, Groovy can do what Java can do, but Java can't do what Groovy can do. What Groovy can do is dynamic modification and dynamic injection. Dynamic modification involves interception. So let's explain it in detail from the two aspects of Groovy interception and injection.
2.1 Groovy interception
2.1.1 interception through GroovyInterceptable
class Person implements GroovyInterceptable { def func() { System.out.println "I have a dream!" } @Override Object invokeMethod(String name, Object args) { //println "invokeMethod" System.out.println "$name invokeMethod" //respondsTo(name) / / judge whether the method exists if (metaClass.invokeMethod(this, 'respondsTo', name, args)) { System.out.println "$name Method exists" System.out.println "$name Before execution.." metaClass.invokeMethod(this, name, args) System.out.println "$name After execution.." } } } new Person().func()
Code parsing
As soon as I see this code, isn't it what I explained before Detailed explanation of static agent and dynamic agent It's as like as two peas buddy, but only by Java. Let's continue to see the operation effect:
func invokeMethod func Method exists func Before execution.. I have a dream! func After execution..
Note: if you want to intercept in this way, you must implement the GroovyInterceptable interface and override the invokeMethod method. This method is too troublesome. Is there a way to intercept without implementing this interface? So there is the second
2.1.2 method interception through MetaClass
class Person3 { def func() { System.out.println "I have a dream!" } } def person3 = new Person3() person3.func() // Here is a method that intercepts an object person3.metaClass.func = { println "I have a new dream !!!" } person3.func()
Operation effect
I have a dream! I have a new dream !!!
It can be seen from here that the logic in the corresponding method can be modified directly through metaClass. It is equivalent to:
class Person3 { def func() { System.out.println "I have a dream!" } } def person3 = new Person3() // Equivalence and implementation of interception interface person3.metaClass.invokeMethod = { String name, Object args ->// invokeMethod(String name, Object args) println "$name Intercepted" } person3.func() new Person3().func() new Person3().func() new Person3().func() new Person3().func() new Person3().func()
Operation effect
func Intercepted I have a dream! I have a dream! I have a dream! I have a dream! I have a dream!
We found that it was only intercepted once and did not achieve global interception. What should we do to achieve global interception?
class Person3 { def func() { System.out.println "I have a dream!" } } def person3=new Person3() person3.func() Person3.metaClass.invokeMethod = { String name, Object args ->// invokeMethod(String name, Object args) println "$name Intercepted" } person3=new Person3() person3.func() new Person3().func() new Person3().func() new Person3().func() new Person3().func() new Person3().func()
Here, we instantiate and call the corresponding method many times before and after interception. Now let's see the effect
I have a dream! func Intercepted func Intercepted func Intercepted func Intercepted func Intercepted func Intercepted
Here we can see that the code before interception and before operation will become the latest logic after interception. So far, we have intercepted the code created by ourselves. Can the system class intercept it?
String.metaClass.invokeMethod = { String name, Object args -> println "String.metaClass.invokeMethod" MetaMethod method = delegate.metaClass.getMetaMethod(name) if (method != null && name == 'toString') { "You were intercepted by me and wanted to toString" } } println "hqk".toString()
Here we see that the toString method of String is directly intercepted to see the running effect
String.metaClass.invokeMethod You were intercepted by me and wanted to toString
Here we see that instead of printing the string we want, we print the intercepted string. At this point, the Groovy interception method is almost finished. Now it's time to explain Groovy injection.
2.2 Groovy injection
2.2.1 using MetaClass to inject methods
class Person4 { def func() { System.out.println "I have a dream!" } } // Before injection: org.codehaus.groovy.runtime.HandleMetaClass println Person4.metaClass Person4.metaClass.newFunc = { println "newFunc call" //Method returns a string "After injection, it can still be accessed later" } // After injection: groovy.lang.ExpandoMetaClass println Person4.metaClass def person=new Person4() //After injection, try whether the injected method can be called println person.newFunc()
Here, we see that there is no newFunc method in person 4, so we inject a new method newFunc through metaClass and implement the corresponding logic. Let's see how it works
org.codehaus.groovy.runtime.HandleMetaClass@ea27e34[groovy.lang.MetaClassImpl@ea27e34[class com.zee.gradle.groovy.Person4]] groovy.lang.ExpandoMetaClass@27a0a5a2[class com.zee.gradle.groovy.Person4] newFunc call After injection, it can still be accessed later
Here, we can see that the Class type before and after injection has changed, and its Class type has changed to groovy.lang.ExpandoMetaClass. Can we implement injection directly through ExpandoMetaClass?
2.2.2 using groovy.lang.ExpandoMetaClass to inject methods
class Person4 { def func() { System.out.println "I have a dream!" } } def emc = new ExpandoMetaClass(Person4) emc.func2 = { println "func2" } emc.initialize() Person4.metaClass = emc new Person4().func2()
Here, the ExpandoMetaClass object is directly instantiated, and then a series of operations are carried out through the object. See how it works
func2
The injected method can still be called normally
2.2.3 using classified injection method (writing method I)
class StringUtils { static def isNotEmpty(String self) { println "isNotEmpty" self != null && self.length() > 0 } } class StringUtils2 { static def isNotEmpty(Integer self) { println "isNotEmpty2" //self != null && self.length() > 0 //Because println does not return a value, it returns null here, so it will be printed as null below } } use(StringUtils, StringUtils2) { println "".isNotEmpty() println 1.isNotEmpty() }
Operation effect
isNotEmpty false isNotEmpty2 null
Here, the corresponding tool class is defined, which has the corresponding non empty judgment logic. The methods in these two classes are directly injected into the corresponding system class (String/Integer) through use. Then you can directly call the corresponding injected method in the closure.
In addition, there are other ways?
2.2.4 using classified injection method (writing 2)
@Category(Object) class StringUtils2 { def isNotEmpty() { println "isNotEmpty2" if (this instanceof Integer){ println "Digital type judgment" return true } println "Non numeric type judgment" this != null && this.length() > 0 } } use(StringUtils2) { println "".isNotEmpty() println "\n" println 1.isNotEmpty() }
Here we see that the @ Category annotation is used, and an Object is placed in it, indicating that any type of variable has a new method isNotEmpty in the use closure. See how it works
isNotEmpty2 Non numeric type judgment false isNotEmpty2 Digital type judgment true
Here we can see that annotation can still be successfully injected, but these two methods can only be used in the use closure, which is usually used for temporary judgment and thrown away when used up.
Because Groovy is a dynamic language, if there are no corresponding methods and member variables in the object during program operation, it will certainly report an error if you force access outside. So how to avoid it?
2.2.5 assignment of initial values to nonexistent methods and variables
class Person5 { def username // get a non-existent variable def propertyMissing(String name) { println "propertyMissing" if (name == 'age') { "19" // Return to default } } // set non-existent variables def propertyMissing(String name, def arg) { println "propertyMissing ${name} : arg${arg}" return "default"// Give return value } // Initializing a method that does not exist def methodMissing(String name, def arg) { println "$name methodMissing" if (name.startsWith 'getFather') { "zzzz" } } } def p = new Person5() println p.age = 12 println "\n" println p.age println "\n" println p.getFather()
Operation effect
propertyMissing age : arg12 12 propertyMissing 19 getFather methodMissing zzzz
It can be seen here that when there are no corresponding methods and variables in the object, you can avoid error reporting in this way, and you can also do different logical processing for different situations.
3. Concluding remarks
Because most of Gradle is written in Groovy language, learning Gradle must master some Groovy syntax. So far, the whole Groovy grammar has been explained. In the next article, we'll really start Gradle's explanation.