MOP——方法注入

前面MOP——方法拦截介绍了利用 MOP 对方法的调用进行拦截,接下来要介绍利用 MOP 实现方法的注入。

方法拦截和方法注入的区别

拦截:侧重对于已有的方法的调用进行拦截
注入:对一个已有的类添加新方法,以拓展该类的功能。

eg:Java 中提供了 String 的类,如果我们想扩展该类,为其提供一个字符串加密方法。在 Java 中,最常见的做法是提供一个接口,内有 encrypt() 方法,让目标类实现该接口;或者继承该类,然后添加一个 encrypt() 方法。但是这里存在的问题是:我们未必可以修改想要扩展的类,就像 String,还是 final 的,只能用,不能改。

不过使用 Groovy,就可以方便的为任何类扩展方法,同时在使用起来,给人的感觉就好像注入的类是该类本身就有的。

MOP 的注入有四种实现方式:

  1. 分类(Category)
  2. ExpandoMetaClass
  3. Minxin
  4. trait

使用分类进行方法注入

第一次接触 Category 的概念是在学习 Objective-C 的时候,只要自定义一个和目标类相同的类,然后在自定义类中添加方法,那么在方法调用时,会先从自定义的类中查找,找不到后再去原本的类中查找。这样一来不仅可以扩展类的方法,同时还可以覆盖原有类的方法。Objective-C 中 Category 感觉是最优雅的方式了。而 Groovy 中的 Category 就逊色的多,接下来看一下 Groovy 中 Category 的使用方法,这里以向 String 中添加一个 encrypt 方法为例。

class StringUtils {
    def static encrypt(String self) {
        byte[] arr = self.bytes;
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (127 - arr[i])
        }
        return new String(arr)
    }


}

class IntegerUtils{
    def static add(Integer a, int b) {
        a + b
    }
}

use(StringUtils,IntegerUtils) {
    String str = "hello"
    s = str.encrypt()
    println 1.add(2)
}

这里先定义了一个的类,其中定义了一个方法 encrypt(),要想要该类成为 String 的分类,需要注意以下几点:

  1. 其内部定义的方法必须为 static
  2. 方法的第一个参数必须定位为目标类的类型(eg:这里定义的 String,当然你可以不写类型,这样就有可能让多个类都是用该方法了),如果该方法还需要参数,那么就从第二个形参开始声明。
  3. 第一个参数如果声明类型,必须为包装类的类型,eg:如果我们想为整数提供方法,即使用到了 1.add(2) 这样的调用方式,但这是 groovy 提供的语法糖,其本质任为 Integer,因此在定义分类的方法时,第一个参数必须是包装类型

在使用时,其必须在 use 所定义的代码块中,出了代码块就无法使用分类中的方法了,否则报找不到方法的错误。在 use 后面必须注明要注入的方法所在的类,eg:use(StringUtils),use 中可以注入多个类,如果多个分类中有重复的方法定义,那么以最后一个分类中方法为准。

使用 ExpandoMetaClass 进行方法注入

注入概述

之前其实我们已经见到过使用 ExpandoMetaClass 注入方法的示例了,就是使用MetaClass 进行方法拦截,这本质就是方法的注入,只不过注入的方法名(invokeMethod)比较特殊,成为了方法拦截。同样,我们也可以用 ExpandoMetaClass 对类进行其它方法的注入,还拿上面 Integer 的加法的例子:

Integer.metaClass.add = {
    int i ->
        delegate + i
}

println 1.add(3)

注入的种类

使用 ExpandoMetaClass 方法注入,可以对以下三种方法进行注入:

  • 非静态方法
  • 静态方法
  • 构造器
  • 属性

接下来一一介绍如何注入:

  1. 非静态方法注入
    这在前面已经见到过了,也是最常用的注入,使用方法:
Foo.metaClass.bar = {}
foo.bar()

Groovy 的设计理念就是让程序的编写更加流程,因此在 DSL 中,可能更常见的一种形式是在调用方法时不写括号,即foo.bar但是没有括号调用时,会将方法的调用当成属性,所以需要对之前的注入进行修改。

Foo.metaClass.getBar = {}
foo.bar

这样的调用方式是否更加优雅呢,在后面的 DSL 中,还会进一步讲解 groovy 的语法糖,让编程更加优雅。

  1. 静态方法注入
    需要使用 'static' 的特殊字面量注入静态方法
Foo.metaClass.'static'.bar = {}
Foo.bar()
  1. 注入构造器
    使用 constructor 属性注入构造器
    添加一个构造器 <<
    替换一个构造器 =
