为了账号安全,请及时绑定邮箱和手机立即绑定

Groovy核心类源码讲解(终)

前两篇groovy我们从源码的角度分析了,groovy对java做了那些扩展和这些扩展的实现的原理,今天这篇文章,我们来讲解groovy源码分析中的最后一部分,来分析一下groovy中最后一部分对java做了那些扩展。本章的内容相对前两章来说,就简单的多了。
代码部分就是下图中的部分:
图片描述
前两篇文章,我们分析的都是org包中的代码,今天要分析的主要代码是在groovy包下的,我们由易到难依次来分析一下这个包中的源码。这个包下大家如果打开源码的话,可以看到,其中的包非常的多,但大部分都是我们在写gradle或者groovy脚本时用不到的,我们最常用到的包就是:json xml lang这三个包,我们重点分析这三个包下的一些核心类,来看一下他们的作用。
第一部分:json package这个package大家一看就可以知道,是提供对json格式的文件,数据等进行一些操作的,比java中确实要强大的多。package中最核心的类又是:JsonSlurper,JsonBuilder这两个类,通过类的名字我们也能够知道,一个类是用来将json格式的数据转化为Object,后一个是将Object转化为json串,下面我们首先来看一下JsonSluper的核心代码。

public class JsonSlurper {
  private int maxSizeForInMemory = 2000000;
  private boolean chop = false;
  private boolean lazyChop = true;
  private boolean checkDates = true;
  private JsonParserType type;

  public JsonSlurper() {
    this.type = JsonParserType.CHAR_BUFFER;
  }

//最常用的方法,将json串解析为Object.
 public Object parseText(String text) {
    if(text != null && !"".equals(text)) {
      return this.createParser().parse(text);
    } else {
      throw new IllegalArgumentException("Text must not be null or empty");
    }
  }

//从此方法以后,都是将一些其它的Json数据来源直接转化为Object,
//注意所有的parse和上面的parseText方法中,都调用了createParser()这个方法
//下面我们就来重点分析这个方法
  public Object parse(Reader reader) {
    if(reader == null) {
      throw new IllegalArgumentException("Reader must not be null");
    } else {
      JsonParser parser = this.createParser();
      Object content = parser.parse(reader);
      return content;
    }
  }

   public Object parse(InputStream inputStream) {
    if(inputStream == null) {
      throw new IllegalArgumentException("inputStream must not be null");
    } else {
      JsonParser parser = this.createParser();
      Object content = parser.parse(inputStream);
      return content;
    }
  }
}

//根据我们不同的数据来源,调用不同的解析器去解析,这里我们可以看到,总共有5种不同的解析器去处理不同的数据,
//所以才说,groovy对json处理做了非常强的扩展
 private JsonParser createParser() {
    switch(null.$SwitchMap$groovy$json$JsonParserType[this.type.ordinal()]) {
    case 1:
      return new JsonParserLax(false, this.chop, this.lazyChop, this.checkDates);
    case 2:
      return new JsonParserCharArray();
    case 3:
      return new JsonParserUsingCharacterSource();
    case 4:
      return new JsonFastParser(false, this.chop, this.lazyChop, this.checkDates);
    default:
      return new JsonParserCharArray();
    }
  }

好,到这里,我们就不再分析他具体的各个解析器是如何去处理的了,大家有兴趣的可以自己去看源码,对我们开发人员来说,就重要的就是这个入口类,JsonSlurper。好下面我简单贴一下这个类的最常见的用法:

  def getNetworkData(String url) {
    //发送http请求
    def connection = new URL(url).openConnection()
    connection.setRequestMethod('GET')
    connection.connect()
    def response = connection.content.text
    //将json转化为实体对象
    def jsonSluper = new JsonSlurper()
    return jsonSluper.parseText(response)
}

以上代码也很简单,就是通过http请求拿到服务端的json数据,然后通过JsonSlurper来解析成Object供我们去更方便的使用。我们系列文章中的示例代码均来自于我的gradle实战课程,大家可以去系统的学习一下。所以在groovy中,我们就不再需要引入一些第三方的json解析库了。非常方便。下面我们再来看一下另一个JsonBuilder类,核心代码如下:

public class JsonBuilder extends GroovyObjectSupport implements Writable {
 //要被转化的对象 
 private Object content;

