字节码增强是Java黑魔法,APM监控、AOP、Mock框架都依赖它。本文讲解ASM、Byte Buddy、Javassist和Java Agent,并实战写一个无侵入监控。
一、字节码基础
Java源码编译为.class,JVM加载并执行。字节码是JVM的指令集。
bash
复制
下载
javap -c -p MyClass.class
java
复制
下载
public class Hello { public static void main(String[] args) { System.out.println("Hello"); } }字节码:
text
复制
下载
0: getstatic #7 // System.out 3: ldc #13 // "Hello" 5: invokevirtual #15 // println 8: return
二、ASM
ASM直接操作字节码,性能高但API复杂。
java
复制
下载
ClassReader reader = new ClassReader(bytes); ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES); ClassVisitor visitor = new ClassVisitor(Opcodes.ASM9, writer) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if ("target".equals(name)) { return new MethodVisitor(Opcodes.ASM9, mv) { @Override public void visitCode() { mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("方法开始"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); super.visitCode(); } }; } return mv; } }; reader.accept(visitor, 0); byte[] newBytes = writer.toByteArray();三、Byte Buddy
API友好,适合快速开发。
java
复制
下载
Class<?> dynamicType = new ByteBuddy() .subclass(Object.class) .name("com.example.Hello") .method(ElementMatchers.named("toString")) .intercept(FixedValue.value("Hello World")) .make() .load(Hello.class.getClassLoader()) .getLoaded(); Object instance = dynamicType.getDeclaredConstructor().newInstance(); System.out.println(instance); // Hello World拦截已有方法:
java
复制
下载
new ByteBuddy() .redefine(UserService.class) .method(named("save")) .intercept(MethodDelegation.to(TimingInterceptor.class)) .make() .load(UserService.class.getClassLoader());java
复制
下载
public class TimingInterceptor { @RuntimeType public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable) throws Exception { long start = System.currentTimeMillis(); try { return callable.call(); } finally { System.out.println(method.getName() + " 耗时: " + (System.currentTimeMillis() - start) + "ms"); } } }四、Javassist
源码级API,适合简单场景。
java
复制
下载
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.example.UserService"); CtMethod m = cc.getDeclaredMethod("save"); m.insertBefore("System.out.println(\"before\");"); m.insertAfter("System.out.println(\"after\");"); cc.toClass();五、Java Agent
Agent可在JVM启动时或运行时增强类。
5.1 启动时Agent
java
复制
下载
public class MyAgent { public static void premain(String args, Instrumentation inst) { inst.addTransformer(new MyTransformer()); } }META-INF/MANIFEST.MF:
text
复制
下载
Premain-Class: com.example.MyAgent Can-Redefine-Classes: true Can-Retransform-Classes: true
启动:
bash
复制
下载
java -javaagent:myagent.jar -jar app.jar
5.2 运行时Attach
java
复制
下载
public class AgentMain { public static void agentmain(String args, Instrumentation inst) { inst.addTransformer(new MyTransformer(), true); for (Class<?> clazz : inst.getAllLoadedClasses()) { if (clazz.getName().startsWith("com.example")) { try { inst.retransformClasses(clazz); } catch (Exception e) {} } } } }Attach:
java
复制
下载
VirtualMachine vm = VirtualMachine.attach(pid); vm.loadAgent("myagent.jar"); vm.detach();六、实战:方法耗时监控Agent
java
复制
下载
public class TimingTransformer implements ClassTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain pd, byte[] classfileBuffer) { if (!className.startsWith("com/example/service")) { return classfileBuffer; } return new ByteBuddy() .redefine(new ByteArrayClassLoader(loader, classfileBuffer)) .method(isPublic().and(not(isStatic()))) .intercept(MethodDelegation.to(TimingInterceptor.class)) .make() .getBytes(); } }七、应用场景
APM监控:SkyWalking、Pinpoint
AOP:Spring AOP(CGLIB)
Mock:Mockito、JMockit
热部署:JRebel
性能分析:async-profiler
安全:RASP
八、总结
字节码增强是Java进阶技能。ASM性能高但复杂,Byte Buddy易用,Java Agent实现无侵入。掌握它们,能开发监控、诊断、框架类工具。