0
点赞
收藏
分享

微信扫一扫

掌握Gradle,还需要掌握这些知识--Groovy MOP


写在最前

Groovy已经不再是一门新出现的语言,而笔者是在2013年左右接触到它的,并且在2017年时,有机会尝试使用它编写了基于SpringBoot的后端项目。

但说来惭愧,在很长的一段时间里,我都没有系统的学习它。并且时至今日,我也 ​​不推荐​​​ 大家再去 ​​系统的学习​​​ 它,毕竟 ​​使用它的机会越发地少了​​, 但是我依旧认为大家有必要花费一些零碎的时间,快餐式的了解它。

这一篇讲MOP,之后还有一篇闭包

文章代码已发布于:​​GroovyWorkshop​​

为何产生编写Groovy系列的想法

一言以蔽之:“被刺激到了,很多事情不知其所以然”.

​​完整了解​​

MetaObject Protocol 元对象协议

本文直接从MOP开始,忽略掉Groovy的大量基础部分,因为这些基础部分,基本和Java一致。

MOP的目标在于:​​运行期进行实时变化​​,听起来有点像Java的反射,但是要比Java的反射更加强大。可以:


  • 修改方法名、属性名,
  • 动态增加类的方法、属性 等等

神奇的方法分配-- ​​invokeMethod​​​ 和 ​​methodMissing​

首先我们简单了解一下Groovy的方法分配机制:

掌握Gradle,还需要掌握这些知识--Groovy MOP_android

本节内容,结果上看起来很神奇,但内容比较枯燥,并且对非语言使用者帮助不大,可以泛读,有个印象即可

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 Bean {
String a
String b
String c
String d

@Override
public String toString() {
return "Bean{" +
"a='" + a + '\'' +
", b='" + b + '\'' +
", c='" + c + '\'' +
", d='" + d + '\'' +
'}';
}
}

static class ConstructorDemo {

void main() {
Bean.metaClass.constructor = { String a ->
new Bean(a: a, b: "b", c: "c", d: "d")
}
def bean = new Bean("a")
println(bean)
}
}

static void main(String[] args) {
def bean = new Bean(a: "a", b: "b", c: "c")
println(bean)
new ConstructorDemo().main()
}
}

本身Groovy允许我们在构造时设置属性值,但这并不是有重载的构造器!如果没有这个机制,我们就不得不建立一系列重载的构造器,或者老老实实赋值。

但Groovy可以添加构造器

添加静态方法

类比前面提到的添加方法,我们只需要添加关键字 ​​static​​ 就可以添加静态方法。

static void main(String[] args) {
GpathDemo.Foo.metaClass.'static'.hello = { args1 ->
return "RuntimeDemo:hello,${args1}"
}
println GpathDemo.Foo.hello("foo")
}

为对象添加方法

前文已经介绍过给类添加方法,不再赘述。这里注意,我们可以单独给对象添加方法,而不累及该类的其他实例。

static void main(String[] args) {
def bean = new Bean(a: "a", b: "b", c: "c")

//为对象添加方法
try {
bean.hello()
} catch(Exception e) {
println(e.message)
}

def emc = new ExpandoMetaClass(Bean.class, false)
emc.hello = { println "hello" }
emc.initialize()
bean.metaClass = emc
bean.hello()

try {
new Bean().hello()
} catch(Exception e) {
println(e.message)
}
}

很显然,第一次得到Exception,第二次正常打印hello,第三次得到Exception

自省

前文讲了如此之多的运行时修改,很显然,Groovy可以自省,我们简单了解一下以下知识,毕竟这些内容使用不多。

反射

Groovy承袭了Java,那么自然可以使用Java的反射,但是注意:


基于MOP添加的内容,均无法通过Java反射获知


respondsTo 和 hasProperty

class ResponseToDemo {
static class Demo {
def p = "p"
def foo() {
println("foo")
}
}

static void main(String[] args) {
Demo.metaClass."bar" = { ->
println 'bar'
}

def demo = new Demo()
if (demo.metaClass.respondsTo(demo, 'bar')) {
println 'bar ok'
}

if (demo.metaClass.respondsTo(demo, 'foo')) {
println 'foo ok'
}

if (demo.metaClass.hasProperty(demo, 'p')) {
println 'p ok'
}
}
}

//> Task :ResponseToDemo.main()
//bar ok
//foo ok
//p ok

利用respondsTo和hasProperty,可以分析固有属性、方法以及MOP添加的内容。

hasMetaMethod 和 hasMetaProperty

这里直接给出结论:


这一组方法仅可对类进行分析,而针对对象利用MOP添加的属性和方法,利用respondsTo和hasProperty 可以分析得到。


