groovy-lang 详细介绍,本文包含原理和测试两部分。
原理
groovy 与java
1、groovy 与java 最终都是以字节码的方式在JVM 上面执行。但是他们生产字节码的方式和内容不一样。
2、groovy 生成字节码
预编译模式:class 文件存在硬盘
直接模式: class 在内存
无论是预编译还是直接模式,groovy 源文件最终都是以字节码方式在JVM 上面执行,这样java 生成的字节码和groovy 生成的字节码在jvm 上面就可以相互调用了。当然java 也可以动态生成字节码

3、java 语法与groovy 语法
java - java语法分析AST — java 编译器 — bytecode
groovy - groovy 语法分析AST- groovy 编译器— bytecode
所以 java 语法与groovy 语法是不同的,groovy 语法可以兼容java 语法,虽然java 与groovy 最终都能生成字节码,并且是jvm 统一的格式,但是java 和groovy 生成的字节码内容是不一样,groovy生成的字节码是一个代理类。所有方法调用方式(内部实现)与普通java 不一样。这是gradle 实现groovy 自定义的基础
4、groovy 核心技术
- asm 字节码生成
- antlr 脚本语法分析
- groovy 核心类,帮助生成字节码
groovy 与class 关系
- groovy 脚本里面没有class 关键字声明,生成的class 会继承默认的groovy.lang.Script 对象,当然也可以设置编译选项,让生成的class 继承自定义对象。class 的名称与文件名称一样,class 还会添加main 和run 方法。groovy脚本还可以直接执行Script 里面定义的方法,方法所属对象为Script 对象。
-
groovy 只有一个class 声明,那么生成的class 与java 定义生成的class 一样
-
groovy 有多个class 声明,每个class 定义都会生产类,第一个类里面还有有一个main 方法定义
-
groovy 里面含有class 声明也含有 脚本代码,脚本代码会变成main class 执行。class 定义的类会生成class 对象
Groovy 方法执行原理(重点)
1.所有groovy 脚本生成的class 都会实现 GroovyObject 接口,Groovy 里面所有方法调用都会通过 invokeMethod 来调用 ,这点说明与java 生成的字节码内容是不一样的。
public interface GroovyObject {
default Object invokeMethod(String name, Object args) {
return getMetaClass().invokeMethod(this, name, args);
}
default Object getProperty(String propertyName) {
return getMetaClass().getProperty(this, propertyName);
}
default void setProperty(String propertyName, Object newValue) {
getMetaClass().setProperty(this, propertyName, newValue);
}
MetaClass getMetaClass();
void setMetaClass(MetaClass metaClass);
}
-
MetaClass and MetaClassRegistry
1、MetaClassRegistry 存储了所有MetaClass 包含java 的,groovy 的
2、 GroovyObject 的invokeMethod 实际是由MetaClass 来执行的
MetaClass.png
MetaClass-GroovyObject.png
-
Groovy 方法执行流程
image.png
groovy 提供集成接口
1.GroovyShell 关注独立的 Script (一个Script) 代码片段,没有class。没有依赖
- GroovyClassLoader 用于动态编译及加载 Groovy 类
- GroovyScriptEngine 关注多个 Script 有依赖,处理 script 的依赖及重新加载
- CompilerConfiguration,设置groovy 编译选项,比如设置基类,设置默认导包,安全校验等等等,其他自定义操作
- Binding 应用程序与脚本之间进行值传递
测试
1、添加依赖
compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '3.0.3'
2、定义接口-Setting.java + DefaultSetting.java
public interface Setting {
public void pluginManagement(Closure clourse);
public void repositories(Closure clourse);
public void maven(Closure clourse);
public void url(String url);
public void plugins(Closure clourse);
public void include(String subproject);
public void settings(Closure clourse);
}
public class DefaultSetting implements Setting {
@Override
public void pluginManagement(Closure clourse) {
System.out.println("pluginManagement clourse"+clourse);
clourse.setDelegate(this);
clourse.call();
}
@Override
public void repositories(Closure clourse) {
clourse.setDelegate(this);
clourse.call();
}
@Override
public void maven(Closure clourse) {
System.out.println("maven clourse"+clourse);
}
@Override
public void url(String url) {
System.out.println(url);
}
@Override
public void plugins(Closure clourse) {
clourse.setDelegate(this);
clourse.call();
}
@Override
public void include(String subproject) {
System.out.println("include.."+subproject);
}
@Override
public void settings(Closure clourse) {
clourse.setDelegate(this);
clourse.call();
}
}
3、定义接口-Plugin.java +DefaultPlugin.java
public interface Plugin {
Plugin id(String id);
Plugin version(String id);
}
public class DefaultPlugin implements Plugin{
@Override
public Plugin id(String id) {
System.out.println("id--"+id);
return this;
}
@Override
public Plugin version(String version) {
System.out.println("version--"+version);
return this;
}
}
4、Groovy 调用 java-class
import com.github.yulechen.gradleconfig.DefaultPlugin
import com.github.yulechen.gradleconfig.DefaultSetting
import com.github.yulechen.gradleconfig.Plugin
import com.github.yulechen.gradleconfig.Setting
println "start groovy config"
Setting setting =new DefaultSetting();
Plugin p =new DefaultPlugin();
// 对应接口 public void pluginManagement(Closure clourse);
setting.pluginManagement {
// 对应接口 public void repositories(Closure clourse);
setting.repositories {
// 对应接口 public void maven(Closure clourse);
setting.maven {
// 对应接口 public void url(String url);
setting.url 'https://repo.spring.io/plugins-release'
}
}
}
setting.plugins {
// 对应接口 Plugin id(String id) + Plugin version(String id);
// 链式编程
p.id "com.gradle.enterprise" version "3.2"
p.id "io.spring.gradle-enterprise-conventions" version "0.0.2"
}
setting.include "spring-aop"
setting.include "spring-aspects"
setting.settings{
println "groovy setting"
}
println "end groovy config"
4、Java 执行groovy 脚本
public class Main {
public static void main(String[] args) throws URISyntaxException, IOException {
URL resource = Main.class.getResource("/setting.groovy");
GroovyShell shell = new GroovyShell();
Script script = shell.parse(resource.toURI());
Object run = script.run();
}
}
这样的方式与gradle 里面还有很大的差距
1、gradle 里面没有导包 import 这样的语句
2、gradle 里面没有new 对象,与setting.pluginManagement 这样的使用 而是 pluginManagement 会自动绑定到内置的对象上面。
解决这两个问题,还是需要从编译代码的角度入手,因为Groovy字节码一旦编译成功加载到内存中就不会改变了。
优化
依靠这两点可以解决
groovy 脚本里面没有class 关键字声明,生成的class 会继承默认的groovy.lang.Script 对象,当然也可以设置编译选项,让生成的class 继承自定义对象。class 的名称与文件名称一样,class 还会添加main 和run 方法。groovy脚本还可以直接执行Script 里面定义的方法,方法所属对象为Script 对象。
groovy 提供的编译接口 CompilerConfiguration,设置groovy 编译选项,比如设置基类,设置默认导包,安全校验等等等,其他自定义操作。
1、设置默认导包和设置编译脚本的基类
public static void main(String[] args) throws URISyntaxException, IOException {
CompilerConfiguration config =new CompilerConfiguration();
ImportCustomizer importCustomizer = new ImportCustomizer();
// 设置默认导包
importCustomizer.addImports(DefaultPlugin.class.getName())
.addImports(DefaultSetting.class.getName())
.addImports(Plugin.class.getName())
.addImports(Setting.class.getName());
config.addCompilationCustomizers(importCustomizer);
// 设置编译脚本依赖的基类
config.setScriptBaseClass(BaseScript.class.getName());
URL resource = Main.class.getResource("/setting.groovy");
GroovyShell shell = new GroovyShell();
Script script = shell.parse(resource.toURI());
Object run = script.run();
}
}
2、重写Script 基类,修改invokeMethod 方法,这是所有groovy 方法调用的入口,默认实现是依据传入的参数在MetaClass 中查找对应的方法执行,可以改变invokeMethod 查询方法的过程,实现对象动态绑定。
public abstract class BaseScript extends Script {
private Object bean=new DefaultSetting();
@Override
public Object invokeMethod(String name, Object args) {
MetaClass metaClass = getInterMetaClass();
System.out.println("other metaClass" + metaClass);
System.out.println("invokeMethod hook....");
// return super.invokeMethod(name, args);
return "haha";
}
private MetaClass getInterMetaClass() {
if (bean instanceof GroovyObject) {
return ((GroovyObject) bean).getMetaClass();
} else {
return GroovySystem.getMetaClassRegistry().getMetaClass(bean.getClass());
}
}
public void printHello(String name){
System.out.println("Script Innner Method.."+name);
}
}
3、修改groovy 脚本,这样的脚本就与gradle 里面配置的脚本一致了
println "start groovy config"
pluginManagement {
repositories {
maven { url 'https://repo.spring.io/plugins-release' }
}
}
plugins {
id "com.gradle.enterprise" version "3.2"
id "io.spring.gradle-enterprise-conventions" version "0.0.2"
}
include "spring-aop"
include "spring-aspects"
settings{
println "groovy setting"
}
println "end groovy config"
// 这个是BaseScript 里面自定的方法
// public void printHello(String name){
// System.out.println("Script Innner Method.."+name);
// }
printHello "sb"
代码:github
网友评论