ByteBuddy(十二)—生成構(gòu)造函數(shù)

本章介紹如何動(dòng)態(tài)生成構(gòu)造函數(shù)。

本章有兩個(gè)功能代碼,父類Producer.java及其派生類DataProducer.java。

這是Producer.java代碼:

public class Producer{
    private long producerId;
    private String record;
    public Producer(){}
    public Producer(long pid, String d){
        producerId = pid;
        record = d;
    }
    public long getProducerId(){
        return producerId;
    }
    public void setProducerId(long producerId){
        this.producerId = producerId;
    }
    public String getRecord(){
        return record;
    }
    public void setRecord(String data){
        this.record = data;
    }
}

這是DataProducer.java代碼:

public class DataProducer extends Producer{
    private int dataProducerId;
    private String data;
    private BigInteger int01;
    public int getDataProducerId(){
        return dataProducerId;
    }
    public void setDataProducerId(int dataProducerId){
        this.dataProducerId = dataProducerId;
    }
    public String getData(){
        return data;
    }
    public void setData(String data){
        this.data = data;
    }
}

觀察到DataProducer.java沒有構(gòu)造函數(shù)。
將向DataProducer.class添加七個(gè)構(gòu)造函數(shù)。

使用define方法克隆構(gòu)造函數(shù)

這是插件程序創(chuàng)建的第一個(gè)構(gòu)造函數(shù):

public DataProducer(int p1, String p2, String p3){}

這是創(chuàng)建構(gòu)造函數(shù)的代碼,它在InterceptorPlugin.javaapply方法中實(shí)現(xiàn)

Constructor c1 = ConstructorPrototype.class
                .getDeclaredConstructor(int.class, String.class, String.class);
        builder = builder.define(c1)
                .intercept(MethodCall
                        .invoke(Producer.class.getConstructor()));

ConstructorPrototype.java:

public class ConstructorPrototype{
    private int data1;
    private String data2;
    private String data3;
    public ConstructorPrototype(int a, String b, String c){
        data1 = a;
        data2 = b;
        data3 = c;
    }
}

define方法克隆ConstructorPrototype.java的構(gòu)造函數(shù),該構(gòu)造函數(shù)包含三個(gè)參數(shù):int.class、String.class,String.class
為了使define方法克隆構(gòu)造函數(shù),程序必須傳遞java.lang.reflect的實(shí)例。
構(gòu)造函數(shù)設(shè)置為define方法的參數(shù)。
然而,define方法從不克隆方法體和在原型構(gòu)造函數(shù)上聲明的注解。

與Java方法類似,新聲明的構(gòu)造函數(shù)需要構(gòu)造函數(shù)體。
intercept方法使用MethodCall生成構(gòu)造函數(shù)體。

.intercept(MethodCall.invoke(Producer.class.getConstructor()));

在此構(gòu)造函數(shù)中,MethodCall用于調(diào)用超類默認(rèn)構(gòu)造函數(shù)Producer(),該構(gòu)造函數(shù)生成以下字節(jié)碼:

public DataProducer(int p1, String p2, String p3){
    super();
}

使用defineConstructor方法生成構(gòu)造函數(shù)

接下來,Plugin程序創(chuàng)建私有構(gòu)造函數(shù):

private DataProducer(long p1){}

這是用于生成構(gòu)造函數(shù)的代碼:

builder = builder.defineConstructor(Visibility.PRIVATE)
                .withParameter(long.class)
                .intercept(MethodCall
                        .invoke(Producer.class.getConstructor()));

代碼使用define構(gòu)造函數(shù)。
defineConstructor方法采用一個(gè)Visibility參數(shù),該參數(shù)指定構(gòu)造函數(shù)的修飾符。
在此示例中,參數(shù)指定私有可見性。
然后,該方法鏈接到withParameter方法,該方法聲明long類型參數(shù)。
之后,intercept方法使用MethodCall創(chuàng)建構(gòu)造函數(shù)體。

接下來,Plugin程序創(chuàng)建此構(gòu)造函數(shù):

public DataProducer(int p1, int p2){}

這是用于生成構(gòu)造函數(shù)的代碼:

builder = builder.defineConstructor(Opcodes.ACC_PUBLIC)
                .withParameters(int.class,int.class)
                .intercept(
                        MethodCall.invoke(
                                Producer.class.getConstructor()));

代碼使用defineConstructor聲明公共構(gòu)造函數(shù)。
Opcodes是指定修改器的可見性的替代方法。
然后,該方法鏈接到帶有兩個(gè)參數(shù)的withParameters方法:int.classint.class,與withParameter方法不同,withParameters可以創(chuàng)建多個(gè)參數(shù)。
然后,intercept方法創(chuàng)建構(gòu)造函數(shù)體。

apply方法進(jìn)一步創(chuàng)建下一個(gè)構(gòu)造函數(shù):

public DataProducer(int var1, int var2, String var3, String var4) {
        super((long)var1, var3);
        this.dataProducerId = var2;
        this.data = var4;
}

這是實(shí)現(xiàn)構(gòu)造函數(shù)的代碼:

builder = builder.defineConstructor(Visibility.PUBLIC)
                .withParameters(int.class, int.class, String.class, String.class)
                .intercept(
                        MethodCall.invoke(Producer.class
                                .getDeclaredConstructor(long.class,String.class))
                                .withArgument(0,2)
                                .andThen(FieldAccessor.ofField("dataProducerId")
                                        .setsArgumentAt(1))
                                .andThen(FieldAccessor.ofField("data")
                                        .setsArgumentAt(3)));

構(gòu)造函數(shù)及其參數(shù)的聲明使用了前面的構(gòu)造函數(shù)中已經(jīng)解釋過的類似方法。
然而,構(gòu)造函數(shù)主體與之前的構(gòu)造函數(shù)不同。
構(gòu)造函數(shù)調(diào)用具有兩個(gè)參數(shù)的父類構(gòu)造函數(shù):

super((long)var1, var3);

這行代碼的字節(jié)碼是通過以下建議代碼創(chuàng)建的:

MethodCall.invoke(Producer.class
    .getDeclaredConstructor(long.class,String.class))
    .withArgument(0,2)

MethodCall調(diào)用另一個(gè)接受兩個(gè)參數(shù)的超級構(gòu)造函數(shù):long.classString.class。
然后使用WithArgument方法將var1var3參數(shù)傳遞給父類構(gòu)造函數(shù)。
使用var1var3參數(shù)是因?yàn)?code>withArgument方法指定了參數(shù)索引02
這意味著DataProducer構(gòu)造函數(shù)的第一個(gè)和第三個(gè)參數(shù)。
類傳遞給其父類構(gòu)造函數(shù)。
之后,構(gòu)造函數(shù)主體將var2var4分配給dataProducerlddata實(shí)例變量:

dataProducerId = var2;
data = var4;

為了為這些代碼行生成字節(jié)碼,Advice代碼將方法鏈接到andThen方法

andThen(FieldAccessor.
    ofField("dataProducerId")
    .setsArgumentAt(1))
net.bytebuddy.implementation.FieldAccessor    用于以編程方式訪問實(shí)例變量。

這里FieldAccessor的用法是將var2參數(shù)設(shè)置為dataProducerld實(shí)例變量。
ofField方法配置dataProducerld實(shí)例變量,setsArgumentAt方法配置var2參數(shù)。
setsArgumentAt方法中的值指定構(gòu)造函數(shù)的參數(shù)索引。
因此,選擇var2參數(shù)。
類似地,為這行代碼生成字節(jié)碼:

data = var4;

FieldAccessor用于實(shí)現(xiàn)以下目的:

andThen(FieldAccessor.ofField("data")
    .setsArgumentAt(3))

接下來,Plugin程序創(chuàng)建此構(gòu)造函數(shù):

public DataProducer(long var1, String var3) throws ClassNotFoundException, SQLException {
        super(var1, var3);
}

這是生成構(gòu)造函數(shù)的代碼:

builder = builder.defineConstructor(Visibility.PUBLIC)
                .withParameters(long.class,String.class)
                .throwing(ClassNotFoundException.class, SQLException.class)
                .intercept(
                        MethodCall.invoke(
                                Producer.class.getDeclaredConstructor(long.class,String.class))
                                .withAllArguments());

與上一個(gè)構(gòu)造函數(shù)類似,構(gòu)造函數(shù)主體調(diào)用接受兩個(gè)參數(shù)的父類構(gòu)造函數(shù)。
但是,Advice代碼使用withAllArguments而不是withArgument方法。
withAllArguments將所有參數(shù)值從構(gòu)造函數(shù)傳遞給父類構(gòu)造函數(shù)。

此構(gòu)造函數(shù)聲明throws子句。
throws子句引發(fā)ClassNotFoundExceptionSQLException。
這些異常是通過throwing方法聲明的。

接下來,Plugin程序插入默認(rèn)構(gòu)造函數(shù)。

這是生成的默認(rèn)構(gòu)造函數(shù):

public DataProducer(){
    this.dataProducerId = 120;
    this.record = "<noData>";
}

這是插入默認(rèn)構(gòu)造函數(shù)的代碼,注意到代碼沒有使用defineConstructor方法聲明默認(rèn)構(gòu)造函數(shù),而是使用構(gòu)造函數(shù)方法來匹配DataProducer.class中的默認(rèn)構(gòu)造函數(shù)

builder = builder.constructor(ElementMatchers.isDefaultConstructor())
                .intercept(
                        MethodCall.invoke(
                                Producer.class.getDeclaredConstructor())
                                .andThen(
                                        FieldAccessor
                                                .ofField("dataProducerId").setsValue(120))
                                .andThen(
                                        FieldAccessor
                                                .ofField("data").setsValue("<noData>")));

