• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • Groovy簡介——運行時MOP

    • 概況

    Groovy是一個動態語言,類型不用定義的語言。它運行與JVM之上,編譯器會先將Groovy代碼編譯為Java語言,然后編譯為字節碼。

     

    Groovy在編譯的時候會先生成AST(抽象語法樹,Abstract Syntax Tree),同時Groovy也提供了AST的操作方法,也就是說在Groovy編譯時可以插入一些模板方法甚至根據AST插入對應的方法。

     

    還有一個很重要的特性,就是MOP(元對象協議 MetaObject Protocol)和元編程,也就是運行時編程,這讓Groovy的靈活性又得到了進一步的提高。

     

    • 語法介紹

    Groovy的語法幾乎和Java是一致的,只是不需要再靜態的確定變量類型,也不需要添加分行符和main方法,編譯器會自動識別從上向下識別語句運行。

     

    Groovy并不需要再定義一個接口,讓實現類來進行實現,只需要在方法中直接調用某個方法,實現類只需要定義這個方法即可。

     

    作為一個Java/Android程序員,會很自主的遵守契約精神(嚴重依賴接口)。我們習慣于將我們的行為定義成接口,這樣我們的代碼會進行更好的分層和結構設計。但是Groovy或者說很多動態語言都采用了參數的自動類型轉換,這也就使得接口設計變得不再那么重要。

     

    舉個例子:

    一個場景,如果我們需要一個人來幫助,由于我們并不知道這個人具體會變成是一個學生還是一個醫生更或者是一個其他什么身份的人,作為Java程序員,我們會定義一個Helper接口,并在Helper接口中創建一個helpDoSomething()方法,由實現類去進行實現,然后在需要幫助者的時候將實現類對象傳遞進來即可。

     

    /**

    * 創建一個幫助者接口

    */

    public interface Helper{

         void helpDoSomething();

    }

     

    public class Man implements Helper{

      public void helpDoSomething(){

      System.out.println("do something");

    }

    }

     

     

    /**

    * 用到Helper的時候直接傳入然后調用方法即可

    */

    public void takeHelp(Helper helper){

        helper.helpDoSomething();

    }

     

     

     

    同樣的使用Groovy則不需要有接口了

     

    def takeHelp(helper){

        helper.helpDoSomething();

    }

     

    class Man{

        void helpDoSomething(){

            println "Man's helping..."

    }

    }

     

    class Woman{

        void helpDoSomething(){

            println "Woman's helping..."

    }

    }

     

    takeHelper(new Man())

    takeHelper(new Woman())

     

     

    此時Groovy就不需要創建接口了,man和woman只要實現了對應的方法,就可以直接傳入使用了。當然如果傳入的對象并沒有實現對應的方法,Groovy也會做處理,會有一些方法補償和方法注入的方式,但如果所有的試錯方式都失敗了,那么運行時就會報錯。

    所以,使用動態類型語言編程,卻沒有使用單元測試的自律,就相當于是在玩火!

    其他的簡單的規則:

     

    Integer val = 4

    val = “hello”

    在Groovy編譯器不會報錯,但在嘗試運行編譯成java字節碼時會報類型轉化錯誤

     

    def val = 4

    val = “hello”是沒問題的

    會自動編譯成:

    Object val = 4;

    String var4 = "hello";

     

    閉包與協程

    調用一個函數或方法會在程序的執行序列中創建一個新的作用域。我們會在一個入口點(方法最上面的語句)進入函數。在方法完成之后,回到調用者的作用域。

     

    協程(Coroutine)則支持多個入口點,每個入口點都是上次掛起的位置。我們可以進入一個函數,執行部分代碼,掛起,再回到調用者的上下文或作用域執行的一些代碼。之后我們可以在掛起的地方恢復該函數的執行。“與主例程和子例程之間的不對稱關系不同,協程之間是完全對稱的,可以相互調用的。”

     

    協程對于實現某些特殊的邏輯或算法非常方便。例如處理生產者-消費者問題時。生產者會接受一些輸入,對輸入做一些初始化處理,通知消費者拿走處理過的值做進一步計算,并輸出或存儲結果。消費著處理它的那部分工作,然后通知生產者以獲取更多輸入。

     

    Coroutine協程例子:

    
    def iterate(n, closure){
        1.upto(n) {
            println "In iterate with value ${it}"
            closure(it)
        }
    }
    
    println "Calling iterate"
    total = 0
    iterate(4){
        total += it
        println "In closure total so far is $total"
    }
    
    println 'Done'
    
    
    
    

    輸出:

    Calling iterate

    In iterate with value 1

    In closure total so far is 1

    In iterate with value 2

    In closure total so far is 3

    In iterate with value 3

    In closure total so far is 6

    In iterate with value 4

    In closure total so far is 10

    Done

     

    這就是協程...

     

    遞歸,階乘:

    def factorial
    
    factorial = { int number, BigInteger theFactorial ->
        number == 1? theFactorial: factorial.trampoline(number-1, number*theFactorial)
    }.trampoline()
    
    println factorial(5,1)
    println factorial(1000,1).bitCount()
    
    
    
    

    結果:

    120

    3788

     

    集合類:

    ArrayList

    
    lst = [3,423,23,13,432,2,432,5]
    
    
    
    println lst[0]
    
    println lst[-1]
    
    
    
    println lst[2..5]
    
    println lst[-3..-6]
    
    
    
    sublist = lst[2..5]
    
    println sublist.dump()
    
    sublist[0] = 20 
    
    println lst
    
    

    結果:

    3

    5

    [23, 13, 432, 2]

    [2, 432, 13, 23]

    <java.util.ArrayList@18f129 elementData=[23, 13, 432, 2] size=4 modCount=1>

    [3, 423, 23, 13, 432, 2, 432, 5]

    可以用負數表示從后向前,這不是數組,自動會變成ArrayList

     

     

    每個元素變成兩倍

    lst.collect{ it*2 }用閉包的方式,將元素每個都放到閉包中,并自動返回收集到一個集合中

     

    lst.find{ it > 4 } 遍歷找到第一個大于4的元素

    lst.findAll{ it>4 }遍歷找到所有大于4的元素

     

     

    • MOP與元編程

    在Java中,使用反射可以在運行時探索程序的結構,以及程序的類、類的方法、方法接受的參數。然而,我們仍然局限于所創建的靜態結構。我們無法修改一個對象的類型,或是讓它動態的獲得行為——至少現在還不能。想象一下,如果可以基于應用的當前狀態、或基于應用所接受的輸入,動態地添加方法和行為,代碼會變得更靈活,我們的創作力和開發效率也會提高。好了,不用想象了——在Groovy中,元編程就提供了這一功能。

     

    POJO(Plain Old Java Object) 普通的Java對象

    POGO (Plain Old Groovy Object) Groovy創建的對象

     

    Groovy攔截器,拓展了GroovyInterceptable的Groovy對象,可以進行方法攔截。

     

    對于一個POJO,Groovy會去應用類的MetaClassRegistry取它的MetaClass,并將方法調用委托給它。因此,我們在它的MetaClass上定義的任何攔截器或方法,都優先于POJO原來的方法。

    也就是說會先在MetaClass上進行方法的尋找。

    
    class Car implements GroovyInterceptable {
        def check() {
            System.out.println "check called ..."
        }
    
        def start() {
            System.out.println "start called ..."
        }
    
        def drive() {
            System.out.println "drive called ..."
        }
    
        def invokeMethod(String name, args){
            System.out.print "Call to $name intercepted ..."
    
            if (name != 'check'){
                System.out.print("running filter ...")
                Car.metaClass.getMetaMethod('check').invoke(this, null)
            }
    
            def validMethod = Car.metaClass.getMetaMethod(name, args)
            if (validMethod != null){
                validMethod.invoke(this, args)
            } else {
                Car.metaClass.invokeMethod(this, name, args)
            }
        }
    
    }
    
    
    car = new Car()
    car.start()
    car.drive()
    car.check()
    
    try{
        car.speed()
    } catch(Exception ex){
        println ex
    }
    
    
    
    

    結果:

    Call to start intercepted ...running filter ...check called ...

    start called ...

    Call to drive intercepted ...running filter ...check called ...

    drive called ...

    Call to check intercepted ...check called ...

    Call to speed intercepted ...running filter ...check called ...

    groovy.lang.MissingMethodException: No signature of method: Car.speed() is applicable for argument types: () values: []

    Possible solutions: sleep(long), sleep(long, groovy.lang.Closure), split(groovy.lang.Closure), check(), start(), grep()

     

     

     

    MOP的Groovy實現方式

    分類(category)

    ExpandoMetaClass

    Mixin

     

     

    1、分類(category)

     

    創建一個StringUtil,添加一個static的方法,用于接受數據。需要調用一個特殊的方法use方法,他接受兩個參數,一個分類,一個閉包代碼塊,需要注入的方法就在這個閉包代碼塊中。

    class StringUtil{
    
        def static toSSN(self){
    
            if (self.size() == 9){
    
                "${self[0..2]}-${self[3..4]}-${self[5..8]}"
    
            }
    
        }
    
    }
    
    use(StringUtil){
    
        println "123456789".toSSN()
    
        println new StringBuilder("987654321").toSSN()
    
    }
    
    try{
    
        println "123456789".toSSN()
    
    } catch(MissingMethodException ex){
    
        println ex.message
    
    }
    
    

    結果:

    123-45-6789

    987-65-4321

    No signature of method: java.lang.String.toSSN() is applicable for argument types: () values: []

    Possible solutions: toSet(), toURI(), toURL(), toURL(), toURI(), toLong()

    也可以是這樣的

    @Category(String)
    
    class StringUtilAnnotated{
    
        def toSSN(){
    
            if (size() == 9){
    
                "${this[0..2]}-${this[3..4]}-${this[5..8]}"
    
            }
    
        }
    
    }
    
    use(StringUtilAnnotated){
    
        println "123456789".toSSN()
    
        println new StringBuilder("987654321").toSSN()
    
    }

    結果:

    123-45-6789

    Caught: groovy.lang.MissingMethodException: No signature of method: java.lang.StringBuilder.toSSN() is applicable for argument types: () values: []

    Possible solutions: toSet(), toURI(), toURL(), toList(), toLong(), toShort()

     

    找不到一個StringBuilder.toSSN()的方法,因為我們Category指定了是String類型,其實最后還是會被編譯成靜態方法與上面一致

     

    class FindUtil {
    
        def static extractOnly(String self, closure) {
    
            def result = ''
    
            self.each {
    
                if (closure(it)) {
    
                    result += it
    
                }
    
            }
    
            result
    
        }
    
    }
    
    use(FindUtil) {
    
        println "12345454524123".extractOnly({ it == '4' || it == '5' })
    
    }
    
    


    結果:

    4545454

     

    分類提供了一個漂亮的方法注入協議。其效果包含在use()塊內的控制流中。一代離開代碼塊,注入的方法就消失了。當在方法上接受了一個參數時,我們可以對這個參數應用自己的分類。那感覺就像是拓展了所接受對象的類型。而當我們離開時,這個對象的類也就不會受到影響。利用不同的分類,可以實現不同版本的攔截或注入的方法。

     

    然而,分類也有一些限制。其作用use代碼塊中,所以也就限定在了執行線程。注入的方法只能在use()塊內使用。多次進入和退出這個塊是有代價的。每次進入時,Groovy都必須檢查靜態方法,并將其加入到新作用的一個方法列表中。在塊的最后還要清理掉該作用域。

     

    如果調用不是太頻繁,而且想要分類這種可控的方法注入所提供的隔離性,就可以使用分類。如果這些特性變成了限制,則可以使用ExpandoMetaClass來注入方法。

     

     

    1. ExpandoMetaClass
    package mop
    
    Integer.metaClass.daysFormNow = { ->
    
        Calendar today = Calendar.instance
    
        today.add(Calendar.DAY_OF_MONTH, delegate)
    
        today.time
    
    }
    
    println 5.daysFormNow()
    
    Integer.metaClass.constructor << { Calendar calendar ->
    
        new Integer(calendar.get(Calendar.DAY_OF_YEAR))
    
    }
    
    println(new Integer(Calendar.instance))

     

    結果:

    Sun Dec 01 14:52:15 CST 2019

    330

    缺點,注入的方法只能在Groovy的代碼里面調用…不過有變通方案。

    就是在Java調用Groovy時,使用Groovy對象,在Groovy對象中重寫methodMissing(String name, args)

    class DynamicGroovyClass {
    
        def methodMissing(String name, args){
    
            println("You Called $name with ${args.join(', ')}.")
    
            args.size()
    
        }
    
    }
    
    public class CallDynamicMethod {
    
        public static void main(String[] args){
    
            groovy.lang.GroovyObject instance = new DynamicGroovyClass();
    
            Object result1 = instance.invokeMethod("squeak", new Object[]{});
    
            System.out.println("Received: "+result1);
    
            Object result2 = instance.invokeMethod("quack", new Object[]{"like","a","duck"});
    
            System.out.println("Received: "+result2);
    
        }
    
    }
    
    

    結果:

    You Called squeak with .

    Received: 0

    You Called quack with like, a, duck.

    Received: 3

     

    1. Mixin
    
    package mixinmop
    
    abstract class Writer {
        abstract void write(String message)
    }
    
    
    class StringWriter extends Writer {
        def target = new StringBuilder()
    
        @Override
        void write(String message) {
            target.append(message)
        }
    
        String toString() {
            target.toString()
        }
    }
    
    def writeStuff(writer) {
        writer.write("This is stupid")
        println writer
    }
    
    def create(theWriter, Object[] filters = []) {
        def instance = theWriter.newInstance()
        filters.each { filter -> instance.metaClass.mixin filter }
        instance
    }
    
    writeStuff(create(StringWriter))
    
    class UppercaseFilter {
        void write(String message) {
            def allUpper = message.toUpperCase()
    
            invokeOnPreviousMixin(metaClass, "write", allUpper)
        }
    }
    
    Object.metaClass.invokeOnPreviousMixin = {
        MetaClass currentMixinMetaClass, String method, Object[] args ->
            def previousMixin = delegate.getClass()
            for (mixin in mixedIn.mixinClasses){
                if (mixin.mixinClass.theClass ==
                    currentMixinMetaClass.delegate.theClass) break
                previousMixin = mixin.mixinClass.theClass
            }
    
            mixedIn[previousMixin]."$method"(*args)
    }
    
    
    writeStuff(create(StringWriter, UppercaseFilter))
    
    class ProfanityFilter{
        void write(String message){
            def filtered = message.replaceAll('stupid', 's*****')
            invokeOnPreviousMixin(metaClass, "write", filtered)
        }
    }
    
    writeStuff(create(StringWriter, UppercaseFilter, ProfanityFilter))
    writeStuff(create(StringWriter, ProfanityFilter, UppercaseFilter))
    
    
    
    
    

    結果:

    This is stupid

    THIS IS STUPID

    THIS IS S*****

    THIS IS STUPID

     

     

    方法注入(Method Injected)

    就是在編寫代碼是就已知了方法名,想在一個類或者一系列類里添加這個方法。利用注入的方法,可以動態的將這個方法添加到對應的類中。

    其中Groovy的Category、ExpandoMetaClass和mixin都是可以利用的方式。

     

    方法合成(Method Synthesis)

    想在調用時動態確定方法的行為,Groovy的invokeMethod、methodMissing和GroovyInterceptable對于方法合成都非常有用。

     

     

    版權聲明:本文為thunder_sz原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/thunder_sz/article/details/103258855

    智能推薦

    函數運行時堆棧

    每個函數在運行時都要使用運行時堆棧(棧幀),用于存儲局部變量和返回地址,下面我就以簡單的求和函數來分析一下堆棧到底怎樣完成的: 下面為函數的代碼: 簡單解釋一下,ebp和esp是兩個寄存器,分別用來保存這個棧的棧底和棧頂指針 要研究函數的調用過程,就必須對應匯編代碼。...

    Android——運行時權限

    需要在運行時申請的權限(Android 6.0 以后) 參考:《第二行代碼》 申請 請求應用權限:https://developer.android.google.cn/training/permissions/requesting.html 在AndroidManifest.xml中聲明要申請的權限。 在Activity里判斷。 在onRequestPermissionResult () 中處理...

    程序運行時間

        這一題其實的算法并不是很難,但是之所以貼到這里來,主要是這里設計幾個基本功的問題。這題有兩個點引起了我的注意。第一個點就是如何對一個數四舍五入。該題中的做法很好 :      k = (int)(1.0 * (c2 - c1) / ctk + 0.5)     ...

    運行時值注入

    直接注入 Bean直接注入示例: XML直接注入示例: 注入外部的值 創建app.properties文件 重Environment中獲取外部注入值 Environment中方法: String getProperty(String key):返回一個String類型值 String getProperty(String key, String defaultValue):返回一個String類型...

    Android 運行時權限

    半年前換過手機之后,就發現許多軟件在安裝之后已經授權過的內容,實際使用的過程中,會再次向我詢問權限。這幾天在開發的過程中,也遇到了明明在AndroidManifest.xml文件中申請過權限了,可是程序依舊拋錯,沒有權限的問題。調查以后發現,Android在6.0系統中引用了運行時權限這個功能,從來更好的保護用戶的安全和隱私。 Android將權限歸為兩類,一類是普通權限,一類是特殊權限。對于不會...

    猜你喜歡

    淺談Runtime運行時

    runtime runtime又叫運行時,是一套底層的c語言api,其為iOS內部核心之一。OC是動態運行時語言,它會將一些工作放在代碼運行時去處理,而非編譯時,比如動態的遍歷屬性和方法,動態的添加屬性和方法,動態的修改屬性和方法等。 了解runtime,首先要先了解它的核心--消息傳遞。 消息傳遞 消息直到運行時才會與方法實踐綁定起來。 一個實例對象調用實例方法,像這樣[obj doSometh...

    Android運行時權限

    Android運行時權限 簡介:Android 6.0 以后,對于一些用戶隱私權限總會在第一次提示用戶是否授予權限。 優點: 更好的保護了用戶的隱私。 給了程序向用戶說明權限的作用。 可以防止一些惡意程序盜取用戶后者手機信息,增強了Android系統的安全性。 Android 6.0 將權限分為兩類: 普通權限:不需要單獨申請,直接在Manifest里注冊即可。 危險權限&危險權限組:危險...

    flink運行時架構

    flink作為計算引擎,在不同的環境中有著不一樣的運行架構,下面我們簡單說兩種架構,standalone模式和yarn 1.standalone模式 作業管理器(簡稱JM),控制一個流式應用程序的主進程,是一個jvm進程,也就是說,每一個流式處理都會有一個對應的JM 這個流程的感覺就像是一個三口之家,孩子是客戶端,母親是JM,父親是TM。當孩子需要一個東西,他會首先找到他媽媽說,需要這個東西,當他...

    運行時值注入

    注入外部的值 在Spring中,處理外部值的最簡單方式就是聲明屬性源并通過Spring的Environment來檢索屬性。 使用@PropertySource注解和Environment 程序清單: 測試代碼: 工程結構 學習Spring的Environment getProperty()方法有四個重載的變種形式: 前兩種形式的getProperty()方法都會返回String類型的值。 第二種在...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    精品国产乱码久久久久久蜜桃不卡