2014-10-29 68 views
4

我正在嘗試爲字節夥伴的字段創建一個「定製」setter方法。 Buddy自己的機制允許非常容易地實現標準的setter/getter方法,但是,我正在尋找一種優雅的方式來擴展setter和一些額外的邏輯。Java字節代碼:與字節夥伴的定製setter/getter

爲了簡化示例,我們假設我們有一個類A,它有一個方法setChanged(String)。 目標是創建A的子類,並添加一個具有相應訪問方法的字段。 問題在於,我想從每個添加的setter方法中調用setChanged(「fieldName」)。

public void setName(String name) 
{ 
    setChanged("name"); 
    this.name = name; 
} 

對於一個 「正常」 的setter方法,字節byddy實現是:我

new ByteBuddy() 
    .subclass(A.class) 
    .name("B") 
    .defineField("name", Integer.TYPE, Visibility.PUBLIC) 
    // args is a ArrayList<Class<?>> 
    .defineMethod(getSetterName(name), Void.TYPE, args, Visibility.PUBLIC) 
    .intercept(FieldAccessor.ofField(name)) 

字節碼看起來像這樣經過:

L0 
ALOAD 0  // Loads the this reference onto the operand stack 
ILOAD 1  // Loads the integer value of the local variable 1 (first method arg) 
PUTFIELD package/B.name : I // stores the value to the field 
L1 
ALOAD 0 
LDC "name" 
INVOKEVIRTUAL package/A.setChanged (Ljava/lang/String;)V 
RETURN 

我的問題是:有沒有在這種情況下重新使用FieldAccessor的方法?

+0

我不確定你真正的問題是什麼。你想知道是否可以將FieldAccessor.ofField(...)'的結果存儲到一個變量中並多次使用它?你爲什麼不嘗試它?或者你的意思是什麼樣的重用? – Holger 2014-10-29 14:57:40

+0

嗨Holger, 我想用方法調用擴展FieldAccessor.ofField(...)(在上面的例子中:setChanged(「name」);)。 我的意思是重複使用代碼,而不是對象。基本上(這可能是錯誤的方法),我想獲得FieldAccessor的堆棧操作並在另一個複合堆棧操作中使用它。 – 2014-10-30 08:23:57

+0

呃,有['Instrumentation.Compound'](http://bytebuddy.net/javadoc/0.3.1/net/bytebuddy/instrumentation/Instrumentation.Compound.html#Compound-net.bytebuddy.instrumentation.Instrumentation ... - ),但奇怪的是,我沒有找到任何方式來調用'this'上的另一種方法... – Holger 2014-10-30 10:01:19

回答

2

從今天開始,您需要定義一個自定義Instrumentation來完成這樣的自定義作業。正如評論中指出的那樣,您可以使用Instrumentation.Compound將新行爲添加到例如FieldAccessor.ofBeanProperty(),從而重新使用字段訪問器代碼。

爲了添加自定義代碼,字節好友知道不同的抽象層次:

  1. Instrumentation:如何定義的方法來實現。工具可以定義實現方法所需的其他字段,方法或靜態初始化程序塊。此外,它確定是否要根據某種類型定義方法。
  2. A ByteCodeAppenderInstrumentation發出,並確定定義的方法是否是抽象的,以及方法的字節碼是否實現。
  3. A StackManipulation是一個字節碼指令,對操作數棧的大小有一定的影響。堆棧操作是爲實現非抽象方法而編寫的。

爲了在字節碼中調用方法,需要將所有參數(包括this)加載到堆棧上,並在放置所有這些參數後調用方法。這可以按如下方式完成:

  1. this參考加載到堆棧上MethodVariableAccess.REFERENCE.loadFromIndex(0)
  2. 將字符串加載到描述訪問字段的堆棧中。這可以從作爲參數給出的ByteCodeAppender的方法名稱導出。使用TextConstant,名稱可以放在堆棧上。
  3. 的方法,然後可以通過使用MethodInvocation其中可以從所創建的提取的setChanged方法儀表被調用TypeDescription類型,其被提供給Instrumentation作爲參數。

當然,這不是很漂亮,它是Byte Buddy希望從用戶隱藏這個字節代碼級別的API,並在DSL或普通Java中表達任何內容。因此,您可能很高興聽到我目前正在使用Byte Buddy 0.4版本,該版本提供了一些可用於此目的的功能。例如,您可以使用Byte Buddy的瑞士軍刀MethodDelegation的擴展形式實現自定義設置器。方法委託允許您通過使用註釋將調用委託給任何Java方法來實現方法。

假設你的bean實現一個類型:

interface Changeable { 
    void setChanged(String field); 
} 

您可以使用攔截方法調用:

class Interceptor { 
    static void intercept(@This Changeable thiz, @Origin Method method) { 
    thiz.setChanged(method.getName()); 
    } 
} 

使用方法代表團字節好友總是會調用攔截時的方法是調用。攔截器方法傳遞了描述特定攔截上下文的參數。上面,this引用和被攔截的方法被傳遞。

當然,我們仍然缺少該領域的實際設置。然而,隨着字節巴迪0.4,現在就可以創建一個新的Instrumentation容易如下:

MethodDelegation.to(Interceptor.class).andThen(FieldAccessor.ofBeanProperty()) 

有了這個代表團,字節巴迪首先調用intercepor(再滴任何潛在的返回值),最後應用Instrumentation那作爲參數傳遞給andThen方法。

+0

太棒了!這是一個非常有用的功能添加! 如果您可以在文檔中給出一些生成的字節碼示例(針對各種Byte Buddy「模式」),那將會很好,可以使性能評估更容易。這是一個糟糕的建議,因爲我可以看看自己,但它可以幫助在性能關鍵的情況下做出設計決策。 – 2014-11-03 10:53:30

+0

我寫了一篇廣泛的教程,但是我計劃在版本0.5發佈之後發佈一系列博客文章,允許在創建DSL代理時創建DSL。目前,我在會議上介紹Byte Buddy會花費很多時間,但預計明年年初舉辦這樣的系列活動。很長時間,感謝您成爲早期採用者。 – 2014-11-03 11:04:05

+0

我已閱讀教程,做得好!當然有一些改進的想法,但總而言之,我覺得它是一個寫得很好的教程。感謝您的努力,繼續努力吧! :) – 2014-11-04 09:39:17