Groovy09_MOP与元编程(方法注入)
Posted 李樟清
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Groovy09_MOP与元编程(方法注入)相关的知识,希望对你有一定的参考价值。
现在讲的都是运行时元编程,而编译时元编程 比如 ButterKnife 使用AOP,进行事件和View的寻找和绑定
运行时元编程,方法注入 3中方式
- category 分类注入 (和继承类似)
- meteclass (ExpandoMetaClass)
- 使用mixin 混合(和category类似,创建一个类,混合进要使用的对象中去,
这样我们就能使用这个对象本身的这个类型,没有的方法)
1. category 分类注入 (和继承类似)
lsn9_0.groovy
// 分类
class Req
static def get(String self)
self.toURL().text
//"".get() // 这样写是找不到get方法的
// 怎么使用呢? 使用use方法
use(Req)
println "https://www.baidu.com/".get()
//<!DOCTYPE html>
//<!--STATUS OK--><html> ...... </html>
// 编译时元编程的写法
@Category(String)
class StringUtls
def get()
toString()
def toUpperCase()
'toUpperCase'
// use 可以接受多个分类,如果StringUtls 和 Req 都为我们注入了get方法
// 当我们在闭包中使用get方法,是可以使用的,他调用的是哪个分类的get方法?
// 它会先去Req 找,没有找到才会去StringUtls找
use(StringUtls,Req)
println "https://www.baidu.com/".get()
//<!DOCTYPE html>
<!--STATUS OK--><html> ...... </html>
use(Req,StringUtls)
println "https://www.baidu.com/".get()
//https://www.baidu.com/
use(Req,StringUtls)
println "https://www.baidu.com/".toUpperCase()
// toUpperCase ,
// 如果分类里面定义了一个String中已经有的方法,
// 他会先去StringUtls找,找不到再去Req找,还找不到才会去他本类中找
// 总结:从后面开始找
// 灵活,可控性高
// 对性能有影响
2. meteclass (ExpandoMetaClass)
lsn9_1.groovy
2.1 meteclass 注入实例方法
// ================ 2.1 meteclass 注入实例方法=========================
// 1.注入用<< 如果是拦截 用=
String.metaClass.get <<
// 默认delegate 是 owner ,owner 定义它的时候 所在类的对象 ,即lsn9_1
// 但是我们利用metaClass的元协议,我们传递给他的
// 闭包会对delegate进行改变成String、
println delegate
"https://www.baidu.com".get() // httpw://www.baidu.com
String.metaClass.get2 <<
delegate.toString().toURL().text
println "https://www.baidu.com".get2()
// <!DOCTYPE html>
//<!--STATUS OK--><html> ...... </html>
2.2 如果我们注入一个已经存在的方法
//String.metaClass.endsWith <<
// String suffix->
//
// 这边是会报错的:groovy.lang.GroovyRuntimeException: Cannot add new method [endsWith] for arguments [[class java.lang.String]]. It already exists!
// 如果使用 = 就不会报错了,所以我们就 使用= 就可以, 不用管他是新注入的方法还是已经存在的方法
String.metaClass.endsWith =
String suffix->
//
2.3 如果往具体的对象注入一个方法
def str = "https://www.baidu.com"
str.metaClass.get3 =
delegate.toString().toURL().text
println str.get3()
//<!DOCTYPE html>
//<!--STATUS OK--><html> ...... </html>
// 如果 使用一个新的对象,还能使用get方法吗? 可以的,
// 因为使用 Java当中,使用==判断两个对象的地址,他们比较的是地址
// 而不是值,但是有的时候 声明两个变量,他们还是相等,也就是说他们
// 使用的是相同的地址,实际上Groovy ,GVM 就为我们进行了一项优化,会使用
// 相同的地址
def str1 = "https://www.baidu.com"
println str1.get3()
// <!DOCTYPE html>
//<!--STATUS OK--><html> ...... </html>
// 下面的写法会报错,
//def str2 = new String("https://www.baidu.com")
//println str2.get3()
2.4 meteclass 注入静态方法
String.metaClass.'static'.printlnClass =
println "123456"
println delegate
"".printlnClass()
// 123456
// 发现delegate 居然没有东西
// 因为我们是在String上注入的方法,而我们是在
// 具体的对象上面调用方法,这样的话,他会把delegate设置为
// 具体的对象
// 看下面的打印就知道了
"zeking".printlnClass()
// 123456
// zeking
String.printlnClass()
// 123456
// class java.lang.String
2.5 meteclass 注入构造函数
//println new String(Calendar.instance) // 报错: Could not find matching constructor for: java.lang.String(java.util.GregorianCalendar)
String.metaClass.constructor =
Calendar calendar->
new String(calendar.getTime().toString())
println new String(Calendar.instance) // Mon Apr 23 22:42:02 CST 2018
2.6 meteclass 多种方法注入写在一起
// 如果我们要注入很多方法,上面的写法 就有点很混乱
// 我们可以用下面的写法
String.metaClass
get4 =
delegate.toString().toURL().text
'static'
printlnClass2 =
println "123456"
println delegate
printlnZeking =
println 'zeking'
constructor =
Calendar calendar->
new String(calendar.getTime().toString())
def str4 = "https://www.baidu.com"
println str4.get4()
String.printlnZeking()
println new String(Calendar.instance)
//<!--STATUS OK--><html>...... </html>
//
//zeking
//Mon Apr 23 22:53:22 CST 2018
2.7 meteclass 介绍
// 1.String.metaClass 是什么
println String.metaClass
// org.codehaus.groovy.runtime.HandleMetaClass@7c0c77c7[groovy.lang.MetaClassImpl@7c0c77c7[class java.lang.String]]
// 类型: HandleMetaClass MetaClassImpl
// 2.当我们注入方法的时候,他的类型就变了看下面代码
String.metaClass
get4 =
delegate.toString().toURL().text
'static'
printlnClass2 =
println "123456"
println delegate
printlnZeking =
println 'zeking'
constructor =
Calendar calendar->
new String(calendar.getTime().toString())
println String.metaClass
// groovy.lang.ExpandoMetaClass@7334aada[class java.lang.String]
// 类型变成了:ExpandoMetaClass
2.8 直接使用ExpandoMetaClass 来注入
def emc = new ExpandoMetaClass(String)
emc.get5 =
delegate.toString().toURL().text
emc.initialize() // 初始化一下,才来使用它
String.metaClass = emc
println String.metaClass.class // class groovy.lang.ExpandoMetaClass
println "https://www.baidu.com".get5()
// <!DOCTYPE html>
//<!--STATUS OK--><html> ...... </html>
String.metaClass = null // 将metaClass置为空,它会变成最开始的metaClass,下面的代码就会报错
println String.metaClass.class // class org.codehaus.groovy.runtime.HandleMetaClass
println "https://www.baidu.com".get5() // 报错 No signature of method: java.lang.String.get5() is applicable for argument types: () values:
// 所以说:我们往metaClass 当中注入方法,实际上是用了 ExpandoMetaClass
// 看下ExpandoMetaClass 的继承关系
// public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject
// public class MetaClassImpl implements MetaClass, MutableMetaClass
// public interface MetaClass extends MetaObjectProtocol
// MetaClass 就是 MOP 元对象协议
2.9 说明
Test.java
public class Test
public void work()
// System.out.println("work");
run();
public void run()
System.out.println("run");
// 不要说,那我们使用这个Groovy,我就能够随意的,无论在Groovy工程
// 或者Java工程随意的往类上面注入方法,实际上还是要有注意的
// 不管注入方法的那种形式,不管是 分类还是metaClass,还是混合mixin
// 举个例子,创建一个Java类,实现两个方法
Test.metaClass.run =
println "groovy run"
new Test().run() // groovy run
new Test().work() // run
// 调用的是run ,
// 首先Test编译出来的class文件 是没有影响的
// 所以我们才说这种注入的方法是运行时的行为
// 不是编译时的行为
// 其实我们之所以之所以可以将run方法改变掉,
// 并不是通过改变class
// 是通过操作内存的形式,是通过调用动态调用的形式
// 是通过 动态调用节点 去调用的 ScriptBytecodeAdapter.setProperty (看lsn9_1.class)
3. 使用mixin 混合
class Get
def get(String url)
println 'get'
url.toURL().text
String.mixin(Get)
//或者这样,一样的 String.metaClass.mixin(Get)
println "".get("https://www.baidu.com")
// get
//<!DOCTYPE html>
//<!--STATUS OK--><html> ...... </html>
@Mixin(String)
class Get2
def get2(String url)
url.toURL().text
//println new Get2().substring(2). // 这样可以调用String 上面原有的方法
// 分类和混合类似,分类是 在 use 的作用域里面,mixin 混合就是在整个lsn9_2的作用域里面
// 如果有多个的话,和分类一样的,优先调用后面的
class Post
def get(String url)
println 'post'
url.toURL().text
String.mixin(Get,Post)
println "".get("https://www.baidu.com")
// post
// <!DOCTYPE html>
// <!--STATUS OK--><html> ...... </html>
// 一样的,优先调用后面的
以上是关于Groovy09_MOP与元编程(方法注入)的主要内容,如果未能解决你的问题,请参考以下文章
GroovyMOP 元对象协议与元编程 ( 方法注入 | 使用 Category 分类进行方法注入的优缺点 )
GroovyMOP 元对象协议与元编程 ( 方法注入 | 使用 ExpandoMetaClass 进行方法注入 )