  public JsonBuilder() {
  }

  public JsonBuilder(Object content) {
    this.content = content;
  }

 //转化成无json格式的字符串
 public String toString() {
    return JsonOutput.toJson(this.content);
  }

 //转化成带json格式的字符串
  public String toPrettyString() {
    return JsonOutput.prettyPrint(this.toString());
  }

上面,我只列出了最核心和常用到的两个方法,我们可以看到,其实最终调用的都是JsonOutput类中对应的方法。下面我们就把JsonOutput中的核心方法列出:

//最终会调用到的方法,此方法通过递归来完成Json格式的转化
private static void writeObject(Object object, CharBuf buffer) {
    if(object == null) {
      buffer.addNull();
    } else {
      Class<?> objectClass = object.getClass();
      if(CharSequence.class.isAssignableFrom(objectClass)) {
        writeCharSequence((CharSequence)object, buffer);
      } else if(objectClass == Boolean.class) {
        buffer.addBoolean(((Boolean)object).booleanValue());
      } else if(Number.class.isAssignableFrom(objectClass)) {
        writeNumber(objectClass, (Number)object, buffer);
      } else if(Date.class.isAssignableFrom(objectClass)) {
        writeDate((Date)object, buffer);
      } else if(Calendar.class.isAssignableFrom(objectClass)) {
        writeDate(((Calendar)object).getTime(), buffer);
      } else if(Map.class.isAssignableFrom(objectClass)) {
        writeMap((Map)object, buffer);
      } else if(Iterable.class.isAssignableFrom(objectClass)) {
        writeIterator(((Iterable)object).iterator(), buffer);
      } else if(Iterator.class.isAssignableFrom(objectClass)) {
        writeIterator((Iterator)object, buffer);
      } else if(objectClass == Character.class) {
        buffer.addJsonEscapedString(Chr.array(new char[]{((Character)object).charValue()}));
      } else if(objectClass == URL.class) {
        buffer.addJsonEscapedString(object.toString());
      } else if(objectClass == UUID.class) {
        buffer.addQuoted(object.toString());
      } else if(objectClass == JsonOutput.JsonUnescaped.class) {
        buffer.add(object.toString());
      } else if(Closure.class.isAssignableFrom(objectClass)) {
        writeMap(JsonDelegate.cloneDelegateAndGetContent((Closure)object), buffer);
      } else if(Expando.class.isAssignableFrom(objectClass)) {
        writeMap(((Expando)object).getProperties(), buffer);
      } else if(Enumeration.class.isAssignableFrom(objectClass)) {
        List<?> list = Collections.list((Enumeration)object);
        writeIterator(list.iterator(), buffer);
      } else if(objectClass.isArray()) {
        writeArray(objectClass, object, buffer);
      } else if(Enum.class.isAssignableFrom(objectClass)) {
        buffer.addQuoted(((Enum)object).name());
      } else {
        Map properties;
        if(File.class.isAssignableFrom(objectClass)) {
          properties = getObjectProperties(object);
          Iterator iterator = properties.entrySet().iterator();

          while(iterator.hasNext()) {
            Entry<?, ?> entry = (Entry)iterator.next();
            if(entry.getValue() instanceof File) {
              iterator.remove();
            }
          }

          writeMap(properties, buffer);
        } else {
          properties = getObjectProperties(object);
          writeMap(properties, buffer);
        }
      }
    }
  }

其实,如果大家看过Java中的gson等库以后,他们基本都是这样的处理方式基本。

接着,我们来看一下xml包,这个package包中最重要的类是:MarkupBuilder.从类名我们可以知道,这个类是用来生成标记格式类型数据,例如:XML,HTML等格式的数据。
这个类的源码我们就不看了,因为他依赖了groovy中的元编程,我们最后会讲groovy的元编程,知道了源编程以后,你就会明白MarkupBuilder的原理。下面我们简单看一下这个类的用法,也非常的简单,代码如下,同样取自于我们的gradle实战教程,

def sw = new StringWriter()
def xmlBuilder = new MarkupBuilder(sw) //用来生成xml数据的核心类
def langs = new Langs()
xmlBuilder.langs(type: langs.type, count: langs.count,mainstream: langs.mainstream) {
    //遍历所有的子结点
    langs.languages.each { lang ->
        language(flavor: lang.flavor,
                version: lang.version, lang.value)
    }
}
println sw
//对应xml中的langs结点
class Langs {
    String type = 'current'
    int count = 3
    boolean mainstream = true
    def languages = [
            new Language(flavor: 'static',
                    version: '1.5', value: 'Java'),
            new Language(flavor: 'dynamic',
                    version: '1.3', value: 'Groovy'),
            new Language(flavor: 'dynamic',
                    version: '1.6', value: 'JavaScript')
    ]
}
//对应xml中的languang结点
class Language {
    String flavor
    String version
    String value
}

下面,我们来稍微解释一下这块代码,最下面的两个实体类代表我们要动态写到xml格式中的数据,核心方法就是这段:

//调用MarkupBuilder中的langs方法去生成最外成的<langs>结点,
xmlBuilder.langs(type: langs.type, count: langs.count,mainstream: langs.mainstream) {
    //调用langs下的languages方法生成二级结点:<languages>
    langs.languages.each { lang ->
        language(flavor: lang.flavor,
                version: lang.version, lang.value)
    }
}

经过上面的代码,我们最终生成的xml格式的数据就是:

<langs type='current' count='3' mainstream='true'>
 <language flavor='static' version='1.5'>Java</language>
 <language flavor='dynamic' version='1.6.0'>Groovy</language>
 <language flavor='dynamic' version='1.9'>JavaScript</language>
 </langs>

你可能会问,MarkupBuilder类中真的有langs和languages这样的方法吗,显然没有,不然他里面的有无数个方法。原理其实就是在运行时通过元编程去动态的为MarkupBuilder创建对应结点的方法。所以大家了解元编程以后就知道是怎么回事了。

那元编程是怎么回事呢,我们先来看一下官方对元编程的定义:元编程意味着编写能够操作程序的程序。这个概念呢太难理解了。我来问大家一个问题,我们知道在Java中,使用反射可以在运行时探索程序的结构,以及程序的类和类的方法,然后我们还是无法在运行时去修改一个对象的类型或是为对象或类动态的添加方法,甚至在运行时去动态的生成一个类。但是基于Groovy的元编程,我们可以很轻松的为类动态的添加方法等,上面讲到的MarkupBuilder就是一个例子。了解了元编程能做什么以后,下面我们就来分析一下Groovy如何实现的元编程。而元编程相关的类呢,就都在lang包中

首先,编程我们都知道是创建一些类,然后为类去写一些属性和方法,那元代表什么呢,其实,元就是一个特殊的属性,我们到源码里来看一下是那个属性。

public interface GroovyObject {
  Object invokeMethod(String var1, Object var2);