代碼調(diào)用構(gòu)造函數(shù)方法并應(yīng)用ElementMatchers.isDefaultConstructor方法以匹配默認(rèn)構(gòu)造函數(shù)。
選擇默認(rèn)構(gòu)造函數(shù)后,構(gòu)建器調(diào)用intercept方法使用MethodCall調(diào)用父類構(gòu)造函數(shù)的代碼。
然后,代碼使用FieldAccessordataProducerld的實(shí)例變量的值設(shè)置為120,并將data實(shí)例變量設(shè)置為字符串值<noData>。
與第10章中介紹的值法相比,集合值法是正確的方法:
動(dòng)態(tài)聲明實(shí)例變量。為Java字節(jié)碼編程時(shí),程序必須使用構(gòu)造函數(shù)來設(shè)置實(shí)例變量的初始值。

為什么DataProducer.java包含默認(rèn)構(gòu)造函數(shù),即使插件程序沒有聲明它?當(dāng)調(diào)用以下方法之一時(shí),ByteBuddy將隱式創(chuàng)建默認(rèn)構(gòu)造函數(shù):define,defineConstructor。
define方法也可以用于聲明實(shí)例變量和Java方法。
如果使用define方法聲明構(gòu)造函數(shù),則ByteBuddy將自動(dòng)創(chuàng)建默認(rèn)構(gòu)造函數(shù)。
因此,在創(chuàng)建DataProducer(int, String, String)構(gòu)造函數(shù)時(shí),已經(jīng)創(chuàng)建了默認(rèn)構(gòu)造函數(shù)。

使用visit方法生成構(gòu)造函數(shù)

最后,Plugin程序聲明了這個(gè)構(gòu)造函數(shù):

public DataProducer(int var1, String var2, String var3, String var4) {
        super((long)var1, var3);
        if (var1 % 2 == 0) {
            this.dataProducerId = var1 + 10000;
        } else {
            this.dataProducerId = var1 + 20000;
        }

        this.data = var2;
        this.int01 = new BigInteger(this.data);
}

這是生成構(gòu)造函數(shù)的代碼:

builder = builder.defineConstructor(Visibility.PUBLIC)
                .withParameters(int.class, String.class, String.class, String.class)
                .intercept(MethodCall
                        .invoke(Producer.class
                                .getDeclaredConstructor(long.class,String.class))
                        .withArgument(0,2));

builder = builder.visit(Advice
                .to(ValueSetter.class)
                .on(ElementMatchers.isConstructor()
                        .and(ElementMatchers.takesArgument(0,int.class))
                        .and(ElementMatchers.takesArgument(1,String.class))
                        .and(ElementMatchers.takesArgument(2,String.class))
                        .and(ElementMatchers.takesArgument(3,String.class))));

intercept方法只生成調(diào)用父類構(gòu)造函數(shù)的字節(jié)碼:

super((long)var1, var3);

剩余的代碼由visit方法生成。
visit方法中,匹配構(gòu)造函數(shù)ElementMatchers。
是構(gòu)造函數(shù)和ElementMatchers
使用takesArgument(0, int.class)方法。
isConstructor方法將匹配范圍僅限于Constructor。
takesArgument方法接受兩個(gè)參數(shù):intjava.lang.Class
第一個(gè)int參數(shù)指定參數(shù)的索引。
第二個(gè)參數(shù)指定其數(shù)據(jù)類型。
此方法僅匹配第一個(gè)參數(shù)。

因此,為了匹配具有四個(gè)參數(shù)的構(gòu)造函數(shù),takesArgument被執(zhí)行四次。
使用此配置,應(yīng)選擇構(gòu)造函數(shù)public DataProducer(int, String, String, String)來應(yīng)用Advice代碼。

ValueSetter.java:

public class ValueSetter{
    @Advice.OnMethodExit
    public static void set(
            int param0, 
            String param1,
            @Advice.FieldValue(value="dataProducerId", readOnly=false)int var1,
            @Advice.FieldValue(value="data", readOnly=false) String var2,
            @Advice.FieldValue(value="int01", readOnly=false) BigInteger biginteger){
        if(param0 % 2 == 0)
            var1 = param0 + 10000;
        else
            var1 = param0 + 20000;
        var2 = param1;
        biginteger = new BigInteger(var2);
    }
}

之后,構(gòu)造函數(shù)主體將包含ValueSetter.java中提供的Advice代碼拷貝到DataProducer.class
Advice代碼實(shí)例化了一個(gè)名為"int01"的實(shí)例變量,它是BigInteger的一個(gè)實(shí)例。
每當(dāng)構(gòu)造函數(shù)想要使用new運(yùn)算符實(shí)例化對象時(shí)(例如,new BigInteger),都應(yīng)該為此實(shí)現(xiàn)Advice代碼。
然而,在撰寫本文時(shí),MethodCall的使用會(huì)在檢測過程中拋出TllegalStateException。

結(jié)論

本章說明:

  • 如何動(dòng)態(tài)聲明構(gòu)造函數(shù)
  • 如何使用MethodCall聲明構(gòu)造函數(shù)體如何將參數(shù)傳遞給超級構(gòu)造函數(shù)
  • 如何使用FieldAccessor.ofFieldsetsValue方法設(shè)置實(shí)例變量的初始值

bytebuddy書籍《Java Interceptor Development with ByteBuddy: Fundamental》

----END----

喜歡就點(diǎn)個(gè)??吧

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容