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()
}

//
//<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()
}

//
<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()
// 
// ...... 

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()
//
//<html> ...... html>

// 如果 使用一个新的对象,还能使用get方法吗? 可以的,
// 因为使用 Java当中,使用==判断两个对象的地址,他们比较的是地址
// 而不是值,但是有的时候 声明两个变量,他们还是相等,也就是说他们
// 使用的是相同的地址,实际上Groovy ,GVM 就为我们进行了一项优化,会使用
// 相同的地址
def str1 = "https://www.baidu.com"
println str1.get3()
// 
//<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)

//...... 
//
//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()
// 
// ...... 

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
//
//<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
// 
// <html> ...... html>
// 一样的,优先调用后面的

你可能感兴趣的:(Groovy,Gradle)