  Object getProperty(String var1);

  void setProperty(String var1, Object var2);

  MetaClass getMetaClass();

  void setMetaClass(MetaClass var1);
}

这个类是Groovy最最基础的类,所有的类都实现了这一接口,我们可以看到一个getMetaClass和setMetaClass ,而MetaClass就是我们就所的元,通过操作MetaClass,就可以动态的在运行时,去改变类中的属性和方法所以我们可以得出结论,groovy中的所有类,都可以为其动态的去操作。因为这是个接口,下面我们看一下他的最重要的一个实现类:

public abstract class GroovyObjectSupport implements GroovyObject {
  //所有类的MetaClass都是通过InvokerHelper获取到。
  private transient MetaClass metaClass = InvokerHelper.getMetaClass(this.getClass());

  public GroovyObjectSupport() {
  }

 //从元类中获取属性
  public Object getProperty(String property) {
    return this.getMetaClass().getProperty(this, property);
  }
//为元类设置属性
  public void setProperty(String property, Object newValue) {
    this.getMetaClass().setProperty(this, property, newValue);
  }
//调用元类中的方法
  public Object invokeMethod(String name, Object args) {
    return this.getMetaClass().invokeMethod(this, name, args);
  }
//获取到当前类的元类
  public MetaClass getMetaClass() {
    if(this.metaClass == null) {
      this.metaClass = InvokerHelper.getMetaClass(this.getClass());
    }

    return this.metaClass;
  }

