字节码增强,实质就是在编译期或运行期进行
字节码插桩
,以便在运行期影响程序的
执行行为
。按照增强时机,可以分为
编译时增强
(
Pluggable Annotation Processing
),
运行时增强
(
代理类
,
java-agent
)。
实现方式:
asm,Javassist, cglib,java-proxy,bytebuddy
字节码工具
|
java-proxy
|
asm
|
Javassist
|
cglib
|
bytebuddy
|
类创建
|
支持
|
支持
|
支持
|
支持
|
支持
|
实现接口
|
支持
|
支持
|
支持
|
支持
|
支持
|
方法调用
|
支持
|
支持
|
支持
|
支持
|
支持
|
类扩展
|
不支持
|
支持
|
支持
|
支持
|
支持
|
父类方法调用
|
不支持
|
支持
|
支持
|
支持
|
支持
|
优点
|
容易上手,
简单动态代理首选
|
任意字节码插入,几乎不受限制
|
java原始语法,字符串形式插入,写入直观
|
bytebuddy看起来差不多
|
支持任意维度的拦截,可以获取原始类、方法,以及代理类和全部参数
|
缺点
|
功能有限,不支持扩展
|
学习难度大,编写代码量大
|
不支持jdk1.5以上的语法,如泛型,增强for
|
正在被bytebuddy淘汰
|
不太直观,学习理解有些成本,API非常多
|
常见应用
|
spring-aop,MyBatis
|
cglib,bytebuddy
|
Fastjson,MyBatis
|
spring-aop,EasyMock,jackson-databind
|
SkyWalking
,Mockito,Hibernate,powermock
|
学习成本
|
一星
|
五星
|
二星
|
三星
|
三星
|
使用场景:
由于字节码增强可以在完全不侵入业务代码的情况下植入代码逻辑,常见的场景:
-
动态代理;
-
热部署;
-
调用链跟踪埋点;
-
动态插入log(性能监控);
-
测试代码覆盖率跟踪;
-
java-proxy介绍
java动态代理基于InvocationHandler接口,代码由ProxyGenerator.generateProxyClass生成,简单直观。
生成的Proxy实现接口全部方法,内部调用InvocationHandler的invoke方法
例子
动物咖啡接口
public interface AnimalCoffee {
Object make();
public interface CoffeeNameAware {
String getName();
}
生成一个原味咖啡
AnimalCoffee original = (AnimalCoffee) Proxy.newProxyInstance(AnimalCoffee.class.getClassLoader(), new Class<?>[]{AnimalCoffee.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Map<String, Object> result = new HashMap<>();
result.put("口味", "原味");
result.put("产地", "中国");
result.put("价格", "10¥");
return result;
});
原味咖啡生成的字节码
//将hashCode,toString,equals方法忽略
public final class $Proxy4 extends Proxy implements AnimalCoffee {
private static Method m3;
public $Proxy4(InvocationHandler var1) throws {
super(var1);
public final Object make() throws {
try {
return (Object)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
static {
try {
m3 = Class.forName("AnimalCoffee").getMethod("make");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
ASM介绍
asm插入字节码是原始字节码语法,上手难度高,原始语法不受框架限制,任意插入字节码;
ASM API 是基于 ClassVisitor 抽象类的。这 个 类中的每个方法都对应于同名的类文件结构部分。简单的部分只需一个方法调 用就能 访问,这个调用返回 void,其参数描述了这些部分的内容。有些部分的内容可以达到 任意长度、 任意复杂度,这样的部分可以用一个初始方法调用来访问,返回一个辅助的访问者 类。 visitAnnotation、visitField 和 visitMethod 方法就是这种情况,它们分别返 回 AnnotationVisitor、FieldVisitor 和 MethodVisitor.
ClassReader (读取)→ ClassVisitor(链式修改)→ ClassWriter → (保存写入)
//javap -v 查看
getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
打印类字节码
ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader("java.lang.Runnable");
cr.accept(cp, 0);
生成类-原味咖啡
ClassWriter classWriter = new ClassWriter(0);
FieldVisitor fieldVisitor;
MethodVisitor methodVisitor;
AnnotationVisitor annotationVisitor0;
classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "OriginalAnimalCoffee", null, "java/lang/Object", new String[]{"AnimalCoffee"});
classWriter.visitSource("OriginalAnimalCoffee.java", null);
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(12, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
methodVisitor.visitInsn(RETURN);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLocalVariable("this", "LOriginalAnimalCoffee;", null, label0, label1, 0);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "make", "()Ljava/lang/Object;", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(15, label0);
methodVisitor.visitTypeInsn(NEW, "java/util/HashMap");
methodVisitor.visitInsn(DUP);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "<init>", "()V", false);
methodVisitor.visitVarInsn(ASTORE, 1);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(16, label1);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitLdcInsn("\u53e3\u5473");
methodVisitor.visitLdcInsn("\u539f\u5473");
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitInsn(POP);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(17, label2);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitLdcInsn("\u4ea7\u5730");
methodVisitor.visitLdcInsn("\u4e2d\u56fd");
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitInsn(POP);
Label label3 = new Label();
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(18, label3);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitLdcInsn("\u4ef7\u683c");
methodVisitor.visitLdcInsn("10\u00a5");
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitInsn(POP);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLineNumber(19, label4);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitInsn(ARETURN);
Label label5 = new Label();
methodVisitor.visitLabel(label5);
methodVisitor.visitLocalVariable("this", "LOriginalAnimalCoffee;", null, label0, label5, 0);
methodVisitor.visitLocalVariable("result", "Ljava/util/Map;", "Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;", label1, label5, 1);
methodVisitor.visitMaxs(3, 2);
methodVisitor.visitEnd();
classWriter.visitEnd();
byte[] classByte = classWriter.toByteArray();
转换(修改)类
byte[] oldCl = oldClassByte;
ClassReader cr = new ClassReader(oldCl); //读取
ClassWriter cw = new ClassWriter(0);
cr.accept(cw, 0); //将old写入cw
//新增一个字段
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I", null, new Integer(-1)).visitEnd();
byte[] newCl = cw.toByteArray();
//引入一个 ClassVisitor:
ClassReader cr = new ClassReader(oldCl);
ClassWriter cw = new ClassWriter(0);
// cv 将所有事件转发给 cw
ClassVisitor cv = new ClassVisitor(ASM4, cw) { };
cr.accept(cv, 0); //将old写入cv
byte[] newCl2 = cw.toByteArray();
给出了与上述代码相对应的体系结构,其中的组件用方框表示,事件用箭头表示(其中的垂直时间线与程序图中一样)。
转换链
public class ChangeVersionAdapter extends ClassVisitor {
public ChangeVersionAdapter(ClassVisitor cv) {
super(ASM4, cv);
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
cv.visit(V1_5, access, name, signature, superName, interfaces);
}
// 和前面相比,这里是互相拥有,前面是单一拥有
byte[] b1 = oldClassByte;
ClassReader cr = new ClassReader(b1);
ClassWriter cw = new ClassWriter(cr, 0); ChangeVersionAdapter ca = new ChangeVersionAdapter(cw); cr.accept(ca, 0);
byte[] b2 = cw.toByteArray();
jdk-agent:
instrument
应用
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
public byte[] transform(ClassLoader l, String name, Class c,
ProtectionDomain d, byte[] b)
throws IllegalClassFormatException {
ClassReader cr = new ClassReader(b);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new ChangeVersionAdapter(cw);
cr.accept(cv, 0);
return cw.toByteArray();
}
移除类成员
用于转换类版本的方法当然也可用于 ClassVisitor 类的其他方法。例如,通过 改 变 visitField 和 visitMethod 方法的 access 或 name 参数,可以改变一个字段 或一个方 法的修饰字段或名字。另外,除了在转发的方法调用中使用经过修改的参数之外,还 可以选择根 本不转发该调用。其效果就是相应的类元素被移除。
public class RemoveMethodAdapter extends ClassVisitor {
private String mName;
private String mDesc;
public RemoveMethodAdapter( ClassVisitor cv, String mName, String mDesc) {
super(ASM4, cv);
this.mName = mName;
this.mDesc = mDesc;
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals(mName) && desc.equals(mDesc)) {
// 不要委托至下一个访问器 -> 这样将移除该方
return null;
return cv.visitMethod(access, name, desc, signature, exceptions);
}
Javassit相比于ASM要简单点,Javassit提供了更高级的API,当时执行效率上比ASM要差,因为ASM上直接操作的字节码。
工作步骤
ClassPool容器
-
getDefault (): 返回默认的ClassPool ,单例模式,一般通过该方法创建我们的ClassPool;
-
appendClassPath(ClassPath cp), insertClassPath(ClassPath cp) : 将一个ClassPath加到类搜索路径的末尾位置或插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类问题;
-
importPackage(String packageName):导入包;
-
makeClass(String classname):创建一个空类,没有变量和方法,后序通过CtClass的函数进行添加;
-
get(String classname)、getCtClass(String classname) : 根据类路径名获取该类的CtClass对象,用于后续的编辑。
CtClass类
-
debugDump;String类型,如果生成。class文件,保存在这个目录下。
-
setName(String name):给类重命名;
-
setSuperclass(CtClass clazz):设置父类;
-
addField(CtField f, Initializer init):添加字段(属性),初始值见CtField;
-
addMethod(CtMethod m):添加方法(函数);
-
toBytecode(): 返回修改后的字节码。
需要注意的是一旦调用该方法,则无法继续修改CtClass
;
-
toClass(): 将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的toClass方法是通过调用本方法实现。
需要注意的是一旦调用该方法,则无法继续修改已经被加载的CtClass
;
-
writeFile(String directoryName):根据CtClass生成 .class 文件;
-
defrost():解冻类,用于使用了toclass()、toBytecode、writeFile(),类已经被JVM加载,Javassist冻结CtClass后;
-
detach():避免内存溢出,从ClassPool中移除一些不需要的CtClass。
Loader类加载器
-
loadClass(String name):加载类
CtField字段
-
CtField(CtClass type, String name, CtClass declaring) :构造函数,添加字段类型,名称,所属的类;
-
CtField.Initializer constant():CtClass使用addField时初始值的设置;
-
setModifiers(int mod):设置访问级别,一般使用Modifier调用常量。
CtMethod方法
-
insertBefore(String src):在方法的起始位置插入代码;
-
insertAfter(String src):在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到exception;
-
insertAt(int lineNum, String src):在指定的位置插入代码;
-
addCatch(String src, CtClass exceptionType):将方法内语句作为try的代码块,插入catch代码块src;
-
setBody(String src):将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除;
-
setModifiers(int mod):设置访问级别,一般使用Modifier调用常量;
-
invoke(Object obj, Object... args):反射调用字节码生成类的方法。
//对于setBody $0代表this $1、$2、...代表方法的第几个参数
setBody("{$0.name = $1;}");
$符号含义
符号
|
含义
|
$0, $1, $2, ...
|
this,第几个参数
|
$args
|
参数列表. $args的类型是Object[].
|
$$
|
所有实参.例如, m($$) 等价于 m($1,$2,...)
|
$cflow(...)
|
cflow变量
|
$r
|
结果类型. 用于表达式转换.
|
$w
|
包装类型. 用于表达式转换.
|
$_
|
结果值
|
$sig
|
java.lang.Class列表,代表正式入参类型
|
$type
|
java.lang.Class对象,代表正式入参值.
|
$class
|
java.lang.Class对象,代表传入的代码段.
|
生成类-原味咖啡
ClassPool classPool = ClassPool.getDefault();
classPool.importPackage("java.util.Map");
classPool.importPackage("java.util.HashMap");
CtClass ctClass = classPool.makeClass("OriginalAnimalCoffee");
ctClass.addInterface(classPool.get("AnimalCoffee"));
ctClass.addMethod(CtMethod.make("public Object make() {\n" +
" Map result = new HashMap();\n" +
" result.put(\"口味\", \"原味\");\n" +
" result.put(\"产地\", \"中国\");\n" +
" result.put(\"价格\", \"10¥\");\n" +
" return result;\n" +
" }", ctClass));
byte[] classByte = ctClass.toBytecode();
ctClass.detach();
javassist常见错误
for (Header header : headers) {}
//高级语法导致,换成1.5jdk的循环
for (int i = 0; i < headers.length; i++){}
javassist.CannotCompileException: [source error] ; is missing
toParams(String query, Map<String, Object> params){}
//泛型语法导致,换成1.5jdk的
toParams(String query, Map params){}
javassist.CannotCompileException: [source error] syntax error near "ery, Map<String, Obj"
pool.importPackage("java.util.ArrayList");
//引入依赖包
javassist.CannotCompileException: [source error] no such class: ArrayList
常用核心API
ByteBuddy
-
流式API方式的入口类
-
提供Subclassing/Redefining/Rebasing方式改写字节码
-
所有的操作依赖DynamicType.Builder进行,创建不可变的对象
ElementMatchers(ElementMatcher)
-
提供一系列的元素匹配的工具类(named/any/nameEndsWith等等)
-
ElementMatcher(提供对类型、方法、字段、注解进行matches的方式,类似于Predicate)
-
Junction对多个ElementMatcher进行了and/or操作
DynamicType(动态类型,所有字节码操作的开始,非常值得关注)
-
Unloaded(动态创建的字节码还未加载进入到虚拟机,需要类加载器进行加载)
-
Loaded(已加载到jvm中后,解析出Class表示)
-
Default(DynamicType的默认实现,完成相关实际操作)
Implementation(用于提供动态方法的实现)
-
FixedValue(方法调用返回固定值)
-
MethodDelegation(方法调用委托,支持两种方式: Class的static方法调用、object的instance method方法调用)
Builder(用于创建DynamicType,相关接口以及实现后续待详解)
-
MethodDefinition
-
FieldDefinition
-
AbstractBase
常用注解说明
注解
|
说明
|
@Argument
|
绑定单个参数
|
@AllArguments
|
绑定所有参数的数组
|
@This
|
当前被拦截的、动态生成的那个对象
|
@Super
|
当前被拦截的、动态生成的那个对象的父类对象
|
@Origin
|
可以绑定到以下类型的参数:Method 被调用的原始方法 Constructor 被调用的原始构造器 Class 当前动态创建的类 MethodHandle MethodType Field 拦截的字段
|
@DefaultCall
|
调用默认方法而非super的方法
|
@SuperCall
|
用于调用父类版本的方法
|
@Super
|
注入父类型对象,可以是接口,从而调用它的任何方法
|
@RuntimeType
|
可以用在返回值、参数上,提示ByteBuddy禁用严格的类型检查
|
@Empty
|
注入参数的类型的默认值
|
@StubValue
|
注入一个存根值。对于返回引用、void的方法,注入null;对于返回原始类型的方法,注入0
|
@FieldValue
|
注入被拦截对象的一个字段的值
|
@Morph
|
类似于@SuperCall,但是允许指定调用参数
|
生成类-原味咖啡
Map<String, Object> result = new HashMap<>();
result.put("口味", "原味");
result.put("产地", "中国");
result.put("价格", "10¥");
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.subclass(AnimalCoffee.class)
//匹配make方法,返回固定值
.method(ElementMatchers.named("make")).intercept(FixedValue.value(result))
.method(ElementMatchers.named("equals")).intercept(FixedValue.value(true))
.method(ElementMatchers.named("toString")).intercept(FixedValue.value("原味咖啡"))
.defineMethod("drink", String.class, Modifier.PUBLIC).withParameter(String.class, "name")
//拦截drink方法,委托给MyDrinkInterceptor
.intercept(MethodDelegation.to(new MyDrinkInterceptor()))
//实现CoffeeNameAware接口,getName返回固定值
.implement(CoffeeNameAware.class)
.method(ElementMatchers.named("getName"))
.intercept(FixedValue.value("原味咖啡"))
.make();
Class<?> type = dynamicType.load(AnimalCoffee.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
AnimalCoffee original = (AnimalCoffee) type.newInstance();
System.out.println(original.make());
Assert.assertTrue(original.make() instanceof Map);
public class MyDrinkInterceptor {
@RuntimeType
public String drink(String name) {
return name + " 喝掉原味咖啡";
}
生成原味咖啡类字节码
public class AnimalCoffee$ByteBuddy$ySIOBpxK implements AnimalCoffee, CoffeeNameAware {
public boolean equals(Object var1) {
public String toString() {
return "原味咖啡";
//FixedValue.value 引用为静态对象的引用
public Object make() {
return value$shoqck1;
public String getName() {
return "原味咖啡";
//MethodDelegation.to 引用为静态代理对象的方法
public String drink(String name) {
return delegate$fkonef0.drink(var1);
}
代理类-摩卡咖啡
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.subclass(AnimalCoffee.class)
.method(ElementMatchers.isDeclaredBy(AnimalCoffee.class)
.or(ElementMatchers.isEquals())
.or(ElementMatchers.isToString()
.or(ElementMatchers.isHashCode()))
//委托代理
.intercept(MethodDelegation
.to(new AnimalCoffeeInterceptor(new MochaAnimalCoffee()))
//新增字段name
.defineField("name", String.class, Modifier.PUBLIC)
//实现CoffeeNameAware接口,返回字段name
.implement(CoffeeNameAware.class)
.method(ElementMatchers.named("getName"))
.intercept(FieldAccessor.ofField("name"))
//新增方法,设置字段name
.defineMethod("setName", Void.TYPE, Modifier.PUBLIC)
.withParameters(String.class)
.intercept(FieldAccessor.ofBeanProperty())
//新增构造函数,设置字段name
.defineConstructor(Modifier.PUBLIC)
.withParameters(String.class)
.intercept(MethodCall.invoke(Object.class.getConstructor()).andThen(
FieldAccessor.ofField("name").setsArgumentAt(0)
.make();
Class<?> type3 = dynamicType.load(AnimalCoffee.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION).getLoaded();
//创建有构造参数的对象
Object proxy = type3.getDeclaredConstructor(String.class).newInstance("摩卡咖啡");
System.out.println(((AnimalCoffee) proxy).make());
System.out.println(((CoffeeNameAware) proxy).getName());
public class MochaAnimalCoffee implements AnimalCoffee, CoffeeNameAware {
@Override
public Object make() {
Map<String, Object> result = new HashMap<>();
result.put("口味", "口感丝滑");
result.put("产地", "中国");
result.put("价格", "15¥");
return result;
@Override
public String getName() {
return null;
public class AnimalCoffeeInterceptor {
final AnimalCoffee animalCoffee;
public AnimalCoffeeInterceptor(AnimalCoffee animalCoffee) {
this.animalCoffee = animalCoffee;
@RuntimeType
public Object intercept(@This Object proxy, @Origin Method method, @AllArguments @RuntimeType Object[] args) throws Throwable {
String name = method.getName();
long start = System.currentTimeMillis();
if ("hashCode".equals(name)) {
return animalCoffee.hashCode();
} else if ("toString".equals(name)) {
return animalCoffee.toString();
} else if ("make".equals(name)) {
Object result = method.invoke(animalCoffee, args);
Map<String, Object> m = (Map<String, Object>) result;
m.put("热量", "360 千卡");
m.put("颜色", "深棕色");
m.put("制造耗时", System.currentTimeMillis() - start);
return m;
return method.invoke(animalCoffee, args);
}
摩卡咖啡代理类字节码
public class AnimalCoffee$ByteBuddy$ogesFMIU implements AnimalCoffee, CoffeeNameAware {
public String name;
//委托给AnimalCoffeeInterceptor
public Object make() {
return delegate$ntgsch1.intercept(this, cachedValue$v4z514W3$eafjf73, new Object[0]);
public String getName() {
return this.name;
public void setName(String var1) {
this.name = var1;
public AnimalCoffee$ByteBuddy$ogesFMIU(String var1) {
this.name = var1;
public AnimalCoffee$ByteBuddy$ogesFMIU() {
}
例子
java-agent应用
//挂载运行应用的java-agent
assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
//拦截任何的方法,并且绑定参数到MorphingCallable
AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
return builder.method(ElementMatchers.any())
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(MorphingCallable.class))
.to(RequestInterceptor.class));
//对任何类都拦截转换
ClassFileTransformer classFileTransformer =
new AgentBuilder.Default()
.type(ElementMatchers.any())
.transform(transformer)
.installOnByteBuddyAgent();
new Lottery().win();
System.out.println(new Lottery().say("ming", "hot"));
ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
public interface MorphingCallable<T> {
T call(Object... arguments);
public class RequestInterceptor {
* @SuperCall 注解注入的 Callable 参数来调用目标方法时,是无法动态修改参数的,如果想要动态修改参数,则需要用到
* @Morph 注解以及一些绑定操作,
* .intercept(MethodDelegation.withDefaultConfiguration().
* withBinders(Morph.Binder.install(MorphingCallable.class)).to(X.class)
@RuntimeType
public static Object interceptor(@This Object proxy, @AllArguments Object[] allArguments, @Origin Method method,
@SuperCall Callable<?> callable, @Morph MorphingCallable<?> overrideCallable) throws Exception {
long start = System.currentTimeMillis();
System.err.println("执行对象:" + proxy);
System.err.println("执行方法:" + method);
if (Objects.nonNull(allArguments)) {
for (Object argument : allArguments) {
System.err.println("执行参数:" + argument);
if (Objects.nonNull(argument)) {
System.out.println("执行参数对象:" + argument.getClass());
try {
Object o = null;
if (Objects.nonNull(allArguments)) {
//可以修改参数: allArguments[1] = "cold";
o = overrideCallable.call(allArguments);
} else {
o = callable.call();
System.err.println("执行结果:" + o);
if (Objects.nonNull(o)) {
System.out.println("执行结果对象:" + o.getClass());
return o;
} finally {
System.out.println("method: " + method + ",cost: " + (System.currentTimeMillis() - start));
}
静态类拦截
使用bytebuddy的redefine机制可以对静态方法进行代理或增强,但是redefine的前提是当前类未被加载过,因此你需要在你的程序中把握增强的时机(确保在类实际加载前执行redefine方法,我们选择的加载处是在java agent的premain方法中)。同时需要通过使用bytebuddy的类型池机制代替直接获取class,可参考的代码如下:
TypePool.Resolution describe = TypePool.Default.ofSystemLoader().describe("AbstractCodec");
new ByteBuddy().redefine(describe.resolve(), ClassFileLocator.ForClassLoader.ofSystemLoader())
.method(ElementMatchers.named("checkPayload"))
.intercept(MethodDelegation.to(MyInterceptor.class))
.make().load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION);
Java虚拟机(JVM)是Java语言的核心,它是一个虚拟的计算机,可以在不同的操作系统上运行Java程序。在Java中,字节码执行机制是JVM的重要组成部分,它负责将编译好的Java代码翻译成机器指令并执行。本文将介绍Java的字节码执行机制和常见问题。
Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。
相对于bcel, asm等这些工具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。javassist简单易用, 快速。