本章介绍如何使用ByteBuddy为Java类动态生成实例变量。
本章的项目没有拦截器包,而是有一个原型包。
在本章中,DataProducer.java是函数代码:
public class DataProducer{
private int data = -1;
}
DataProducer.java非常简单,包含一个名为data
的实例变量,并且没有方法。
所有代码生成逻辑都在InterceptorPlugin.java
中实现。
程序通过这个Plugin将多个新的实例变量添加到DataProducer
中。
maven编译后生成的class文件在target目录,这是由ByteBuddy生成的字节码DataProducer.class
:
public class DataProducer{
private int data = -1;
public static final String FINAL_DATA = "test1";
private int number;
private long longData;
public static final long SERIALNUMBER = 12345L;
private long number1;
protected long number2;
volatile long number3;
private transient long number4;
long number5;
public static final long number6;
private volatile transient long number7;
}
一、define方法
使用define
方法克隆实例变量
InterceptorPlugin.java
的apply
方法负责生成实例变量。
1、第一个生成的实例变量是FINAL_DATA
以下是代码语句:(第一次写完整的apply方法,之后就只写里面的内容了
)
@SneakyThrows
@Override
public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassFileLocator classFileLocator) {
Field f1 = InstanceVariablePrototype.class.getDeclaredField("FINAL_DATA");
return builder.define(f1).value("test1");
}
ByteBuddy将FINAL_DATA
的字段声明从InstanceVariablePrototype.class
克隆到DataProducer.class
中。
这是InstanceVariableProtype.java
中声明的变量FINAL_DATA
。
public class InstanceVariablePrototype {
public static final String FINAL_DATA = "example finaldata";
}
apply方法首先声明f1局部变量
,其类型为java.lang.reflect.Field
,并使用Field功能访问FINAL_DATA
变量的信息。
apply方法然后使用builder
的define
方法声明FINAL_DATA
变量,并使用value
方法设置默认值"test1"
:
builder.define(f1).value("test1")
因此,这是DataProducer.class
中生成的变量FINAL_DATA
:
public static final String FINAL_DATA = "test1";
2、接下来,apply方法将数字变量从InstanceVariablePrototype.class克隆到DataProducer.class中。
这些是代码语句:
Field f2 = InstanceVariablePrototype.class.getDeclaredField("number");
return builder.define(f2);
在InstanceVariablePrototype.java
中number
变量有一个@Deprecated
注解:
@Deprecated
private int number = 10000;
但是,过程中是不会克隆源变量的注解和值。
因此,这是DataProducer.class
中number
变量的结果是:
private int number;
观察到变量number
的修饰符是private
,这是因为InstanceVariablePrototype.java
中number
变量的修饰符是private
。
3、以下是从InstanceVariablePrototype.java克隆longData变量的代码语句:
Field f3 = InstanceVariablePrototype.class.getDeclaredField("longData");
return builder.define(f3);
因此,这是 DataProducer.class
中longData
变量的结果
private long longData;
4、使用defineField方法生成新实例变量
ByteBuddy还可以在不进行克隆的情况下声明新
的实例变量。
ByteBuddy为此使用defineField
方法。
这是用于在DataProducer.class
中声明SERIALNUMBER
变量的代码:
return builder.defineField(
"SERIALNUMBER",
long.class,
Visibility.PUBLIC,
FieldManifestation.FINAL,
Ownership.STATIC)
.value(12345L);
defineField
方法
第一个参数: 变量的名称。
第二个参数: 数据类型。
第三个参数: 可变长度参数,它用于指定实例变量的修饰符。
在此示例中,使用了以下修饰符类:
- net.bytebuddy.description.modifier.Visibility
- net.bytebuddy.description.modifier.FieldManifestation
- net.bytebuddy.description.modifier.Ownership
Visibility
用于指定可见性:public
、private
、protected
、无
。
FieldManifestion
用于为实例变量指定final
或volatile
关键字。
当程序想要声明静态时,使用Ownership
。
因此,检测过程为SERIALNUMBER
变量生成以下声明:
public static final long SERIALNUMBER = 12345L;
5、接下来,Plugin程序声明一个名为number1
的私有变量
return builder.defineField("number1", long.class, Visibility.PRIVATE);
此声明使用Visibility.PRIVATE
,这是生成的结果:
private long number1;
6、要声明受保护的变量,请使用Visibility.PROTECTED
;
return builder.defineField("number2", long.class, Visibility.PROTECTED)
结果:
protected long number2;
7、要声明package_private
和 volatile
变量,请执行以下操作:
return builder.defineField("number3", long.class, Visibility.PACKAGE_PRIVATE, FieldManifestation.VOLATILE)
结果:
volatile long number3;
8、要声明 private
和 transient
变量
return builder.defineField(“number4”,long.class,Visibility.PRIVATE,FieldPersistence.TRANSIENT)
结果:
private transient long number4;
9、要声明变量而不指定可见性修饰符,请执行以下操作:
return builder.defineField(“number5”,long.class)
如果未指定修饰符,则该变量默认为包访问。
long number5;
10、net.bytebuddy.jar.asm.Opcodes是为变量指定修饰符的另一种方法。
例如,此行声明了public static final
变量:
return builder.defineField(
"number6",
long.class,
Opcodes.ACC_PUBLIC|Opcodes.ACC_FINAL|Opcodes.ACC_STATIC)
检测结果:
public static final long number6;
操作码可以在一行中组合多个修饰符,每个修饰符用"|"
字符分隔。
另一个使用Opcodes的示例:
return builder.defineField(
"number7",
long.class,
Opcodes.ACC_PRIVATE|Opcodes.ACC_VOLATILE|Opcodes.ACC_TRANSIENT);
结果:
private transient volatile long number7;
二、value方法
value方法仅
适用于静态变量
。
例如,SERIALNUMBER是一个静态变量:
return builder.defineField(
"SERIALNUMBER",
long.class,
Visibility.PUBLIC,FieldManifestation.FINAL,Ownership.STATIC)
.value(12345L);
使用此配置,SERIALNUMBER变量将具有12345的值。
如果在非静态实例变量上使用value方法,例如:
builder.defineField(
"number1",
long.class,
Visibility.PRIVATE)
.value(1000L);
number1变量的值不会设置为1000。
要设置变量的值,必须通过构造函数
设置该值。
这是因为Java字节码规范要求构造函数为变量设置值。
如何在构造函数中为变量设置值将在第12篇文章中解释。
结论
本章仅说明如何动态声明实例变量。
bytebuddy书籍《Java Interceptor Development with ByteBuddy: Fundamental》
喜欢就点个👍吧