Foo.metaClass.constructor << { 
  int i -> 
  Foo foo = new Foo();
  foo.i = i
  foo
}

构造方法注入特别要注意的是,要确保没有递归调用自身,否则栈溢出。因为我们是想定义构造器,肯定会借助现有的构造器,然后进行属性的改造,但是不要产生递归。如果是想覆盖构造器的话,那么只能在内部使用反射

  1. 注入属性
    类似以闭包的方式注入方法,属性注入也是支持的,只要在后面 = 具体值 即可。
Foo.metaClass.bar = 1
println foo.bar
  1. 一次注入多个方法
    Groovy 提供了使用ClassName.metaClass.method = { ... }这样的语法向 metaClass 中添加,既简单又方便,但如果想添加一堆方法,这样的声明就会感觉很费劲。groovy 提供了更简洁的语法,用来减少噪音!!这种方式也是在 DSL 中常见到的。
Foo.metaClass = {
  bar1 = {}
  bar2 = {}

  'static'{
      bar3 = {}
  }
  //针对于不管是覆盖还是注入,在这种语法环境下,都应该使用 = 
  constructor = {
      int i - >
  }

  constructor = {
      int i,int j ->

  }
}

再次重申:使用 ExpandoMetaClass 注入的闭包中,delegate 指的是调用该方法的对象,在此基础上,闭包中使用类原本的成员变量,或者方法也是可以的。

向单个实例中注入方法

前面介绍的是向整个类中注入方法,那么基于该类的所有对象都可以使用闭包中的方法。如果只是想扩展该类的某一个对象的方法,而不影响该类的其它对象,该如何处理呢?
其实不单是 Class,每个具体的对象也包含一个 metaClass,我们可将创建一个具体的 ExpandoMetaClass 实例,并将制定方法加入其中,然后将其赋给对应的具体对象,也可以将方法直接注入到具体的对象的 metaClass 上。

//方式 1
class Man{
    def talk(){}
}
def emc = new ExpandoMetaClass(Man)
emc.sing = { -> ... }
emc.initialize()
def mike = new Man()
mile.metaClass = emc
mike.sing()

//方式 2
mike.metaClass.dance = { -> ...}
mike.dance()


//卸载之前注入的方法
mike.metaClass = null

很明显方式 2 是最为优雅的,因此推荐使用方式 2。同时,当我们为一个对象注入了方法,在使用了一段时间不想使用后,那么很方便的卸载之前注入的方法。

ExpandoMetaClass 小结

使用 ExpandoMetaClass,无论是注入方法,还是调用方法,都比 Category 要优雅的多。因此推荐使用该方法。
但是要注意的是,如果对象想使用注入的方法,必须要先进行注入。如果在已经有对象产生之后再向类中注入方法,那么该对象无法调用注入的方法!!
因此使用 ExpandoMetaClass 进行注入,最好是在整个应用初始化时进行。

同时方法注入具有继承性。如果向 Object 注入了方法,那么所有的类都可以使用该方法。

使用 Minxin,trait 进行方法注入

这两种方式更像是开头提到的定义接口的实现方式。
个人感觉最为强大的方式是 Mixin 的方式。可以为类注入多个 Mixin,就好想让类实现了多个接口,同时接口中相同的方法,以后面加入的为准。
这里不再重点展开了。
Groovy Mixin 注入
Groovy 2.3 introduces traits
Mixins and traits

实现方式的优劣对比

  • Category 存在的问题:其作用被限定在 use()块内,所以也就限定于当前执行的线程。进入该 use()块内的代码会在当前线程创建一个栈帧,并压入到当前线程的栈上,而当 use 代码块结束后,当前线程的栈会将刚刚压入的栈帧弹栈。但是如果频繁的调用 use 代码块,势必会对性能造成一定的影响。

    凡事都有两面性,Categoty 的使用 use 块,提供了更好的隔离性,我们可以再不同的地方,使用不同的分类,这也为类的扩展提供了灵活性。

  • trait:缺点是在有类的修改权的情况下才能使用,类似接口。

  • Mixin:其实是最强大的方式,但需要对其有进一步的了解,以防走火。。

这里推荐使用 ExpandoMetaClass。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,801评论 6 342
  • Groovy学习目录-传送门 元编程(Metaprogramming)->百度百科 Groovy语言支持两种类型的...
    化作春泥_阅读 9,053评论 0 19
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,617评论 18 399
  • 从离开学校到现在,算算也有一年的时间了,这一年,每天深夜都会在心里默默许诺:从明天起,要做这个,要做那个........
    叶下柳苏阅读 459评论 0 0