注解列表
bytebuddy实现原理分析 &源码分析(一)
bytebuddy实现原理分析 &源码分析 (二)
bytebuddy实现原理分析 &源码分析 (三)
bytebuddy实现原理分析 &源码分析 (四)
一、 注解列表
注解 | 值 | ||
---|---|---|---|
@OnMethodEnter |
表示这个方法会在,进入目标方法时调用,这个注解声明的方法必须是static 。当目标的方法是constructor 构造器时,@This 只能写field ,不能读field,或者调用方法 |
skipOn() | 跳过一些方法 |
prependLineNumber() | 如果为true,会改目标方法的行号 | ||
inline() | 标识方法应该被内联到目标的方法中去 | ||
suppress() | 忽略某些异常 | ||
@OnMethodExit |
表示这个方法会在,目标方法结束时调用,这个注解声明的方法必须是static 。如果方法提前终止了,那么这个就不i呗调用 |
repeatOn() | 标识目标方法是否被重复执行 |
onThrowable() | 一般被织入的方法抛出了某些异常,可以有响应的handler 处理 |
||
backupArguments() | 备份所有被执行方法的类型,开始会影响效率 | ||
inline() | 标识方法应该被内联到目标的方法中去 | ||
suppress() | 忽略某些异常 | ||
@This |
表示被注解的参数,应该是被修改对象的引用,不能用在静态方法和构造器上 |
optional() | 决定被修改的类型是否需要被设置为null,如果目标方法类型是static 或者在一个constructor |
readOnly() | 只读不能修改 | ||
typing() | 类型转化,默认要求静态转化,就是转化不能改变类型。动态是可以改变 | ||
@Argument |
被标注到目标类型的参数上,表示被标注的参数会被value()代表的索引拿到 | ||
readOnly() | 只读 | ||
typing() | 转换这个类型使用的方式,默认是静态转换(类型不会被改动),动态转换是void.class可以被改成其他类 | ||
optional() | 备选值,如果索引不存在,会使用这个提供的值。默认是关闭的 | ||
@AllArguments |
使用一个数组来包含目标方法的参数,目标的参数必须是一个数组。 | ||
readOnly() | 只读 | ||
typing() | 类型开关 | ||
@Return |
标注在参数上,来引用目标方法的返回值 | readOnly() | 只读 |
typing() |
类型转化,默认是静态转换 | ||
@Thrown |
获取抛出的异常 | ||
readOnly() | 只读 | ||
typing() | 默认是动态转化,可以改变类型 | ||
@FieldValue |
被注解的参数,可以引用,目标method内的定义的局部变量 | String value() | 局部变量的名称 |
declaringType() | 被声明的类型 | ||
readOnly() | 只读的 | ||
typing() | 默认是静态转化 | ||
@Origin |
使用一个String代表目标方法的 | String value() default DEFAULT | 默认值是"" |
@Enter |
标注在参数上,指向被标注@OnMethodEnter 的advice方法的返回值, |
||
readOnly() | 只读标记 | ||
typing() | 转换 | ||
@Exit |
标注在参数上,指向被标注@OnMethodExit 的advice方法的返回值, |
||
readOnly() | 只读标记 | ||
typing() | 转换 | ||
@Local |
声明被注解的参数当做一个本地变量,被Byte Buddy ,织入目标方法中。本地变量可以被@OnMethodEnter 和 @link OnMethodExit 读写。然而如果本地变量被exit advice 引用了,它必须也在enter 的advice所声明。就是用来交换变量的。 |
String value() | name |
@StubValue |
mock值,总是返回一个设定的值 | ||
@Unused |
让被标记的参数,总是返回默认值,比如int 返回0, 其他类型返回null |
二、 demo 解析
byte budddy 单测
测试修改的类。
byte buddy 实用手册
2.1 测试工具介绍
2.1.1 目标的类得classloader
ChildFirst
打破双亲委派,当前classLoader直接加载。
readToNames
返回Map<String , byte[]>
返回类和类得字节码。自己读取也可以。
MANIFEST
持久化策略,代表加载得会被保存下来
ClassLoader classLoader = new ByteArrayClassLoader.ChildFirst(getClass().getClassLoader(),
ClassFileLocator.ForClassLoader.readToNames(Cooker.class),
ByteArrayClassLoader.PersistenceHandler.MANIFEST);
2.1.2 获取当前jvm得 Instrumentation
ByteBuddyAgent.install();
一旦调用就会在内存中维护一个实例,实例中得Instrumentation
是当前jvm得应用。
2.2 @OnMethodEnter 和 @OnMethodExit的简单应用
代码得位置
定义了两个一模一样得目标类Cooker
和Cooker2
package bytebuddys.advice.target;
public class Cooker {
String name = "foo";
public Cooker() {
System.out.println("");
System.out.println("Im Cooker");
System.out.println("Constructor();");
}
public void hello() {
System.out.println("public void hello();");
}
public String makeFood(String foodName, int deskId, Double[] materialsPrice) {
System.out.println(materialsPrice.getClass().getName());
System.out.println("public String makeFood(String foodName, int deskId, Double[] materialsPrice)! ");
return foodName + ":" + deskId + ":" + materialsPrice.length;
}
public static void taste(String foodName) {
System.out.println("public static void taste(String foodName)! ");
}
}
我们给Cooker
(除了构造器)所有方法的开始和结束织入额外的逻辑
package bytebuddys.advice;
import net.bytebuddy.asm.Advice;
public class AdviceLogic {
@Advice.OnMethodEnter
public static void onMethodEnter(){
System.out.println("- - - - - - - - - - -");
System.out.println("enter!");
}
@Advice.OnMethodExit
public static void onMethodExit(){
System.out.println("exit!");
}
}
测试一下
我们对Cooker
改造,不动 Cooker2
package bytebuddys.advice;
import bytebuddys.advice.target.Cooker;
import bytebuddys.advice.target.Cooker2;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import java.lang.instrument.ClassFileTransformer;
public class TestAdvice {
public static TestAdvice INSTANCE = new TestAdvice();
ClassLoader classLoader;
/**
* 加载未被修改的类
*/
public void initClassLoader() throws Exception {
classLoader = new ByteArrayClassLoader.ChildFirst(getClass().getClassLoader(),
ClassFileLocator.ForClassLoader.readToNames(Cooker.class),
ByteArrayClassLoader.PersistenceHandler.MANIFEST);
}
/**
* modify
*/
public void modifyTarget() throws Exception {
ByteBuddyAgent.install();
ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
.with(AgentBuilder.PoolStrategy.Default.EXTENDED)
.with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
//.ignore()
// 设定匹配范围
.type(ElementMatchers.is(Cooker.class), ElementMatchers.is(classLoader)).transform(new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
// 对任何类都剩下
return builder.visit(Advice.to(AdviceLogic.class).on(ElementMatchers.not(ElementMatchers.isConstructor()).and(ElementMatchers.any())));
}
})
.installOnByteBuddyAgent();
}
public void print() throws Exception {
Class<Cooker> cookerType = (Class<Cooker>) classLoader.loadClass(Cooker.class.getName());
Object Cooker = cookerType.getDeclaredConstructor().newInstance();
cookerType.getDeclaredMethod("hello").invoke(Cooker);
cookerType.getDeclaredMethod("taste", String.class).invoke(null, "pototo");
cookerType.getMethod("makeFood", String.class, int.class, Double[].class).invoke(Cooker, "pototo", 1, new Double[]{
1.0, 2.0});
Class<Cooker2> cooker2Type = (Class<Cooker2>) classLoader.loadClass(Cooker2.class.getName());
Object Cooker2 = cooker2Type.getDeclaredConstructor().newInstance();
cooker2Type.getDeclaredMethod("hello").invoke(Cooker2);
cooker2Type.getDeclaredMethod("taste", String.class).invoke(Cooker2, "pototo");
cooker2Type.getDeclaredMethod("makeFood", String.class, int.class, Double[].class).invoke(Cooker2, "pototo", 1, new Double[]{
1.0, 2.0});
}
public static void main(String[] args) throws Exception {
INSTANCE.initClassLoader();
INSTANCE.modifyTarget();
INSTANCE.print();
}
}
Im Cooker
Constructor();
- - - - - - - - - - -
enter!
public void hello();
exit!
- - - - - - - - - - -
enter!
public static void taste(String foodName)!
exit!
- - - - - - - - - - -
enter!
[Ljava.lang.Double;
public String makeFood(String foodName, int deskId, Double[] materialsPrice)!
exit!
Im Cooker2
Constructor();
public void hello();
public static void taste(String foodName)!
[Ljava.la