Groovy09_MOP与元编程(方法注入)

Posted 李樟清

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Groovy09_MOP与元编程(方法注入)相关的知识,希望对你有一定的参考价值。

现在讲的都是运行时元编程,而编译时元编程 比如 ButterKnife 使用AOP,进行事件和View的寻找和绑定
运行时元编程,方法注入 3中方式

  1. category 分类注入 (和继承类似)
  2. meteclass (ExpandoMetaClass)
  3. 使用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与元编程(方法注入)的主要内容,如果未能解决你的问题,请参考以下文章

Groovy08_MOP与元编程(方法拦截) ```

Groovy08_MOP与元编程(方法拦截) ```

GroovyMOP 元对象协议与元编程 ( 方法注入 | 使用 Category 分类进行方法注入的优缺点 )

GroovyMOP 元对象协议与元编程 ( 方法注入 | 使用 ExpandoMetaClass 进行方法注入 )

GroovyMOP 元对象协议与元编程 ( 方法委托 | 使用 @Delegate 注解进行方法委托 )

GroovyMOP 元对象协议与元编程 ( 方法合成引入 | 类内部获取 HandleMetaClass )