  public void setMetaClass(MetaClass metaClass) {
    this.metaClass = metaClass;
  }
}

所以,到这里大家应该大体可以猜到,正是因为我们所有的类都有一个MetaClass属性,所以我们的元编程并不会去修改我们的真正定义的类,而实际是去动态的操作MetaClass中的内容,例如我们上面的MarkupBuilder,那些langs和language方法并不是真正的添到加了MarkupBuilder类中,而是那个对象的MetaClass中。这下大家应该明白元编程到底改的是那里了。下面我们就看一下这个神秘的MetaClass中有那些东西:

public interface MetaClass extends MetaObjectProtocol {
  Object invokeMethod(Class var1, Object var2, String var3, Object[] var4, boolean var5, boolean var6);

  Object getProperty(Class var1, Object var2, String var3, boolean var4, boolean var5);

  void setProperty(Class var1, Object var2, String var3, Object var4, boolean var5, boolean var6);

  Object invokeMissingMethod(Object var1, String var2, Object[] var3);

  Object invokeMissingProperty(Object var1, String var2, Object var3, boolean var4);

  Object getAttribute(Class var1, Object var2, String var3, boolean var4);

  void setAttribute(Class var1, Object var2, String var3, Object var4, boolean var5, boolean var6);

  void initialize();

  List<MetaProperty> getProperties();

  List<MetaMethod> getMethods();

  ClassNode getClassNode();

  List<MetaMethod> getMetaMethods();

  int selectConstructorAndTransformArguments(int var1, Object[] var2);

  MetaMethod pickMethod(String var1, Class[] var2);
}

同样的,这也只是个接口。我们看到了这两个方法: List<MetaMethod> getMetaMethods()和List<MetaProperty> getProperties()这两个方法,我们想一想,这两方法可以返回整个元类中的Method和Property的集合,那我们在运行时,动态的往里添加方法,不就修改了MetaClass的内容了吗?好,我们就到他的一个重要的实现类中看一下他的实现:

private final Set<MetaMethod> newGroovyMethodsSet;
public List<MetaMethod> getMetaMethods() {
    return new ArrayList(this.newGroovyMethodsSet);
  }

这里只贴了实现类 MetaClassImpl类中的实现,就是将原来存在set集合中的MetaMethod返回,所以想要动态的为某一个类添加方法,只需要我们将一个方法封装成MetaMethod类型,然后添加到它的set中即可,当然,这是动态添加方法和属性的原理,在实际开发中,动态的添加是更加的简单的,完全无需了解底层的这些类。
好,了解了元编程的底层原理以后,我们来看一看,在使用的时候,如何去用。假设我们现在想为String增加一个方法,在Java里无论如何也是做不到的,但是在Groovy中确是很轻松的。代码如下:

 // 给String类添加了一个名为capitalize的方法
 String.metaClass.capitalize = { -> delegate[0].toUpperCase() + delegate[1..-1] }
 // 给String类添加了一个名为spaceCount的只读属性
 String.metaClass.getSpaceCount = { -> delegate.count(' ') }

