2021SC@SDUSC
Groovy对Java功能性扩展(下)
接着,我们来看一下xml包,这个package包中最重要的类是:MarkupBuilder.从类名我们可以知道,这个类是用来生成标记格式类型数据,例如:XML,HTML等格式的数据。
这个类的源码依赖了groovy中的元编程,我们之后会讲groovy的元编程,知道了元编程以后,你就会明白MarkupBuilder的原理。下面我们简单看一下这个类的用法,也非常的简单,代码如下:
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创建对应结点的方法。所以大家了解元编程以后就知道是怎么回事了。
浅识Groovy元编程
那元编程是怎么回事呢,我们先来看一下官方对元编程的定义:元编程意味着编写能够操作程序的程序。我们知道在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这个属性,Groovy是如何凭空捏造了一个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>> {
SoftReference<LocalMap> recentThreadMapRef; // 最近一次使用的引用
public SoftReference<LocalMap> get() {
SoftReference<LocalMap> mapRef = recentThreadMapRef;
LocalMap recent = null;
if (mapRef!=null) recent = mapRef.get();
// 如果最近一次使用的引用就是由当前进程创建的,则直接返回该引用,否则才调用ThreadLocal的get方法。这样可以减少在ThreadLocal.get()中查找Map的消耗
if (recent != null && recent.myThread.get() == Thread.currentThread()) {
return mapRef;
} else {
SoftReference<LocalMap> ref = super.get();
recentThreadMapRef = ref; // 更新最近一次使用的引用
return ref;
}
}
}
有兴趣的可以继续分析一下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中。