ByteBuddy字节码增强器

Byte Buddy是java的字节码增强器,一个优雅的运行时java代码生成库,使用时需要慎重

文档地址:http://bytebuddy.net/#/tutorial-cn

1. 引入ByteBuddy

 <!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
 <dependency>
     <groupId>net.bytebuddy</groupId>
     <artifactId>byte-buddy</artifactId>
     <version>1.14.17</version>
 </dependency>

2. ByteBuddy学习

2.1 类的创建

任何类的实例创建都是从ByteBuddy的实例开始

DynamicType.Unloaded<Object> dynamicType = new ByteBuddy()
        .subclass(Object.class)// 增强方式:目标类生成子类
        .name("example.Type") // 自定义生成的类名:包名+类名
        .make();// 编译生成该类

2.2 指定类的包名

 DynamicType.Unloaded<?> dynamicType = byteBuddy
         .with(new NamingStrategy.AbstractBase() {
               @Override
               protected String name(TypeDescription superClass) {
                   return "example." + superClass.getSimpleName(); // 可自定义包名和类型
               }
           }) 
           .subclass(Object.class)
           .make();

2.3 类的保存

DynamicType.Unloaded<Object> dynamicType = new ByteBuddy()
dynamicType.saveIn(new File("文件路径"));// 将类Class文件保存文件夹

2.4 类的注入

可以将动态的生成的类注入到指定jar包中

DynamicType.Unloaded<Object> dynamicType = new ByteBuddy()
dynamicType.inject(new File("jar"));// 将类Class文件注入到jar包中

2.5 ByteBuddy增强方式

ByteBuddy共有三种增强方式:

  • subclass:为目标类生成子类进行增强
  • rebase:当对类型变基时,Byte Buddy 会保留所有被变基类的方法实现。Byte Buddy 会用兼容的签名复制所有方法的实现为一个私有的重命名过的方法, 而不像类重定义时丢弃覆写的方法。用这种方式的话,不存在方法实现的丢失,而且变基的方法可以通过调用这些重命名的方法(目前并未发现起作用)
  • redefine:重新定义方法,会替换已存在的方法实现。

2.6 类的加载策略

ClassLoadingStrategy.Default定义了内置策略,如果不选择,系统会自动默认推导出一个策略。

WRAPPER 策略:
	1. 创建一个新的 ClassLoader 来加载动态生成的类型。
	2. 适合大多数情况,这样生产的动态类不会被ApplicationClassLoader加载到,不会影响到项目中已经存在的类。
WRAPPER_PERSISTENT:该策略与WRAPPER相同,但通过ClassLoader.getResourceAsStream(String)公开表示类的字节数组。为此,所有类文件都在包装类加载器中作为字节数组持久化。
CHILD_FIRST:创建一个子类优先加载的 ClassLoader,即打破了双亲委派模型。
CHILD_FIRST_PERSISTENT:该策略与CHILD_FIRST相同,但通过ClassLoader.getResourceAsStream(String)公开表示类的字节数组。为此,所有类文件都在包装类加载器中作为字节数组持久化。
INJECTION 策略:使用反射,将动态生成的类型直接注入到当前 ClassLoader 中。

2.7 运行时实例化

DynamicType.Unloaded<Object> dynamicType = new ByteBuddy()
        .subclass(Object.class)// 增强方式:目标类生成子类
        .name("example.Type") // 自定义生成的类名:包名+类名
        .make();// 编译生成该类

2.8 字段和方法的声明

DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
           // 增强方式
           .subclass(Object.class)
           // 类名
           .name("subclass.Example")
           // 定义字段,类型、可见行 // 多个可使用|并列
           .defineField("username", String.class, Modifier.PUBLIC | Modifier.STATIC)
           // 定义get方法
           .defineMethod("getUserName", String.class, Modifier.PUBLIC)
           .intercept(FieldAccessor.ofField("username")) // 获取属性username的值
           .make();

2.9 FixedValue固定值

FixedValue可以声明任意固定值,用于对字段,方法的定义,固定值最终会写入类的常量池中。

2.10 ElementMatchers 元素选择器

