本章介紹如何動(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.java的apply方法中實(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.class和int.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.class和String.class。
然后使用WithArgument方法將var1和var3參數(shù)傳遞給父類構(gòu)造函數(shù)。
使用var1和var3參數(shù)是因?yàn)?code>withArgument方法指定了參數(shù)索引0和2。
這意味著DataProducer構(gòu)造函數(shù)的第一個(gè)和第三個(gè)參數(shù)。
類傳遞給其父類構(gòu)造函數(shù)。
之后,構(gòu)造函數(shù)主體將var2和var4分配給dataProducerld和data實(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ā)ClassNotFoundException和SQLException。
這些異常是通過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ù)的代碼。
然后,代碼使用FieldAccessor將dataProducerld的實(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ù):int和java.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.ofField的setsValue方法設(shè)置實(shí)例變量的初始值
bytebuddy書籍《Java Interceptor Development with ByteBuddy: Fundamental》
喜歡就點(diǎn)個(gè)??吧