程式扎記: [ In Action ] The Groovy basics - Groovy’s place in the Java environment

標籤

2013年12月24日 星期二

[ In Action ] The Groovy basics - Groovy’s place in the Java environment

Preface: 
Behind the fun of Groovy looms the world of Java. We will examine how Groovy classes enter the Java environment to start with, how Groovy augments the existing Java class library, and finally how Groovy gets its groove: a brief explanation of the dynamic nature of Groovy classes. 

My class is your class: 
“Mi casa es su casa.” My home is your home. That’s the Spanish way of expressing hospitality. Groovy and Java are just as generous with each other’s classes. All Groovy code runs inside the Java Virtual Machine (JVM) and is therefore bound to Java’s object model. Regardless of whether you write Groovy classes or scripts, they run as Java classes inside the JVM. You can run Groovy classes inside the JVM two ways: 
  • You can use groovyc to compile *.groovy files to Java *.class files, put them on Java’s classpath, and retrieve objects from those classes via the Java classloader.

  • You can work with *.groovy files directly and retrieve objects from those classes via the Groovy classloader. In this case, no *.class files are generated, but rather class objects—that is, instances of java.lang.Class. In other words, when your Groovy code contains the expression new MyClass() , and there is aMyClass.groovy file, it will be parsed, a class of type MyClass will be generated and added to the classloader, and your code will get a new MyClass object as if it had been loaded from a *.class file.

  • These two methods of converting *.groovy files into Java classes are illustrated in figure 2.7. Either way, the resulting classes have the same format as classic Java classes. Groovy enhances Java at the source code level but stays identical at the byte-code level
     

    GDK: the Groovy library 
    Groovy’s strong connection to Java makes using Java classes from Groovy and vice versa exceptionally easy. Because they are both the same thing, there is no gap to bridge. In our code examples, every Groovy object is instantly a Java object. This has an enormous benefit for Java programmers, who can fully leverage their knowledge of the Java libraries. Consider a sample string in Groovy: 
    Because this is a java.lang.String , Java programmers knows that they can use JDK’s String.startsWith method on it: 
    1. if ('Hello World!'.startsWith('Hello')) {  
    2.   // Code to execute if the string starts with 'Hello'  
    3. }  
    The library that comes with Groovy is an extension of the JDK library. It provides some new classes (for example, for easy database access and XML processing), but it also adds functionality to existing JDK classes. This additional functionality is referred to as the GDK, and it provides significant benefits in consistency, power, and expressiveness. 

    One example is the size method as used in the GDK. It is available on everything that is of some size: strings, arrays, lists, maps, and other collections. Behind the scenes, they are all JDK classes. This is an improvement over the JDK, where you determine an object’s size in a number of different ways, as listed in table 2.1. 
     

    Groovy can play this trick by funneling all method calls through a device called MetaClass. This allows a dynamic approach to object orientation, only part of which involves adding methods to existing classes. You’ll learn more about MetaClass in the next section. 

    The Groovy lifecycle: 
    Although the Java runtime understands compiled Groovy classes without any problem, it doesn’t understand .groovy source files. More work has to happen behind the scenes if you want to load .groovy files dynamically at runtime. Let’s dive under the hood to see what’s happening. 

    Groovy syntax is line oriented, but the execution of Groovy code is not. Unlike other scripting languages, Groovy code is not processed line-by-line in the sense that each line is interpreted separately. Instead, Groovy code is fully parsed, and a class is generated from the information that the parser has built. The generated class is the binding device between Groovy and Java, and Groovy classes are generated such that their format is identical to Java bytecode. 

    Inside the Java runtime, classes are managed by a classloader. When a Java classloader is asked for a certain class, it loads the class from the *.class file, stores it in a cache, and returns it. Because a Groovy-generated class is identical to a Java class, it can also be managed by a classloader with the same behavior. The difference is that the Groovy classloader can also load classes from *.groovy files (and do parsing and class generation before putting it in the cache). 

    Groovy can at runtime read *.groovy files as if they were *.class files. The class generation can also be done before runtime with the groovyc compiler. The compiler simply takes *.groovy files and transforms them into *.class files using the same parsing and class-generation mechanics. 

    Groovy class generation at work 
    Suppose we have a Groovy script stored in a file named MyScript.groovy, and we run it via groovy MyScript.groovy . The following are the class-generation steps, as shown previously in figure 2.7: 
    1. The file MyScript.groovy is fed into the Groovy parser.
    2. The parser generates an Abstract Syntax Tree (AST) that fully represents all the code in the file.
    3. The Groovy class generator takes the AST and generates Java bytecode from it. Depending on the file content, this can result in multiple classes. Classes are now available through the Groovy classloader.
    4. The Java runtime is invoked in a manner equivalent to running java MyScript .

    Figure 2.8 shows a second variant, when groovyc is used instead of groovy . This time, the classes are written into *.class files. Both variants use the same class-generation mechanism. Classes are always fully constructed before runtime and do not change while running
     

    Groovy is dynamic 
    What makes dynamic languages so powerful is the ability to seemingly modify classes at runtime—for example to add new methods. But as you just learned, Groovy generates classes once and cannot change the bytecode after it has been loaded. How can you add a method without changing the class? The answer is simple but delicate. 

    The bytecode that the Groovy class generator produces is necessarily different from what the Java compiler would generate—not in format but in content. Suppose a Groovy file contains a statement like foo . Groovy doesn’t generate byte-code that reflects this method call directly, but does something like: 
    1. getMetaClass().invokeMethod(this"foo", EMPTY_PARAMS_ARRAY)  
    That way, method calls are redirected through the object’s MetaClass. This MetaClass can now do tricks with method invocations such as intercepting, redirecting, adding/removing methods at runtime, and so on. This principle applies to all calls from Groovy code, regardless of whether the methods are in other Groovy objects or are in Java objects. Remember: There is no difference. 

    A second option of dynamic code is putting the code in a string and having Groovy evaluate it. You will see how this works in chapter 11. Such a string can be constructed literally or through any kind of logic. But beware: You can easily get overwhelmed by the complexity of dynamic code generation. 

    Here is an example of concatenating two strings and evaluating the result: 
    1. def code = '1 + '  
    2. code += System.getProperty('os.version')  
    3. println code            // Prints “1 + 5.1”  
    4. println evaluate(code)  // Prints “6.1”  
    Note that code is an ordinary string! It happens to contain '1 + 5.1' , which is a valid Groovy expression (a script, actually). Instead of having a programmer write this expression (say, println 1 + 5.1 ), the program puts it together at runtime! The evaluate method finally executes it. 

    Wait—didn’t we claim that line-by-line execution isn’t possible, and code has to be fully constructed as a class? How can code then be executed? The answer is simple. Remember the left-hand path in figure 2.8? Class generation can transparently happen at runtime. The only news is that the class-generation input can also be a string like code rather than a *.groovy file.

    沒有留言:

    張貼留言

    網誌存檔

    關於我自己

    我的相片
    Where there is a will, there is a way!