借助Interceptor实现AOP能力

相信各位对AOP(Aspect-Orient-Program)都有或多或少的了解,在Java中也有大名鼎鼎的AspectJ,而在Groovy中, 本身就可以利用 ​​Interceptor机制​​ 进行简单的AOP,而不必借助框架的力量。

看一个简单的例子:

class InterceptorDemo {
static class Demo {
void foo() {
println("foo")
}
}

static class DemoInterceptor1 implements Interceptor {

@Override
Object beforeInvoke(Object o, String s, Object[] objects) {
println("before $s")
return o
}

@Override
Object afterInvoke(Object o, String s, Object[] objects, Object o1) {
println("after $s")
return o1
}

@Override
boolean doInvoke() {
return true
}
}

static void main(String[] args) {
def proxy = ProxyMetaClass.getInstance(Demo)
proxy.interceptor = new DemoInterceptor1()

proxy.use {
def demo = new Demo()
demo.foo()
}
}
}

得到的结果会如何呢?

> Task :InterceptorDemo.main()
before ctor
after ctor
before foo
before println
foo
after println
after foo

如果 ​​doInvoke​​ 返回false,那么 foo() 方法将被拦截。

接下来我们做个有趣的事情,先做一定的修改:

class InterceptorDemo {
static class Demo {
def String s

String foo() {
return "foo $s"
}

@Override
public String toString() {
return "Demo{" +
"s='" + s + '\'' +
'}';
}
}

static class DemoInterceptor1 implements Interceptor {

def tmp = new Demo(s: "DemoInterceptor1")

@Override
Object beforeInvoke(Object o, String s, Object[] objects) {
if (s == "foo") {
println("before $s, $o")
o.s = "aaaaa"
}
return tmp
// return o
}

@Override
Object afterInvoke(Object o, String s, Object[] objects, Object o1) {
if (s == "foo") {
println("after $s, $o")
return "hahaha"
}
return o1
}

@Override
boolean doInvoke() {
return true
}
}

static void main(String[] args) {
def proxy = ProxyMetaClass.getInstance(Demo)
proxy.interceptor = new DemoInterceptor1()

proxy.use {
def demo = new Demo(s: "demo")
println demo.foo()
}
}
}

会得到怎么的输出呢?

不难推测:


  • 最开始会打印 “before foo, Demo{s=‘demo’}”
  • 最终会打印 “hahaha”

那么afterInvoke会打印什么呢?

after foo, Demo{s='aaaaa'}

如果将doInvoke设为false,又会打印什么呢?

> Task :InterceptorDemo.main()
foo DemoInterceptor1

确实有趣,感兴趣的同学可以继续研究。我们言归正传。

上面的例子是比较简单的,结合大家的编程经验,不难想到,可以添加助手类和拦截器逻辑,针对各种类采用拦截器了。

如果有进行实践的同学,是否在折腾过程中出现了堆栈溢出?这里补充一个知识:


在调用方法时添加 ​​&​​,例如:demo.&foo() , 使得本身拦截的方法不被拦截,本身不拦截的方法会被拦截。

显然,会出现堆栈溢出,应该对方法进行了拦截并且不断拦截。


当然,还有一个很简单的技巧,分别在before和after中,​​先将拦截器移除,执行完业务后再添加回去​

对Java类使用上述特性

拦截器是Groovy的运行机制,所以依旧可以使用。

使用代理对象

ExpandoMetaClass emc = new ExpandoMetaClass(JavaClz, false)
emc.foo = { return "hello,world!" }
emc.initialize()

def foo = new groovy.util.Proxy().wrap(new JavaClz())
foo.setMetaClass(emc)
println foo.foo()

除此之外,还可以利用委托,向上下文注册Java类的MetaClass为一个委托,进而实现一些动态性,因为和上文的做法有一定区别,不再扩展,有兴趣的同学 可以搜索 ​​groovy.lang.DelegatingMetaClass​​ 了解更多。

结语

MOP是Groovy的高阶内容之一,可能短期内你都不会使用到它,甚至不作为主语言时,你永远不会用到它,但再次遇到gradle项目采用了 “神奇方案” 来解决问题时, 有能力弄明白为何这会生效!!

如果你觉得笔者的博客对你有帮助,希望可以点个赞或者留个言,让我知道这达到了正向的效果!!

最后

小编在网上收集了一些 Android 开发相关的学习文档、面试题、Android 核心笔记等等文档,希望能帮助到大家学习提升。

掌握Gradle,还需要掌握这些知识--Groovy MOP_android studio_02



举报

相关推荐

0 条评论