ElementMatchers包含了大量匹配规则,用于覆写原类,常用的有:

  • isDeclaredBy 匹配某一个类中的所有方法
  • named 指定方法名
  • takesArguments 指定参数个数

如下示例:

public class Foo {
  public String bar() { return null; }
  public String foo() { return null; }
  public String foo(Object o) { return null; }
}
// 构建一个实现类
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
           .with(new NamingStrategy.AbstractBase() {
               @Override
               protected String name(TypeDescription superClass) {
                   return "subclass.subclass" + superClass.getSimpleName();
               }
           })
           .subclass(Foo.class)
           .method(ElementMatchers.isDeclaredBy(Foo.class)).intercept(FixedValue.value("One!"))
           .method(ElementMatchers.named("foo")).intercept(FixedValue.value("Two!"))
           .method(ElementMatchers.named("foo").and(ElementMatchers.takesArguments(1)))
                   .intercept(FixedValue.value("Three!"))
           .make();
 // 最终生成的类如下
 public class subclassFoo extends Foo {
    public String foo(Object var1) {return "Three!";}
    public String foo() {return "Two!";}
    public String bar() {return "One!";}
    public subclassFoo() {}
}

该示例中
bar()方法仅被ElementMatchers.isDeclaredBy(Foo.class)匹配到,最终覆写了One!
foo()方法被ElementMatchers.named(“foo”)和ElementMatchers.named(“foo”).and(ElementMatchers.takesArguments(1))匹配到,覆写了Tow!
foo(Object var1)然后再被ElementMatchers.named(“foo”).and(ElementMatchers.takesArguments(1))匹配,再次覆写,得到Three!

2.11 MethodDelegation(方法委托)

方法委托给,它对方法调用做出反应时提供了最大程度的自由。Source中的方法可以委托给Target方法调用(Source和Target的方法名可以不一致);注:所有的委托方法必须是static声明
简单示例:

// 变基对象
public class MemoryDatabase {
    public void hello(String name) {
        System.out.println("MemoryDatabase.hello origin");
    }
    public List<String> load(String info) {
        System.out.println("MemoryDatabase.load orign:" + info);
        return Arrays.asList(info, info);
    }
}
// 委托类
public class LoggerInterceptor {
    public static void hello(String name) {
        System.out.println("LoggerInterceptor.hello " + name + "!");
    }
    public static List<String> load(String info) {
        System.out.println("LoggerInterceptor.load  before");
        try {
            // 参数增强
            List<String> list = Arrays.asList(info + ": foo", info + ": bar");
            System.out.println("LoggerInterceptor.load after");
            return list;
        } finally {
            System.out.println("LoggerInterceptor.load finally");
        }
    }
}
// 测试方法
 MemoryDatabase loggingDatabase = new MemoryDatabase();
 loggingDatabase.hello("hello MemoryDatabase");
 System.out.println("MemoryDatabase.load return value:" + loggingDatabase.load("hello MemoryDatabase"));
 // 将MemoryDatabase类的指定方法进行委托变基
 new ByteBuddy()
         .redefine(MemoryDatabase.class)
         .method(named("hello")).intercept(MethodDelegation.to(LoggerInterceptor.class))
         .method(named("load")).intercept(MethodDelegation.to(LoggerInterceptor.class))
         .make()
         .load(MemoryDatabase.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent())// ClassReloadingStrategy.fromInstalledAgent()表示使用代理修改源方法
         .getLoaded();
 System.out.println("变基后。。。");
 loggingDatabase = new MemoryDatabase();
 loggingDatabase.hello("hello MemoryDatabase");
 System.out.println("MemoryDatabase.load return value:" + loggingDatabase.load("hello MemoryDatabase"));
// 运行结果
MemoryDatabase.hello origin
MemoryDatabase.load orign:hello MemoryDatabase
MemoryDatabase.load return value:[hello MemoryDatabase, hello MemoryDatabase]
变基后。。。
LoggerInterceptor.hello hello MemoryDatabase!
LoggerInterceptor.load  before
LoggerInterceptor.load after
LoggerInterceptor.load finally
MemoryDatabase.load return value:[hello MemoryDatabase: foo, hello MemoryDatabase: bar]

