关键词:
Groovy:MOP一文打尽
写在最前
Groovy已经不再是一门新出现的语言,而笔者是在2013年左右接触到它的,并且在2017年时,有机会尝试使用它编写了基于SpringBoot的后端项目。
但说来惭愧,在很长的一段时间里,我都没有系统的学习它。并且时至今日,我也 不推荐
大家再去 系统的学习
它,毕竟 使用它的机会越发地少了
,
但是我依旧认为大家有必要花费一些零碎的时间,快餐式的了解它。
这一篇讲MOP,之后还有一篇闭包
文章代码已发布于:GroovyWorkshop
为何产生编写Groovy系列的想法
一言以蔽之:“被刺激到了,很多事情不知其所以然”.
MetaObject Protocol 元对象协议
本文直接从MOP开始,忽略掉Groovy的大量基础部分,因为这些基础部分,基本和Java一致。
MOP的目标在于:运行期进行实时变化
,听起来有点像Java的反射,但是要比Java的反射更加强大。可以:
- 修改方法名、属性名,
- 动态增加类的方法、属性 等等
神奇的方法分配-- invokeMethod
和 methodMissing
首先我们简单了解一下Groovy的方法分配机制:
图片来自网络
本节内容,结果上看起来很神奇,但内容比较枯燥,并且对非语言使用者帮助不大,可以泛读,有个印象即可
invokeMethod 拦截示例
我们先看一个简单的例子:
class Demo1
def foo()
println 'foo'
def invokeMethod(String name, Object args)
return "unknown method $name($args.join(','))"
static void main(String[] args)
def demo = new Demo1()
demo.foo()
println demo.bar("A", "B")
如果是Java或者Kotlin,很明显,Demo1
中并没有 bar
方法,编译不能通过,但Groovy可以通过编译,得到运行结果:
> Task :Demo1.main()
foo
unknown method bar(A,B)
这就是所谓的 invokeMethod
,Groovy中设计了一个顶层接口:GroovyObject
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);
编译结果:
package osp.leobert.groovyworkshop.mop;
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.transform.Generated;
import org.codehaus.groovy.runtime.GStringImpl;
import org.codehaus.groovy.runtime.callsite.CallSite;
public class Demo1 implements GroovyObject
@Generated
public Demo1()
CallSite[] var1 = $getCallSiteArray();
super();
MetaClass var2 = this.$getStaticMetaClass();
this.metaClass = var2;
public Object foo()
CallSite[] var1 = $getCallSiteArray();
return var1[0].callCurrent(this, "foo");
public Object invokeMethod(String name, Object args)
CallSite[] var3 = $getCallSiteArray();
return new GStringImpl(new Object[]name, var3[1].call(args, ","), new String[]"unknown method ", "(", ")");
public static void main(String... args)
CallSite[] var1 = $getCallSiteArray();
Object demo = var1[2].callConstructor(Demo1.class);
var1[3].call(demo);
var1[4].callStatic(Demo1.class, var1[5].call(demo, "A", "B"));
我们发现,调用 foo()
方法时,并未执行 invokeMethod
中的逻辑,而Groovy中还有一个特殊的接口:GroovyInterceptable
,如果实现它的话:
class Demo2 implements GroovyInterceptable
def foo(String s)
return "foo:$s"
def invokeMethod(String name, Object args)
return "unknown method $name($args.join(','))"
static void main(String[] args)
def demo = new Demo2()
println demo.foo("a")
println demo.bar("A", "B")
我们将得到如下结果:
> Task :Demo2.main()
unknown method foo(a)
unknown method bar(A,B)
foo()
方法并未被分配!!, 这个场景很容易让我们联想到 Java的动态代理
并 拦截、自行分配方法执行
methodMissing示例
class Demo3
def foo(String s)
return "foo:$s"
def invokeMethod(String name, Object args)
return "unknown method $name($args.join(','))"
def methodMissing(String name, Object args)
return "methodMissing $name($args.join(','))"
static void main(String[] args)
def demo = new Demo3()
println demo.foo("a")
println demo.bar("A", "B")
按照前文的分派流程图,我们猜到结果为:
> Task :Demo3.main()
foo:a
methodMissing bar(A,B)
而实现了 GroovyInterceptable
的情况下,就无法再利用methodMissing机制拦截,而是按照 GroovyInterceptable
走 invokeMethod拦截
class Demo4 implements GroovyInterceptable
def foo(String s)
return "foo:$s"
def invokeMethod(String name, Object args)
return "unknown method $name($args.join(','))"
def methodMissing(String name, Object args)
return "methodMissing $name($args.join(','))"
static void main(String[] args)
def demo = new Demo4()
println demo.foo("a")
println demo.bar("A", "B")
> Task :Demo4.main()
unknown method foo(a)
unknown method bar(A,B)
动态处理类的属性
一个有关的题外话
一个传统的
简单JavaBean
,在很多场景下又称为POJO
,大家对此不会陌生,它包含了属性和属性的Getter、Setter并且不包含任意逻辑。我们知道, POJO需要添加Getter、Setter,哪怕通过IDE生成,并且编译时如果可能,会被inline优化,为此,还有
是否该使用Lombok之争
但是,对于 “应当有Getter、Setter,但是不应当由编写者处理,而是应该由编译器处理” 是多数人认同的
Groovy中对此进行了尝试,提供了 GPath
机制:通过编译器直接生成Getter、Setter,编码时形如属性访问,用"."符 foo.bar
,实际却相对复杂。
kotlin中也有类似的机制。
class GpathDemo
static class Foo
String bar
def getBaz()
return "baz"
static void main(String[] args)
Foo foo = new Foo(bar:"bar")
foo.bar = "bar 2"
println(foo.bar)
println(foo.baz)
我们可以发现,生成的类:
public static class Foo implements GroovyObject
private String bar;
@Generated
public Foo()
CallSite[] var1 = $getCallSiteArray();
super();
MetaClass var2 = this.$getStaticMetaClass();
this.metaClass = var2;
public Object getBaz()
CallSite[] var1 = $getCallSiteArray();
return "baz";
@Generated
public String getBar()
return this.bar;
@Generated
public void setBar(String var1)
this.bar = var1;
public static void main(String... args)
CallSite[] var1 = $getCallSiteArray();
GpathDemo.Foo foo = (GpathDemo.Foo)ScriptBytecodeAdapter
.castToType(var1[0].callConstructor(GpathDemo.Foo.class,
ScriptBytecodeAdapter.createMap(new Object[]"bar", "bar")),
GpathDemo.Foo.class);
String var3 = "bar 2";
ScriptBytecodeAdapter.setProperty(var3, (Class)null, foo, (String)"bar");
var1[1].callStatic(GpathDemo.class, var1[2].callGetProperty(foo));
var1[3].callStatic(GpathDemo.class, var1[4].callGetProperty(foo));
读者可能已经注意到了,通过手动添加Getter,也可以利用GPath机制,用"."访问;
另外,读者可能也注意到:设置bar属性时,并未直接访问Setter,此处,我们可以动态的添加属性!
class GpathDemo
static class Bar
static void main(String[] args)
Bar.metaClass."getBaz" = ->
return "baz"
Bar bar = new Bar()
println(bar.baz)
从编译结果看:
public static class Bar implements GroovyObject
@Generated
public Bar()
CallSite[] var1 = $getCallSiteArray();
super();
MetaClass var2 = this.$getStaticMetaClass();
this.metaClass = var2;
// main:
public static void main(String... args)
CallSite[] var1 = $getCallSiteArray();
final class _main_closure1 extends Closure implements GeneratedClosure
public _main_closure1(Object _outerInstance, Object _thisObject)
CallSite[] var3 = $getCallSiteArray();
super(_outerInstance, _thisObject);
public Object doCall()
CallSite[] var1 = $getCallSiteArray();
return "baz";
_main_closure1 var4 = new _main_closure1(GpathDemo.class, GpathDemo.class);
ScriptBytecodeAdapter.setProperty(var4, (Class)null,
var1[5].callGetProperty(GpathDemo.Bar.class), (String)"getBaz");
GpathDemo.Bar bar = (GpathDemo.Bar)ScriptBytecodeAdapter.castToType(
var1[6].callConstructor(GpathDemo.Bar.class),
GpathDemo.Bar.class);
var1[7].callStatic(GpathDemo.class, var1[8].callGetProperty(bar));
此时,在运行期增加了属性!
如果对Kotlin的扩展和代理比较熟悉,此处应该不难理解
但Groovy的设计更加有趣:
追踪:
org.codehaus.groovy.runtime.InvokerHelper#getProperty
org.codehaus.groovy.runtime.InvokerHelper#setProperty
发现会进入:GroovyObject
,前面已经接触过
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);
那么借助集合,如 Map
,并复写 getProperty
、 setProperty
,就可以做一些有趣的事情
特殊的Expando类
哈哈,这个有趣的事情Groovy已经做了,这就是 Expando
类。
class ExpandoDemo
static void main(String[] args)
Expando expando = new Expando()
expando.foo = "foo"
println(expando.foo)
expando.bar = "bar"
println(expando.bar)
expando.properties.forEach(new BiConsumer()
@Override
void accept(Object o, Object o2)
println("key:$o,value:$o2")
)
> Task :ExpandoDemo.main()
foo
bar
key:bar,value:bar
key:foo,value:foo
利用ExpandoMetaClass实现Mixin机制
Mixin 即 Mix In,混合, 我们可以笼统地认为:Mixin 即为 在一个类中混入其他类的内容
。
- 对于支持多继承的语言,往往是在讨论
多继承
的问题; - 对于单继承的语言,Java是利用
接口
制造多继承的表现,基于组合
,委托
等方式在目标类中混入
,
从规格继承
变相解决问题;Ruby 等语言则引入Minin
从实现继承
变相解决问题。
我们不再对此概念进行纠缠,可以认为 “多继承语言可以解决很多问题并带来更多的关联问题,单继承语言想要好处又要规避坏处,部分语言提出了Minin机制”
而Groovy的Minin,除了 编译期
要能混入,还要 运行期混入
看个例子,虽然它的场景很不合理,你一定有一万种理由劝说我使用各类设计模式,但不要较真
class MixinDemo
static class Paint
def draw(Drawable drawable)
println("paint $drawable.name")
static class Drawable
String name
static void main(String[] args)
def paint = new Paint()
Drawable.metaClass.draw = paint.&"draw"
def drawable = new Drawable(name: "test")
drawable.draw(drawable)
例子中,我们动态的给Drawable添加了draw方法
如果我们将这一过程适当的封装:
class MixinDemo2
static class MixinDelegate
private targetClass
MixinDelegate(targetClass)
this.targetClass = targetClass
def mixin(String asMethodName, Closure closure)
targetClass.metaClass."$asMethodName" = closure
static void main(String[] args)
def mixin = new MixinDelegate(MixinDemo.Drawable)
mixin.mixin("draw",new MixinDemo.Paint().&"draw")
def drawable = new MixinDemo.Drawable(name: "test")
drawable.draw(drawable)
这将会变得很有趣!!!
假设我们有一套 控制协议
,在此之前,我们只能在编译期决定好 指令的执行
– 即控制协议实现,即使运用一些巧妙的设计模式,自由程度也很低,
但现在可以在运行时更为自由地扩展、修改
当然,结合前面的知识,我们可以让它更加的酷炫:
class MixinDemo3
static class MixinDsl implements GroovyInterceptable
private targetClass
MixinDsl(targetClass)
this.targetClass = targetClass
def invokeMethod(String s, o)
if (s.startsWith("mixinFun") && s.length() > 8 && o[0] instanceof Closure)
def methodName = s[8].toLowerCase() + s[9..-1]
targetClass.metaClass."$methodName" = o[0]
return null
else
println("cannot handle")
static void main(String[] args)
(new MixinDsl(MixinDemo.Drawable)).mixinFunDraw new MixinDemo.Paint().&"draw"
def drawable = new MixinDemo.Drawable(name: "test")
drawable.draw(drawable)
此时,添加方法的写法呈现出 DSL的风格
运行时的其他修改
前面我们已经学习了在运行时给类添加方法,接下来再了解更多的内容:
添加构造器
这个例子要和Java进行对比
class RuntimeDemo
static class 查看详情
新手学习嵌入式需要掌握的几点知识点
从事嵌入式开发十年了,有些感想写出来,一则鞭策自己,让自己看到自己的不足,认清以后的发展方向,二则深知很多朋友会像我当初一样,为不知道储备什么知识而苦恼,所以写点东西给这些朋友们提供参考。一些浅见。这... 查看详情
做好seo需要掌握的20个基础知识
作为一个网站优化者,有一些基础seo知识点是大家必须要掌握的,网站排名的好快,和这些基础的SEO优化知识有没做好,有没做到位,有着直接的关系!今天,伟伟SEO就把我前面讲的SEO优化基础知识做个总结,大家优化网站时,... 查看详情
从输入网址到内容返回解析|前端工程师需要掌握这些知识(代码片段)
https=fs=options=key:fs.readFileSync(cert:fs.readFileSync(res.writeHead(res.end(外网IP:default_server;/HostX-Real-IPX-Forwarded-Forhttps://abcdef;/etc/apache2/ssl/nginx-cert.crt;/etc/apache2/ssl/private 查看详情
网站系统开发需要掌握的技术
...CSS3.有一些交互,比如修改了代码可以实时看到效果,得掌握Javascript4.直接写CSS太累,需要套用别人写好的,来得快,也比自己做出来的好看,那么就可以用用Bootstrap5.学MySQL6.学习表关系的相关知识7.有了数据库,也有了表,那... 查看详情
选c++还是java?做软件研发还需掌握哪些知识和技能?(代码片段)
...1.3、C++的应用领域与使用场景1.4、如何选择2、需要掌握的知识和技能2.1、掌握一些基础的网络知识2.2、熟悉一些常用的SQL语句2.3、了解Linux系统,掌握常用的Linux命令2. 查看详情
《掌握需求过程》阅读笔记05
需求策略需要平衡需求知识、活动和人。沟通需求知识的一致的语言,发现和传播知识的活动,参与的人,这些是影响需求策略的所有变量。 需求策略是一个活动的框架,需要根据给定的项目轮廓... 查看详情
android虚拟机:你需要掌握的基本知识
本文简要介绍AndroidRuntime虚拟机里的一些细节点,主要包括dexfile,oatfile,mirror::Class,ArtField,ArtMethod,DexCache,ClassTable等。了解这些细节,在后面学习类查找等原理时会轻松很多,所以先讲一下。文章目录dex2oat触发场景各种文件.dex.odex.... 查看详情
前端工程师需要掌握的技能
作为一个前端工程师,需要掌握的技能还真的不少。 最基本的三个技能:HTML、CSS、Javascript。 这是前端开发中最基本也是最必须的三个技能。前端的开发中,在页面的布局时,HTML将元素进行定义,CSS对展示的元素进行... 查看详情
[渗透入门篇]从渗透测试执行标准着手的渗透学习大纲。掌握了这些知识点还担心找不到工作?
想要学好渗透,就必须须知渗透测试流程标准。这篇文章根据诸葛建伟翻译脑图来介绍渗透率流程执行标准。以思维导图为主,后续会有每一个知识点的详细介绍。学会了这些知识点,工作应该是妥妥的。一、渗透测... 查看详情
web前端在实际的工作当中除了会代码还需要掌握其他哪些软件吗?
...见的PC/移动端网页,小程序,APP。完成开发我们需要学习掌握常规的HTML,CSS,JavaScript代码编辑能力,同时还需要掌握前端开发框架知识。首先我们要知道,目前前端开发工程师在企业里有两种情况:1.美工出身的前端开发工程师... 查看详情
初学web开发需要掌握哪些知识
...元素进行定位,再通过JavaScript实现相应的效果和交互。掌握三大技能,还要运用多种开发工具辅助开发。目前我们常用到的有:Dreamweaver,SublimeText,HBuilder等。工具只是解决单个问题,在你更加深入了解这个行业之后,你可能... 查看详情
掌握这些知识,你的认知将提升一个档次
上一篇文章《如何在三年内获得十年工作经验》,得到了大家的好评。其实是得益于是成甲老师的《好好学习——个人知识管理精进指南》,我吸取了这本书中自认为的精华。主要讲述了学习临界知识的心态、如何学习临界知识... 查看详情
学习嵌入式之前你需要掌握什么?
...血进入其中的不在少数,然而,在学习嵌入式之前你需要掌握哪些知识点?是否任何人都适合学习嵌入式了首先C语言,这个是毋庸置疑的,不管是做嵌入式软件还是硬件开发的人员,对C语言的掌握这个是必需的,特别是对于以... 查看详情
怎么样学习熟练win32api?需要掌握哪些知识点、要点?
1、那些C函数怎么学,怎么分类学?2、需要掌握哪些知识点、要点?会做什么样的基本操作?3、那些从汇编的层次掌握这些函数的人是怎么学的?需不需要学到这种程度呢?有上千个函数,多个lib,怎么去学好。只限于win32api,... 查看详情
linux运维需要掌握什么知识?linux运维学习路线
linux运维需要掌握什么知识?这个问题算是老生常谈了,但是本人认为知道需要掌握什么知识不是重点,重点是我们需要知道运维是做什么的?再来根据工作需求去讨论需要学习什么知识才是正途,须知知识是学不完的,技能亦... 查看详情
java工程师需要掌握哪些知识
1、语法:必须比较熟悉,在写代码的时候,IDE(IntegratedDevelopmentEnvironment,集成开发环境)的编辑器对某一行报错应该能够根据报错信息知道是什么样的语法错误,并且知道任何修正。2、命令:必须熟悉JDK(JavaDevelopmentKit,Java开发... 查看详情
android虚拟机:你需要掌握的基本知识
本文简要介绍AndroidRuntime虚拟机里的一些细节点,主要包括dexfile,oatfile,mirror::Class,ArtField,ArtMethod,DexCache,ClassTable等。了解这些细节,在后面学习类查找等原理时会轻松很多,所以先讲一下。文章目录dex2oat触发场景各种文件.dex.odex.... 查看详情