 assert "this is groovy".capitalize() == "This is groovy"
 assert "this is not ruby".spaceCount == 3

来看一下,是不是非常的简单,其实这些新的方法就是添加到了String类的MetaClass中。

那么问题来了,Java中的String类并没有MetaClass这个属性啊,如何凭空捏造了一个MetaClass属性给Java中的类,此处是String,下面来给大家解答这个问题,先来看一张图,大家就明白了。如图:
图片描述
可以看到,是在一个叫MetaClassRegister类中会有一个Map,在这个Map中会为所有的Java类去存一个对应的MetaClass,而所有的Groovyo类中已经有这个属性了,所以不必单独去保存。而MetaClassRegister类的实现类MetaClassRegisterImpl我们上篇文章也提到过,可见这个类在整个Groovy对Java的扩展过种中起到一个非常重要的作用。
下面我们就通过源码来看一下,他到底是如何在map里保存各个Java类的MetaClass的。
代码如下:

//MetaClassRegistryImpl中的方法
 public final MetaClass getMetaClass(Class theClass) {
     return ClassInfo.getClassInfo(theClass).getMetaClass();
 }

所以真正的逻辑是在ClassInfo这个类中,我们来看一下他的getClassInfo方法的实现:

public static ClassInfo getClassInfo(Class cls) {
      // localMapRef的类型是WeakReference<ThreadLocalMapHandler>
     //这是一个缓存类 
     ThreadLocalMapHandler handler = localMapRef.get();
      SoftReference<LocalMap> ref=null;
      //总是先从缓存中取MetaClass
      if (handler!=null) ref = handler.get();
      LocalMap map=null;
      if (ref!=null) map = ref.get();
      if (map!=null) return map.get(cls);
      // 只有当localMapRef或ref已被回收时,才会调用下面的代码
     // globalClassSet的类型是ClassInfoSet
     return (ClassInfo) globalClassSet.getOrPut(cls,null);
 }

ClassInfo中使用静态的globalClassSet存储Metaclass,不过并不是直接存储Class到Metaclass的映射,而是Class到ClassInfo的映射,而ClassInfo则包含了Groovy中跟Class相关的内部属性,其中就包括了Metaclass。 globalClassSet的类型是ClassInfoSet,而ClassInfoSet类继承了ManagedConcurrentMap<Class,ClassInfo>类型,这样我们就找到了图中的Map,就是我们这里的ClassInfoSet.也就是Java类的MetaClass都是存放在ClassInfoSet这个类里面了。
上面的ThreadLocalMapHandler是一个缓存工具类,下面我们来看下这个缓存类的实现:

private static class ThreadLocalMapHandler extends ThreadLocal<SoftReference<LocalMap>> {
 2     SoftReference<LocalMap> recentThreadMapRef; // 最近一次使用的引用
 3     
 4     public SoftReference<LocalMap> get() {
 5         SoftReference<LocalMap> mapRef = recentThreadMapRef;
 6         LocalMap recent = null;
 7         if (mapRef!=null) recent = mapRef.get();
 8         // 如果最近一次使用的引用就是由当前进程创建的,则直接返回该引用,否则才调用ThreadLocal的get方法。这样可以减少在ThreadLocal.get()中查找Map的消耗
 9         if (recent != null && recent.myThread.get() == Thread.currentThread()) {
10             return mapRef;
11         } else {
12             SoftReference<LocalMap> ref = super.get();
13             recentThreadMapRef = ref; // 更新最近一次使用的引用
14             return ref;
15         }
16     }
17 }

大家有兴趣的可以继续分析一下ManagedConcurrentMap和ClassInfoSet的源码。这里由于不是关键路径上的代码,就不再带大家读了。
最后总结一下就是:在MetaClassRegistryImpl.getMetaClass(Class)方法中,查找到Class对应的ClassInfo后,再调用ClassInfo的getMetaClass方法获得Class对应的Metaclass。

总的来说,在各种情况下,Metaclass的存放方式如下: POGO Per-instance Metaclass:直接存放在对象的metaClass字段中。POJO Per-instance Metaclass:对象到Metaclass的映射关系存放在该对象的Class对应的ClassInfo中的一个ManagedConcurrentMap中。

好的,到这里我们groovy包中的重要源码呢,就分析完毕了,下面我们来回顾一下这三篇文章的核心内容。第一篇文章我们主要分析了groovy中都为Java的类和对象扩展了那些常用方法,主要就是所有的XXXGroovyMethods类中的方法,总共有1100多个。紧接着第二篇我们讲解了为什么这些XXXGroovyMethods中的方法能够作用到Java中的对象上,分别是在编译时会为各方法生成包装类,然后在运行时由MetaClassRegisterImpl类去负责创建对应类的Proxy,最后由对应的Proxy类去调用到那些方法。第三篇文章,我们分析了另一部分groovy包中对java的扩展,我们由易到难分别讲了groovy中的json和XML数据的处理。然后由他们引出了groovy对Java最大的扩展,那就是元编程,通过对groovy元编程中几个核心类的引出和源码的分析,让大家更加清楚的了解groovy底层是如何实现元编程的,groovy元编程这块有非常多的用处,例如,通过元编程去动态创建方法,以及动态对方法进行拦截,方法合成等等,大家有兴趣的都可以深入的再去学习。

通过这三篇文章的学习,大家可以对groovy的核心代码及核心功能有一个深刻的认识,如果还没系统的学习过groovy,可通过我的实战课程去系统的学习一下groovy.

点击查看更多内容
7人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
移动开发工程师
手记
粉丝
1.1万
获赞与收藏
275

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消