摘要:在里面,最大的改变使运行时的成为可能。缺省情况下,将会在方法中计算这些,通过在加载这两个类进入虚拟机时,使用反射来计算。通过重写方法,更正获取的方式,改成使用指定的方式进行。
Grays Anatomy源码浅析
标签(空格分隔):JAVA JVM 问题排查
在知乎上看到一个问题,被R大推荐了这个线上排查的工具,就下来用了用,感觉还不错,知道是Java写的后,就行看看源码,相关知识比较欠缺,前后看了一个月左右,才知其大概原理,记录下来分给大家.
读源码前,需要掌握几点知识,不然硬看是看不懂的.
Instrumentation利用 Java 代码,即 java.lang.instrument 做动态 Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能从本地代码中解放出来,使之可以用 Java 代码的方式解决问题。使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。
实现方法:
1.实现ClassFileTransformer来完成AOP 2.编写 premain 函数编写一个 Java 类,包含如下两个方法当中的任何一个,来指定哪些class需要做AOP
public static void premain(String agentArgs, Instrumentation inst); public static void premain(String agentArgs);3.jar 文件打包
将这个 Java 类打包成一个 jar 文件,并在其中的 manifest 属性当中加入” Premain-Class”来指定步骤 2 当中编写的那个带有 premain 的 Java 类。(可能还需要指定其他属性以开启更多功能)
Manifest-Version: 1.0 Premain-Class: Premain4.运行
用如下方式运行带有 Instrumentation 的 Java 程序:
java -javaagent:jar 文件的位置 [= 传入 premain 的参数 ]
在 Java SE 6 里面,instrumentation 包被赋予了更强大的功能:启动后的 instrument、本地代码(native code)instrument,以及动态改变 classpath 等等。这些改变,意味着 Java 具有了更强的动态控制、解释能力,它使得 Java 语言变得更加灵活多变。
在 Java SE6 里面,最大的改变使运行时的 Instrumentation 成为可能。在 Java SE 5 中,Instrument 要求在运行前利用命令行参数或者系统参数来设置代理类,在实际的运行之中,虚拟机在初始化之时(在绝大多数的 Java 类库被载入之前),instrumentation 的设置已经启动,并在虚拟机中设置了回调函数,检测特定类的加载情况,并完成实际工作。但是在实际的很多的情况下,我们没有办法在虚拟机启动之时就为其设定代理,这样实际上限制了 instrument 的应用。而 Java SE 6 的新特性改变了这种情况,通过 Java Tool API 中的 attach 方式,我们可以很方便地在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的。
另外,对 native 的 Instrumentation 也是 Java SE 6 的一个崭新的功能,这使以前无法完成的功能 —— 对 native 接口的 instrumentation 可以在 Java SE 6 中,通过一个或者一系列的 prefix 添加而得以完成。
最后,Java SE 6 里的 Instrumentation 也增加了动态添加 class path 的功能。所有这些新的功能,都使得 instrument 包的功能更加丰富,从而使 Java 语言本身更加强大。
实现方法:
1.实现ClassFileTransformer来完成AOP 2.编写 agentmain 函数编写一个 Java 类,包含如下两个方法当中的任何一个,来指定哪些class需要做AOP
public static void agentmain (String agentArgs, Instrumentation inst); public static void agentmain (String agentArgs);3.jar 文件打包
将这个 Java 类打包成一个 jar 文件,并在其中的 manifest 属性当中加入” Agent-Class”来指定步骤 2 当中编写的那个带有 agentmain 的 Java 类。(可能还需要指定其他属性以开启更多功能)
Manifest-Version: 1.0 Agent-Class: AgentMain4.运行
用如下方式运行带有 Instrumentation 的 Java 程序:
java -javaagent:jar 文件的位置 [= 传入 premain 的参数 ]
这里只是简要概述下,具体请点标题链接
ASMASM是一种字节码增强技术,即通过修改字节码来实现修改类的行为的功能.没有找到讲ASM比较好的博客,还是官方文档明晰透彻还会穿插着讲讲JVM的方法调用模型,建议阅读之,这里就举个ASM的小例子来说明它的使用.
类C如下所示
public class C { public void m() throws Exception { Thread.sleep(100); } }
通过ASM动态修改其字节码将其方法加上计算时间调用的功能
public class C { public static long timer; public void m() throws Exception { timer -= System.currentTimeMillis(); Thread.sleep(100); timer += System.currentTimeMillis(); } }
ASM方法编写如下,详情请见ASM官方文档Core API,其实若不考虑性能影响的话,Tree API更符合Java程序员的思维
public class AddTimerAdapter extends ClassVisitor { private String owner; private boolean isInterface; public AddTimerAdapter(ClassVisitor cv) { super(ASM4, cv); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { cv.visit(version, access, name, signature, superName, interfaces); owner = name; isInterface = (access & ACC_INTERFACE) != 0; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); if (!isInterface && mv != null && !name.equals("")) { mv = new AddTimerMethodAdapter(mv); } return mv; } @Override public void visitEnd() { if (!isInterface) { FieldVisitor fv = cv.visitField(ACC_PUBLIC + ACC_STATIC, "timer", "J", null, null); if (fv != null) { fv.visitEnd(); } } cv.visitEnd(); } public class AddTimerMethodAdapter extends MethodVisitor { public AddTimerMethodAdapter(org.objectweb.asm.MethodVisitor mv) { super(ASM4,mv); } @Override public void visitCode() { mv.visitCode(); mv.visitFieldInsn(GETSTATIC, owner, "timer", "J"); mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J"); mv.visitInsn(LSUB); mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J"); } @Override public void visitInsn(int opcode) { if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) { mv.visitFieldInsn(GETSTATIC, owner, "timer", "J"); mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J"); mv.visitInsn(LADD); mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J"); } mv.visitInsn(opcode); } @Override public void visitMaxs(int maxStack, int maxLocals) { mv.visitMaxs(maxStack + 4, maxLocals); } } public static void main(String[] args) throws IOException { ClassReader cr = new ClassReader("com.asm.temp.C"); ClassWriter cw = new ClassWriter(cr,0); TraceClassVisitor classVisitor = new TraceClassVisitor(cw,new PrintWriter(System.out)); AddTimerAdapter addTimerAdapter = new AddTimerAdapter(classVisitor); cr.accept(addTimerAdapter,0); System.out.println(cw.toByteArray()); } }
通过输出的字节码,可以看出其已加上了计算调用的功能
GETSTATIC C.timer : J INVOKESTATIC java/lang/System.currentTimeMillis()J LSUB PUTSTATIC C.timer : J LDC 100 INVOKESTATIC java/lang/Thread.sleep(J)V GETSTATIC C.timer : J INVOKESTATIC java/lang/System.currentTimeMillis()J LADD PUTSTATIC C.timer : J RETURN MAXSTACK = 4 MAXLOCALS = 1Grays Anatomy
Greys要实现的功能,是动态的监测JVM方法的执行.很自然的就想到了它会去实现Instrumentation的preMain和agentMain.AgentLauncher实现了preMain和agentMain方法,其都调用了main方法,其主要功能透过反射实例化一个GaServer.这个Server主要实现的就是接受命令,处理命令,返回响应.
public class AgentLauncher { public static void premain(String args, Instrumentation inst) { main(args, inst); } public static void agentmain(String args, Instrumentation inst) { main(args, inst); } private static synchronized void main(final String args, final Instrumentation inst) { try { // 传递的args参数分两个部分:agentJar路径和agentArgs // 分别是Agent的JAR包路径和期望传递到服务端的参数 final int index = args.indexOf(";"); final String agentJar = args.substring(0, index); final String agentArgs = args.substring(index, args.length()); // 将Spy添加到BootstrapClassLoader inst.appendToBootstrapClassLoaderSearch( new JarFile(AgentLauncher.class.getProtectionDomain().getCodeSource().getLocation().getFile()) ); // 构造自定义的类加载器,尽量减少Greys对现有工程的侵蚀 final ClassLoader agentLoader = loadOrDefineClassLoader(agentJar); // Configure类定义 final Class> classOfConfigure = agentLoader.loadClass("com.github.ompc.greys.core.Configure"); // GaServer类定义 final Class> classOfGaServer = agentLoader.loadClass("com.github.ompc.greys.core.server.GaServer"); // 反序列化成Configure类实例 final Object objectOfConfigure = classOfConfigure.getMethod("toConfigure", String.class) .invoke(null, agentArgs); // JavaPid final int javaPid = (Integer) classOfConfigure.getMethod("getJavaPid").invoke(objectOfConfigure); // 获取GaServer单例 final Object objectOfGaServer = classOfGaServer .getMethod("getInstance", int.class, Instrumentation.class) .invoke(null, javaPid, inst); // gaServer.isBind() final boolean isBind = (Boolean) classOfGaServer.getMethod("isBind").invoke(objectOfGaServer); if (!isBind) { try { classOfGaServer.getMethod("bind", classOfConfigure).invoke(objectOfGaServer, objectOfConfigure); } catch (Throwable t) { classOfGaServer.getMethod("destroy").invoke(objectOfGaServer); throw t; } } } catch (Throwable t) { t.printStackTrace(); } }
GaServer的bind方法为启动服务,启动后activeSelectorDaemon方法启动一个Daemon线程负责命令的处理,其中doRead是主要逻辑的实现,其中又委托给了CommandHandler的executeCommand方法解析输入行并执行命令.
public class GaServer { /** * 启动Greys服务端 * * @param configure 配置信息 * @throws IOException 服务器启动失败 */ public void bind(Configure configure) throws IOException { if (!isBindRef.compareAndSet(false, true)) { throw new IllegalStateException("already bind"); } try { serverSocketChannel = ServerSocketChannel.open(); selector = Selector.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.socket().setSoTimeout(configure.getConnectTimeout()); serverSocketChannel.socket().setReuseAddress(true); serverSocketChannel.register(selector, OP_ACCEPT); // 服务器挂载端口 serverSocketChannel.socket().bind(getInetSocketAddress(configure.getTargetIp(), configure.getTargetPort()), 24); logger.info("ga-server listening on network={};port={};timeout={};", configure.getTargetIp(), configure.getTargetPort(), configure.getConnectTimeout()); activeSelectorDaemon(selector, configure); } catch (IOException e) { unbind(); throw e; } } private void activeSelectorDaemon(final Selector selector, final Configure configure) { final ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE); final Thread gaServerSelectorDaemon = new Thread("ga-selector-daemon") { @Override public void run() { while (!isInterrupted() && isBind()) { try { while (selector.isOpen() && selector.select() > 0) { final Iteratorit = selector.selectedKeys().iterator(); while (it.hasNext()) { final SelectionKey key = it.next(); it.remove(); // do ssc accept if (key.isValid() && key.isAcceptable()) { doAccept(key, selector, configure); } // do sc read if (key.isValid() && key.isReadable()) { doRead(byteBuffer, key); } } } } catch (IOException e) { logger.warn("selector failed.", e); } catch (ClosedSelectorException e) { logger.debug("selector closed.", e); } } } }; gaServerSelectorDaemon.setDaemon(true); gaServerSelectorDaemon.start(); } private void doRead(final ByteBuffer byteBuffer, SelectionKey key) { final GaAttachment attachment = (GaAttachment) key.attachment(); final SocketChannel socketChannel = (SocketChannel) key.channel(); final Session session = attachment.getSession(); try { // 若读到EOF,则说明SocketChannel已经关闭 if (EOF == socketChannel.read(byteBuffer)) { logger.info("client={}@session[{}] was closed.", socketChannel, session.getSessionId()); // closeSocketChannel(key, socketChannel); session.destroy(); if(session.isLocked()) { session.unLock(); } return; } // decode for line byteBuffer.flip(); while (byteBuffer.hasRemaining()) { switch (attachment.getLineDecodeState()) { case READ_CHAR: { final byte data = byteBuffer.get(); if (" " == data) { attachment.setLineDecodeState(READ_EOL); } // 遇到中止命令(CTRL_D),则标记会话为不可写,让后台任务停下 else if (CTRL_D == data || CTRL_X == data) { session.unLock(); break; } // 普通byte则持续放入到缓存中 else { if (" " != data) { attachment.put(data); } break; } } case READ_EOL: { final String line = attachment.clearAndGetLine(session.getCharset()); executorService.execute(new Runnable() { @Override public void run() { // 会话只有未锁定的时候才能响应命令 if (session.tryLock()) { try { // 命令执行 commandHandler.executeCommand(line, session); // 命令结束之后需要传输EOT告诉client命令传输已经完结,可以展示提示符 socketChannel.write(ByteBuffer.wrap(new byte[]{EOT})); } catch (IOException e) { logger.info("network communicate failed, session[{}] will be close.", session.getSessionId()); session.destroy(); } finally { session.unLock(); } } else { logger.info("session[{}] was locked, ignore this command.", session.getSessionId()); } } }); attachment.setLineDecodeState(READ_CHAR); break; } } }//while for line decode byteBuffer.clear(); } // 处理 catch (IOException e) { logger.warn("read/write data failed, session[{}] will be close.", session.getSessionId(), e); closeSocketChannel(key, socketChannel); session.destroy(); } } }
接下来看看CommandHandler接口的默认实现DefaultCommandHandler,其executeCommand方法的主逻辑由excute实现,我们最关心的类增强部分是通过EnhancerAffect实现的.
public class DefaultCommandHandler implements CommandHandler { @Override public void executeCommand(final String line, final Session session) throws IOException { final Command command = Commands.getInstance().newCommand(line); execute(session, command); } /* * 执行命令 */ private void execute(final Session session, final Command command) throws GaExecuteException, IOException { // 需要做类增强的动作 else if (action instanceof GetEnhancerAction) { affect = new EnhancerAffect(); // 执行命令动作 & 获取增强器 final Command.GetEnhancer getEnhancer = ((GetEnhancerAction) action).action(session, inst, printer); final int lock = session.getLock(); final AdviceListener listener = getEnhancer.getAdviceListener(); final EnhancerAffect enhancerAffect = Enhancer.enhance( inst, lock, listener instanceof InvokeTraceable, getEnhancer.getPointCut() ); } } }
其增强其是AdviceListener的实现类,AdviceListener由before,afterReturning等围绕着方法执行阶段的方法组成,按道理这时候就该是ASM登场,修改类行为的时刻了.
/** * 前置通知 * * @param loader 类加载器 * @param className 类名 * @param methodName 方法名 * @param methodDesc 方法描述 * @param target 目标类实例 * 若目标为静态方法,则为null * @param args 参数列表 * @throws Throwable 通知过程出错 */ void before( ClassLoader loader, String className, String methodName, String methodDesc, Object target, Object[] args) throws Throwable; /** * 返回通知 * * @param loader 类加载器 * @param className 类名 * @param methodName 方法名 * @param methodDesc 方法描述 * @param target 目标类实例 * 若目标为静态方法,则为null * @param args 参数列表 * @param returnObject 返回结果 * 若为无返回值方法(void),则为null * @throws Throwable 通知过程出错 */ void afterReturning( ClassLoader loader, String className, String methodName, String methodDesc, Object target, Object[] args, Object returnObject) throws Throwable;
接下来看起增强方法,可见真正的增强class是Enhancer
public static synchronized EnhancerAffect enhance( final Instrumentation inst, final int adviceId, final boolean isTracing, final PointCut pointCut) throws UnmodifiableClassException { final EnhancerAffect affect = new EnhancerAffect(); final Map, Matcher > enhanceMap = toEnhanceMap(pointCut); // 构建增强器 final Enhancer enhancer = new Enhancer(adviceId, isTracing, enhanceMap, affect); try { inst.addTransformer(enhancer, true); // 批量增强 if (GlobalOptions.isBatchReTransform) { final int size = enhanceMap.size(); final Class>[] classArray = new Class>[size]; arraycopy(enhanceMap.keySet().toArray(), 0, classArray, 0, size); if (classArray.length > 0) { inst.retransformClasses(classArray); } } // for each 增强 else { for (Class> clazz : enhanceMap.keySet()) { try { inst.retransformClasses(clazz); } catch (Throwable t) { logger.warn("reTransform {} failed.", clazz, t); if (t instanceof UnmodifiableClassException) { throw (UnmodifiableClassException) t; } else if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new RuntimeException(t); } } } } } finally { inst.removeTransformer(enhancer); } return affect; }
Enhancer实现了Java Instrumentation的接口ClassFileTransformer,来看其核心方法transform
@Override public byte[] transform( final ClassLoader inClassLoader, final String className, final Class> classBeingRedefined, final ProtectionDomain protectionDomain, final byte[] classfileBuffer) throws IllegalClassFormatException { // 过滤掉不在增强集合范围内的类 if (!enhanceMap.containsKey(classBeingRedefined)) { return null; } final ClassReader cr; // 首先先检查是否在缓存中存在Class字节码 // 因为要支持多人协作,存在多人同时增强的情况 final byte[] byteOfClassInCache = classBytesCache.get(classBeingRedefined); if (null != byteOfClassInCache) { cr = new ClassReader(byteOfClassInCache); } // 如果没有命中缓存,则从原始字节码开始增强 else { cr = new ClassReader(classfileBuffer); } // 获取这个类所对应的asm方法匹配 final MatcherasmMethodMatcher = enhanceMap.get(classBeingRedefined); // 字节码增强 final ClassWriter cw = new ClassWriter(cr, COMPUTE_FRAMES | COMPUTE_MAXS) { /* * 注意,为了自动计算帧的大小,有时必须计算两个类共同的父类。 * 缺省情况下,ClassWriter将会在getCommonSuperClass方法中计算这些,通过在加载这两个类进入虚拟机时,使用反射API来计算。 * 但是,如果你将要生成的几个类相互之间引用,这将会带来问题,因为引用的类可能还不存在。 * 在这种情况下,你可以重写getCommonSuperClass方法来解决这个问题。 * * 通过重写 getCommonSuperClass() 方法,更正获取ClassLoader的方式,改成使用指定ClassLoader的方式进行。 * 规避了原有代码采用Object.class.getClassLoader()的方式 */ @Override protected String getCommonSuperClass(String type1, String type2) { Class> c, d; try { c = Class.forName(type1.replace("/", "."), false, inClassLoader); d = Class.forName(type2.replace("/", "."), false, inClassLoader); } catch (Exception e) { throw new RuntimeException(e); } if (c.isAssignableFrom(d)) { return type1; } if (d.isAssignableFrom(c)) { return type2; } if (c.isInterface() || d.isInterface()) { return "java/lang/Object"; } else { do { c = c.getSuperclass(); } while (!c.isAssignableFrom(d)); return c.getName().replace(".", "/"); } } }; try { // 生成增强字节码 cr.accept(new AdviceWeaver(adviceId, isTracing, cr.getClassName(), asmMethodMatcher, affect, cw), EXPAND_FRAMES); final byte[] enhanceClassByteArray = cw.toByteArray(); // 生成成功,推入缓存 classBytesCache.put(classBeingRedefined, enhanceClassByteArray); // dump the class dumpClassIfNecessary(className, enhanceClassByteArray, affect); // 成功计数 affect.cCnt(1); // 排遣间谍 try { spy(inClassLoader); } catch (Throwable t) { logger.warn("print spy failed. classname={};loader={};", className, inClassLoader, t); throw t; } return enhanceClassByteArray; } catch (Throwable t) { logger.warn("transform loader[{}]:class[{}] failed.", inClassLoader, className, t); } return null; }
其最主要的逻辑应该是派遣间谍了
/* * 派遣间谍混入对方的classLoader中 */ private void spy(final ClassLoader targetClassLoader) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { // 如果对方是bootstrap就算了 if (null == targetClassLoader) { return; } // Enhancer类只可能从greysClassLoader中加载 // 所以找他要ClassLoader是靠谱的 final ClassLoader greysClassLoader = Enhancer.class.getClassLoader(); final String spyClassName = GaStringUtils.SPY_CLASSNAME; // 从GreysClassLoader中加载Spy final Class> spyClassFromGreysClassLoader = loadSpyClassFromGreysClassLoader(greysClassLoader, spyClassName); if (null == spyClassFromGreysClassLoader) { return; } // 从目标ClassLoader中尝试加载或定义ClassLoader Class> spyClassFromTargetClassLoader = null; try { // 去目标类加载器中找下是否已经存在间谍 // 如果间谍已经存在就算了 spyClassFromTargetClassLoader = targetClassLoader.loadClass(spyClassName); logger.info("Spy already in targetClassLoader : " + targetClassLoader); } // 看来间谍不存在啊 catch (ClassNotFoundException cnfe) { try {// 在目标类加载起中混入间谍 spyClassFromTargetClassLoader = defineClass( targetClassLoader, spyClassName, toByteArray(Enhancer.class.getResourceAsStream("/" + spyClassName.replace(".", "/") + ".class")) ); } catch (InvocationTargetException ite) { if (ite.getCause() instanceof java.lang.LinkageError) { // CloudEngine 由于 loadClass 不到,会导致 java.lang.LinkageError: loader (instance of com/alipay/cloudengine/extensions/equinox/KernelAceClassLoader): attempted duplicate class definition for name: "com/taobao/arthas/core/advisor/Spy" // 这里尝试忽略 logger.debug("resolve #112 issues", ite); } else { throw ite; } } } // 无论从哪里取到spyClass,都需要重新初始化一次 // 用以兼容重新加载的场景 // 当然,这样做会给渲染的过程带来一定的性能开销,不过能简化编码复杂度 finally { if (null != spyClassFromTargetClassLoader) { // 初始化间谍 invokeStaticMethod( spyClassFromTargetClassLoader, "init", greysClassLoader, getField(spyClassFromGreysClassLoader, "ON_BEFORE_METHOD").get(null), getField(spyClassFromGreysClassLoader, "ON_RETURN_METHOD").get(null), getField(spyClassFromGreysClassLoader, "ON_THROWS_METHOD").get(null), getField(spyClassFromGreysClassLoader, "BEFORE_INVOKING_METHOD").get(null), getField(spyClassFromGreysClassLoader, "AFTER_INVOKING_METHOD").get(null), getField(spyClassFromGreysClassLoader, "THROW_INVOKING_METHOD").get(null) ); } } }
接下来看Spy的实现,发现其没什么特别的啊,怎么实现织入呢,期间在这迷了很久
public class Spy { // -- 各种Advice的钩子引用 -- public static volatile Method ON_BEFORE_METHOD; public static volatile Method ON_RETURN_METHOD; public static volatile Method ON_THROWS_METHOD; public static volatile Method BEFORE_INVOKING_METHOD; public static volatile Method AFTER_INVOKING_METHOD; public static volatile Method THROW_INVOKING_METHOD; /** * 代理重设方法 */ public static volatile Method AGENT_RESET_METHOD; /* * 用于普通的间谍初始化 */ public static void init( @Deprecated ClassLoader classLoader, Method onBeforeMethod, Method onReturnMethod, Method onThrowsMethod, Method beforeInvokingMethod, Method afterInvokingMethod, Method throwInvokingMethod) { ON_BEFORE_METHOD = onBeforeMethod; ON_RETURN_METHOD = onReturnMethod; ON_THROWS_METHOD = onThrowsMethod; BEFORE_INVOKING_METHOD = beforeInvokingMethod; AFTER_INVOKING_METHOD = afterInvokingMethod; THROW_INVOKING_METHOD = throwInvokingMethod; } /* * 用于启动线程初始化 */ public static void initForAgentLauncher( @Deprecated ClassLoader classLoader, Method onBeforeMethod, Method onReturnMethod, Method onThrowsMethod, Method beforeInvokingMethod, Method afterInvokingMethod, Method throwInvokingMethod, Method agentResetMethod) { ON_BEFORE_METHOD = onBeforeMethod; ON_RETURN_METHOD = onReturnMethod; ON_THROWS_METHOD = onThrowsMethod; BEFORE_INVOKING_METHOD = beforeInvokingMethod; AFTER_INVOKING_METHOD = afterInvokingMethod; THROW_INVOKING_METHOD = throwInvokingMethod; AGENT_RESET_METHOD = agentResetMethod; } public static void clean() { ON_BEFORE_METHOD = null; ON_RETURN_METHOD = null; ON_THROWS_METHOD = null; BEFORE_INVOKING_METHOD = null; AFTER_INVOKING_METHOD = null; THROW_INVOKING_METHOD = null; AGENT_RESET_METHOD = null; } }
迷久了,偶尔查看其方法的调用,发现奥妙,其真正值得织入逻辑原来是在AdviceWeaver的相关方法内
private static ClassLoader loadOrDefineClassLoader(String agentJar) throws Throwable { final ClassLoader classLoader; // 如果已经被启动则返回之前启动的classloader if (null != greysClassLoader) { classLoader = greysClassLoader; } // 如果未启动则重新加载 else { classLoader = new AgentClassLoader(agentJar); // 获取各种Hook final Class> adviceWeaverClass = classLoader.loadClass("com.github.ompc.greys.core.advisor.AdviceWeaver"); // 初始化全局间谍 Spy.initForAgentLauncher( classLoader, adviceWeaverClass.getMethod("methodOnBegin", int.class, ClassLoader.class, String.class, String.class, String.class, Object.class, Object[].class), adviceWeaverClass.getMethod("methodOnReturnEnd", Object.class, int.class), adviceWeaverClass.getMethod("methodOnThrowingEnd", Throwable.class, int.class), adviceWeaverClass.getMethod("methodOnInvokeBeforeTracing", int.class, Integer.class, String.class, String.class, String.class), adviceWeaverClass.getMethod("methodOnInvokeAfterTracing", int.class, Integer.class, String.class, String.class, String.class), adviceWeaverClass.getMethod("methodOnInvokeThrowTracing", int.class, Integer.class, String.class, String.class, String.class, String.class), AgentLauncher.class.getMethod("resetGreysClassLoader") ); } return greysClassLoader = classLoader; }
抽出一个方法来看,其最终还是委托listner的before来实现的,MonitorCommand只是实现一个invokeCost.Begin.可是没有字节码增强啊,怎么能动态实现呢
/** * 方法开始
* 用于编织通知器,外部不会直接调用 * * @param loader 类加载器 * @param adviceId 通知ID * @param className 类名 * @param methodName 方法名 * @param methodDesc 方法描述 * @param target 返回结果 * 若为无返回值方法(void),则为null * @param args 参数列表 */ public static void methodOnBegin( int adviceId, ClassLoader loader, String className, String methodName, String methodDesc, Object target, Object[] args) { if (!advices.containsKey(adviceId)) { return; } if (isSelfCallRef.get()) { return; } else { isSelfCallRef.set(true); } try { // 构建执行帧栈,保护当前的执行现场 final GaStack
其实上面都是幌子,真正的增强是透过visitMethod实现的,其又委托了AdviceAdapter实现,其onMethodEnter方法是真正的before类增强(参见ASM官方文档),
这里面不只用了字节码增强,还直接操作了堆栈,这部分看的云里雾里的.你有好的资料推荐我学习,我会很感谢的,如果我实现的话应该就如上述ASM例子中的实现,加字节码之类的吧.
@Override protected void onMethodEnter() { codeLockForTracing.lock(new CodeLock.Block() { @Override public void code() { final StringBuilder append = new StringBuilder(); _debug(append, "debug:onMethodEnter()"); // 加载before方法 loadAdviceMethod(KEY_GREYS_ADVICE_BEFORE_METHOD); _debug(append, "loadAdviceMethod()"); // 推入Method.invoke()的第一个参数 pushNull(); // 方法参数 loadArrayForBefore(); _debug(append, "loadArrayForBefore()"); // 调用方法 invokeVirtual(ASM_TYPE_METHOD, ASM_METHOD_METHOD_INVOKE); pop(); _debug(append, "invokeVirtual()"); } }); mark(beginLabel); }
最近看了几个阿里开源的框架或工具,希望能有机会去阿里码代码,和优秀的人一起共事.
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/67483.html
为什么读webpack源码 因为前端框架离不开webpack,天天都在用的东西啊,怎能不研究 读源码能学到很多做项目看书学不到的东西,比如说架构,构造函数,es6很边缘的用法,甚至给函数命名也会潜移默化的影响等 想写源码,不看源码怎么行,虽然现在还不知道写什么,就算不写什么,看看别人写的总可以吧 知道世界的广阔,那么多插件,那么多软件开发师,他们在做什么,同样是写js的,怎么他们能这么伟大 好奇...
摘要:前言本文的目的是阅读理解的源码,作为集合中重要的一个角色,平时用到十分多的一个类,深入理解它,知其所以然很重要。 前言 本文的目的是阅读理解HashMap的源码,作为集合中重要的一个角色,平时用到十分多的一个类,深入理解它,知其所以然很重要。本文基于Jdk1.7,因为Jdk1.8改变了HashMap的数据结构,进行了优化,我们先从基础阅读,之后再阅读理解Jdk1.8的内容 HashMa...
摘要:大多的初学者都会使用中间件来处理异步请求,其理解简单使用方便具体使用可参考官方文档。源码的源码非常简洁,出去空格一共只有行,这行中如果不算上则只有行。官方文档中的一节讲解的非常好,也确实帮我理解了中间件的工作原理,非常推荐阅读。 总觉得文章也应该是有生命力的,欢迎关注我的Github上的博客,这里的文章会依据我本人的见识,逐步更新。 大多redux的初学者都会使用redux-thunk...
阅读 2261·2021-10-09 09:41
阅读 3114·2021-09-26 09:46
阅读 804·2021-09-03 10:34
阅读 3110·2021-08-11 11:22
阅读 3335·2019-08-30 14:12
阅读 685·2019-08-26 11:34
阅读 3308·2019-08-26 11:00
阅读 1694·2019-08-26 10:26