bytebuddy之advice详解 & 注解详解

本文详细介绍了ByteBuddy的Advice注解及其用法,包括@OnMethodEnter和@OnMethodExit的skipOn、inline、suppress等属性,以及@This、@Argument、@Return、@Thrown等注解的应用,展示了如何在方法进入和退出时插入额外逻辑,并提供了相关示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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的简单应用

代码得位置
在这里插入图片描述
定义了两个一模一样得目标类CookerCooker2

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();

    }
}

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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值