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