1. javaagent 1) 简述 从java5开始,jdk中新增了一个java.lang.instrument.Instrumentation 类,它提供在运行时重新加载某个类的的class文件的api。 通过addTransformer可以加入一个转换器,转换器可以实现对类加载的事件进行拦截并返回转换后新的字节码,通过redefineClasses或retransformClasses都可以触发类的重新加载事件。
2)加载方式
在jvm启动时指定agent,Instrumentation对象会通过agent的premain方法传递。
在jvm启动后通过jvm提供的机制加载agent,Instrumentation对象会通过agent的agentmain方法传递。
3)主流的作用 java instrument在很多应用领域都发挥着重要的作用,比如:
apm:(Application Performance Management)应用性能管理。pinpoint、cat、skywalking等都基于Instrumentation实现
idea的HotSwap、Jrebel等热部署工具
应用级故障演练
Java诊断工具Arthas、Btrace等1 2 Btrace开源地址:https://gi thub.com/btraceio/ btrace Arthas开源地址:https://gi thub.com/alibaba/ arth
2.ASM 1)什么是ASM ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
2)如何使用ASM ASM框架中的核心类有以下几个: ① ClassReader:该类用来解析编译过的class字节码文件。 ② ClassWriter:该类用来重新构建编译后的类,比如说修改类名、属性以及方法,甚至可以生成新的类的字节码文件。 ③ ClassAdapter:该类也实现了ClassVisitor接口,它将对它的方法调用委托给另一个ClassVisitor对象。
3)样例 ASM代理方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public class ASMProxy extends ClassLoader { public static <T> T getProxy (Class clazz) throws Exception { ClassReader classReader = new ClassReader (clazz.getName()); ClassWriter classWriter = new ClassWriter (classReader, ClassWriter.COMPUTE_MAXS); classReader.accept(new ClassVisitor (ASM5, classWriter) { @Override public MethodVisitor visitMethod (int access, final String name, String descriptor, String signature, String[] exceptions) { if (!"queryUserInfo" .equals(name)) return super .visitMethod(access, name, descriptor, signature, exceptions); final MethodVisitor methodVisitor = super .visitMethod(access, name, descriptor, signature, exceptions); return new AdviceAdapter (ASM5, methodVisitor, access, name, descriptor) { @Override protected void onMethodEnter () { methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System" , "out" , "Ljava/io/PrintStream;" ); methodVisitor.visitLdcInsn(name + " 你被代理了,By ASM!" ); methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream" , "println" , "(Ljava/lang/String;)V" , false ); super .onMethodEnter(); } }; } }, ClassReader.EXPAND_FRAMES); byte [] bytes = classWriter.toByteArray(); return (T) new ASMProxy ().defineClass(clazz.getName(), bytes, 0 , bytes.length).newInstance(); } }@Test public void test_ASMProxy () throws Exception { IUserApi userApi = ASMProxy.getProxy(UserApi.class); String invoke = userApi.queryUserInfo(); logger.info("测试结果:{}" , invoke); }
场景:全链路监控、破解工具包、CGLIB、Spring获取类元数据等 点评:这种代理就是使用字节码编程的方式进行处理,它的实现方式相对复杂,而且需要了解Java虚拟机规范相关的知识。因为你的每一步代理操作,都是在操作字节码指令,例如:Opcodes.GETSTATIC、Opcodes.INVOKEVIRTUAL,除了这些还有小200个常用的指令。但这种最接近底层的方式,也是最快的方式。所以在一些使用字节码插装的全链路监控中,会非常常见。
3. Byte-Buddy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class ByteBuddyProxy { public static <T> T getProxy(Class clazz) throws Exception { DynamicType.Unloaded<?> dynamicType = new ByteBuddy() .subclass(clazz) .method (ElementMatchers.<MethodDescription>named("queryUserInfo")) .intercept(MethodDelegation.to (InvocationHandler.class )) .make(); return (T) dynamicType.load (Thread.currentThread().getContextClassLoader()).getLoaded().newInstance(); } } @RuntimeTypepublic static Object intercept(@Origin Method method , @AllArguments Object [] args, @SuperCall Callable<?> callable) throws Exception { System .out .println(method .getName() + " 你被代理了,By Byte-Buddy!"); return callable.call (); } @Testpublic void test_ByteBuddyProxy() throws Exception { IUserApi userApi = ByteBuddyProxy.getProxy(UserApi.class ); String invoke = userApi.queryUserInfo(); logger.info ("测试结果:{}", invoke); }
场景:AOP切面、类代理、组件、监控、日志 点评:Byte Buddy 也是一个字节码操作的类库,但 Byte Buddy 的使用方式更加简单。无需理解字节码指令,即可使用简单的 API 就能很容易操作字节码,控制类和方法。比起JDK动态代理、cglib,Byte Buddy在性能上具有一定的优势。另外,2015年10月,Byte Buddy被 Oracle 授予了 Duke’s Choice大奖。该奖项对Byte Buddy的“ Java技术方面的巨大创新 ”表示赞赏。
4. Javassist代理方式 它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class JavassistProxy extends ClassLoader { public static <T> T getProxy (Class clazz) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.get(clazz.getName()); CtMethod ctMethod = ctClass.getDeclaredMethod("queryUserInfo" ); ctMethod.insertBefore("{System.out.println(\"" + ctMethod.getName() + " 你被代理了,By Javassist\");}" ); byte [] bytes = ctClass.toBytecode(); return (T) new JavassistProxy ().defineClass(clazz.getName(), bytes, 0 , bytes.length).newInstance(); } }@Test public void test_JavassistProxy () throws Exception { IUserApi userApi = JavassistProxy.getProxy(UserApi.class) String invoke = userApi.queryUserInfo(); logger.info("测试结果:{}" , invoke); }
场景:全链路监控、类代理、AOP 点评:Javassist 是一个使用非常广的字节码插装框架,几乎一大部分非入侵的全链路监控都是会选择使用这个框架。因为它不想ASM那样操作字节码导致风险,同时它的功能也非常齐全。另外,这个框架即可使用它所提供的方式直接编写插装代码,也可以使用字节码指令进行控制生成代码,所以综合来看也是一个非常不错的字节码框架。