上面这个简单示例可以看出,方法委托可以将原来的方法直接替换掉,进行变基修改。在进行修改源码中起到了重要作用。注:subclass是对父类的增强,不会对源类造成影响,redefine和rebase是需要代理权限的,需要再启动类加载前添加VM参数:-javaagent:路径地址\byte-buddy-agent-1.14.17.jarbyte-buddy-agent指的是代码jar,网上搜索即可下载。下面就一一开始学习方法委托的各种场景:

  • @SuperCall

    public class MemoryDatabase {
        public List<String> load(String info) {
            System.out.println("MemoryDatabase.load orign:" + info);
            return Arrays.asList(info, info);
        }
    }
    public class LoggerInterceptor {
        public static List<String> log(@SuperCall Callable<List<String>> zuper) {
            System.out.println("LoggerInterceptor.log before");
            try {
                // 参数增强
                List<String> call = zuper.call();
                System.out.println("LoggerInterceptor.log after");
                return call;
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                System.out.println("LoggerInterceptor.log finally");
            }
        }
    }
    // 测试TEST:
    MemoryDatabase loggingDatabase = new ByteBuddy()
            .subclass(MemoryDatabase.class)
            .method(isDeclaredBy(MemoryDatabase.class)).intercept(MethodDelegation.to(LoggerInterceptor.class))
            .make()
            .load(MemoryDatabase.class.getClassLoader())
            .getLoaded().newInstance();
    System.out.println("MemoryDatabase.load return value:" + loggingDatabase.load("hello MemoryDatabase"));
    

    上面的示例将MemoryDatabase.load委托给了LoggerInterceptor.log;@SuperCall声明表示这是一个代理方法,指的就是MemoryDatabase.load。其中Callable<List>表示load方法的返回类型。这种方式其实就是spring aop的实现方式。

  • @Super

    public class MemoryDatabase {
        public List<String> load(String info) {
            System.out.println("MemoryDatabase.load orign:" + info);
            return Arrays.asList(info, info);
        }
    }
    public class LoggerInterceptor {
        // info表示方法参数,zuper注解@Super声明,表示代理对象
        public static List<String> log(String info, @Super MemoryDatabase zuper) {
            System.out.println("LoggerInterceptor.log before");
            try {
                // 参数增强
                List<String> strings = zuper.load(info + " Interceptor");
                System.out.println("LoggerInterceptor.log after");
                return strings;
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                System.out.println("LoggerInterceptor.log finally");
            }
        }
    }
    // 测试TEST:
    MemoryDatabase loggingDatabase = new ByteBuddy()
            .subclass(MemoryDatabase.class)
            .method(isDeclaredBy(MemoryDatabase.class)).intercept(MethodDelegation.to(LoggerInterceptor.class))
            .make()
            .load(MemoryDatabase.class.getClassLoader())
            .getLoaded().newInstance();
    System.out.println("MemoryDatabase.load return value:" + loggingDatabase.load("hello MemoryDatabase"));
    

    @Super是明确指定了代理实例。进行精确化的实现

  • @RuntimeType

public class MemoryDatabase {
    public List<String> load(String info) {
        System.out.println("MemoryDatabase.load orign:" + info);
        return Arrays.asList(info, info);
    }
    public List<Integer> load(Integer info) {
        System.out.println("MemoryDatabase.load orign:" + info);
        return Arrays.asList(info, info);
    }
}
public class LoggerInterceptor {
// @RuntimeType运行时指定具体参数类型
    public static List<String> log(@RuntimeType Object info, @Super MemoryDatabase zuper) {
            // 参数增强
            List<String> strings = zuper.load(info + " Interceptor");
            System.out.println("LoggerInterceptor.log :" + info);
            return strings;
    }
}

@RuntimeType运行时指定参数,可以实现方法重载的效果

  • @Pipe

持续学习更新中。。。

借鉴文章:https://blog.csdn.net/zhou920786312/article/details/130649115

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

£漫步 云端彡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值