Analysis of Grays Anatomy Source Code

Knowing a problem, R recommended this online search tool, and then came down to use it, feeling good, know that Java is written, go to see the source code, lack of relevant knowledge, before and after a month or so to know its general principle, record and analysis to you, the first time no previous blog can refer to see the source code, inappropriate also look forward to pointing out.

Before you read the source code, you need to master a few knowledge, otherwise you can't read it. There's no way to think about it.

Java Instrumentation

Using Java code, that is, java.lang.instrument to do dynamic Instrumentation is a new feature of Java SE 5. It frees Java instrument function from local code so that it can solve problems in the way of Java code. With Instrumentation, developers can build an application-independent Agent to monitor and assist programs running on JVM, and even to replace and modify the definitions of certain classes. With this feature, developers can achieve more flexible runtime virtual machine monitoring and Java class operations. This feature actually provides a virtual machine-level support for AOP implementation, so that developers can achieve some AOP functions without any upgrade and modification of JDK.

Method of realization:

1. Implement ClassFileTransformer to complete AOP

2. Writing premain function

Write a Java class that contains either of the following two methods to specify which classes need to do AOP

public static void premain(String agentArgs, Instrumentation inst); 
public static void premain(String agentArgs); 

3. Packaging of jar files

Pack this Java class into a jar file and add "Premain-Class" to its manifest attribute to specify the Java class with premain written in Step 2. (You may also need to specify additional properties to enable more functionality)

Manifest-Version: 1.0 
Premain-Class: Premain

4. Operation

Run a Java program with Instrumentation as follows:

Java-javaagent: The location of the jar file [= parameters passed into premain]

In Java SE 6, instrumentation packages are given more powerful functions: instrumentation after startup, native code instrumentation, dynamic change of classpath, and so on. These changes mean that Java has a stronger dynamic control and interpretation ability, which makes the Java language more flexible and changeable.
In Java SE6, the biggest change has made the runtime Instrumentation possible. In Java SE 5, instrument requires that proxy classes be set using command line parameters or system parameters before running. In actual operation, when the virtual machine is initialized (before most of the Java class libraries are loaded), instrumentation settings have been started, and callback functions have been set in the virtual machine to detect the loading of specific classes and complete the actual operation. Work. But in many cases, we can't set agents for virtual machines when they start, which actually limits the application of instrumentation. The new features of Java SE 6 change this situation. By attach ing in the Java Tool API, we can easily set up loading agent classes dynamically in the running process to achieve the purpose of instrumentation.
In addition, Instrumentation for native is a new function of Java SE 6, which makes instrumentation for native interface, which can be completed by adding one or a series of prefix in Java SE 6.
Finally, Instrumentation in Java SE 6 adds the ability to dynamically add class path. All these new features enrich the instrument package and make the Java language itself more powerful.

Method of realization:

1. Implement ClassFileTransformer to complete AOP

2. Writing premain function

Write a Java class that contains either of the following two methods to specify which classes need to do AOP

public static void agentmain (String agentArgs, Instrumentation inst); 
public static void agentmain (String agentArgs);    

3. Packaging of jar files

Pack this Java class into a jar file and add "Agent-Class" to its manifest attribute to specify the Java class with agent main written in Step 2. (You may also need to specify additional properties to enable more functionality)

Manifest-Version: 1.0 
Agent-Class: AgentMain

4. Operation

Run a Java program with Instrumentation as follows:

Java-javaagent: The location of the jar file [= parameters passed into premain]

Here is just a brief overview, please click on the title link.

ASM

ASM is a bytecode enhancement technology, which modifies the behavior of classes by modifying the bytecode. It is not found that the blog that talks about ASM is better or that the official document is clear and thorough. It is suggested to read the method invocation model that talks about JVM. Here is a small example of ASM to illustrate its use.

Class C is shown below.

public class C {
        public void m() throws Exception {
          Thread.sleep(100);
          }
}

Dynamic modification of bytecode by ASM to add the function of calculating time call to its method

 public class C {
        public static long timer;
        public void m() throws Exception {
          timer -= System.currentTimeMillis();
          Thread.sleep(100);
          timer += System.currentTimeMillis();
} }

The ASM method is written as follows. See the official ASM document Core API for details. In fact, Tree API is more in line with the thinking of Java programmers without considering the performance impact.

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("<init>")) {
            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());
    }

}

From the output bytecode, we can see that it has added the function of calculating calls.

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 = 1

Grays Anatomy

Greys implements the function of dynamically monitoring the execution of JVM methods. It is natural to think that it will implement Instrumentation's preMain and agent Main. Agent Launcher implements the preMain and agent Main methods, which call the main method. Its main function is to instantiate a Gaver through reflection. This Server mainly implements accepting commands, processing commands and returning responses.


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 {

            // The args parameters passed are divided into two parts: the agent Jar path and the agent Args
            // JAR Packet Path of Agent and Parameters Expected to Pass to Server
            final int index = args.indexOf(';');
            final String agentJar = args.substring(0, index);
            final String agentArgs = args.substring(index, args.length());

            // Add Spy to Bootstrap Class Loader
            inst.appendToBootstrapClassLoaderSearch(
                    new JarFile(AgentLauncher.class.getProtectionDomain().getCodeSource().getLocation().getFile())
            );

            // Construct a custom class loader to minimize Greys'erosion of existing projects
            final ClassLoader agentLoader = loadOrDefineClassLoader(agentJar);

            // Configure class definition
            final Class<?> classOfConfigure = agentLoader.loadClass("com.github.ompc.greys.core.Configure");

            // GaServer class definition
            final Class<?> classOfGaServer = agentLoader.loadClass("com.github.ompc.greys.core.server.GaServer");

            // Deserialize to an instance of the Configure class
            final Object objectOfConfigure = classOfConfigure.getMethod("toConfigure", String.class)
                    .invoke(null, agentArgs);

            // JavaPid
            final int javaPid = (Integer) classOfConfigure.getMethod("getJavaPid").invoke(objectOfConfigure);

            // Getting GaServer singletons
            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's bind method is to start the service. After startup, the activeSelector Daemon method starts a Daemon thread responsible for command processing, in which doRead is the implementation of the main logic, in which the executeCommand method of CommandHandler is entrusted to parse the input line and execute the command.

public class GaServer {
/**
     * Start Greys Server
     *
     * @param configure configuration information
     * @throws IOException Server Startup Failure
     */
    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);

            // Server mount port
            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 Iterator<SelectionKey> it = 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 {

            // If you read EOF, the Socket Channel is closed
            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 ('\n' == data) {
                            attachment.setLineDecodeState(READ_EOL);
                        }

                        // When an abort command (CTRL_D) is encountered, the session is marked as not writable and the background task is stopped.
                        else if (CTRL_D == data
                                || CTRL_X == data) {
                            session.unLock();
                            break;
                        }

                        // Ordinary byte s are kept in the cache
                        else {
                            if ('\r' != data) {
                                attachment.put(data);
                            }
                            break;
                        }

                    }

                    case READ_EOL: {
                        final String line = attachment.clearAndGetLine(session.getCharset());

                        executorService.execute(new Runnable() {
                            @Override
                            public void run() {

                                // Sessions can only respond to commands if they are not locked
                                if (session.tryLock()) {
                                    try {

                                        // Command Execution
                                        commandHandler.executeCommand(line, session);

                                        // After the command ends, you need to transmit EOT to tell client that the command transfer is finished, and you can display the prompt.
                                        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();

        }

        // Handle
        catch (IOException e) {
            logger.warn("read/write data failed, session[{}] will be close.", session.getSessionId(), e);
            closeSocketChannel(key, socketChannel);
            session.destroy();
        }
    }
}

Next, let's look at DefaultCommandHandler, the default implementation of CommandHandler interface. The main logic of its executeCommand method is implemented by excute. The most important part of class enhancement we care about is through 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);
    }
    
     /*
     * Execution of orders
     */
    private void execute(final Session session, final Command command) throws GaExecuteException, IOException {
            

            // Actions requiring class enhancement
            else if (action instanceof GetEnhancerAction) {

                affect = new EnhancerAffect();

                // Executing Command Actions - Getting Enhancers
                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()
                );
            }
}

}

Its enhancement is the implementation class of AdviceListener. AdviceListener is composed of before, after Returning and other methods around the method execution stage. It is time for ASM to come on stage and modify the class behavior.

/**

 * Before advice
 *
 * @param loader     classloader
 * @param className  Class name
 * @param methodName Method name
 * @param methodDesc Method Description
 * @param target     Target class instance
 *                   If the target is a static method,Then for null
 * @param args       parameter list
 * @throws Throwable Notification process error
 */
void before(
        ClassLoader loader, String className, String methodName, String methodDesc,
        Object target, Object[] args) throws Throwable;

/**
 * Notification of return
 *
 * @param loader       classloader
 * @param className    Class name
 * @param methodName   Method name
 * @param methodDesc   Method Description
 * @param target       Target class instance
 *                     If the target is a static method, then null
 * @param args         parameter list
 * @param returnObject Return results
 *                     null if no return value method (void)
 * @throws Throwable Notification process error
 */
void afterReturning(
        ClassLoader loader, String className, String methodName, String methodDesc,
        Object target, Object[] args,
        Object returnObject) throws Throwable;              
Looking at the enhancement method, we can see that the real enhancement class is 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<Class<?>, Matcher<AsmMethod>> enhanceMap = toEnhanceMap(pointCut);

    // Building Enhancers
    final Enhancer enhancer = new Enhancer(adviceId, isTracing, enhanceMap, affect);
    try {
        inst.addTransformer(enhancer, true);

        // Batch Enhancement
        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 enhancement
        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 implements the Java Instrumentation interface ClassFileTransformer to see its core method transform

@Override

public byte[] transform(
        final ClassLoader inClassLoader,
        final String className,
        final Class<?> classBeingRedefined,
        final ProtectionDomain protectionDomain,
        final byte[] classfileBuffer) throws IllegalClassFormatException {

    // Filter out classes that are not within the scope of enhanced collections
    if (!enhanceMap.containsKey(classBeingRedefined)) {
        return null;
    }

    final ClassReader cr;

    // First check if there is Class bytecode in the cache
    // Because in order to support multi-person collaboration, there is a situation of multi-person enhancement at the same time.
    final byte[] byteOfClassInCache = classBytesCache.get(classBeingRedefined);
    if (null != byteOfClassInCache) {
        cr = new ClassReader(byteOfClassInCache);
    }

    // If there is no hit cache, the enhancement begins with the original bytecode
    else {
        cr = new ClassReader(classfileBuffer);
    }

    // Get the asm method matching for this class
    final Matcher<AsmMethod> asmMethodMatcher = enhanceMap.get(classBeingRedefined);

    // Bytecode Enhancement
    final ClassWriter cw = new ClassWriter(cr, COMPUTE_FRAMES | COMPUTE_MAXS) {

        /*
         * Note that in order to automatically calculate the frame size, it is sometimes necessary to compute the common parent class of two classes.
         * By default, ClassWriter calculates these in the getCommonSuperClass method by using the reflection API when loading the two classes into the virtual machine.
         * However, if you are going to generate several classes that refer to each other, this will cause problems, because the classes referenced may not yet exist.
         * In this case, you can override the getCommonSuperClass method to solve this problem.
         *
         * By rewriting the getCommonSuperClass() method, the way to get the ClassLoader is corrected and changed to the way to specify the ClassLoader.
         * Avoid using Object.class.getClassLoader() as the original code
         */
        @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 {

        // Generating enhanced bytecode
        cr.accept(new AdviceWeaver(adviceId, isTracing, cr.getClassName(), asmMethodMatcher, affect, cw), EXPAND_FRAMES);
        final byte[] enhanceClassByteArray = cw.toByteArray();

        // Successfully generated, pushed into the cache
        classBytesCache.put(classBeingRedefined, enhanceClassByteArray);

        // dump the class
        dumpClassIfNecessary(className, enhanceClassByteArray, affect);

        // Success counting
        affect.cCnt(1);

        // Dispatch spies
        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;
}
Its main logic should be to send spies.

/*

 * Sending spies to infiltrate each other classLoader in
 */
private void spy(final ClassLoader targetClassLoader)
        throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {

    // If the other party is bootstrap, that's all.
    if (null == targetClassLoader) {
        return;
    }
    // Enhancer classes can only be loaded from greysClassLoader
    // So it's reliable to ask him for Class Loader.
    final ClassLoader greysClassLoader = Enhancer.class.getClassLoader();

    final String spyClassName = GaStringUtils.SPY_CLASSNAME;

    // Loading Spy from Greys Class Loader
    final Class<?> spyClassFromGreysClassLoader = loadSpyClassFromGreysClassLoader(greysClassLoader, spyClassName);
    if (null == spyClassFromGreysClassLoader) {
        return;
    }

    // Attempt to load or define ClassLoader from the target ClassLoader
    Class<?> spyClassFromTargetClassLoader = null;
    try {

        // Go to the target class loader and find out if there is a spy
        // If spies already exist
        spyClassFromTargetClassLoader = targetClassLoader.loadClass(spyClassName);
        logger.info("Spy already in targetClassLoader : " + targetClassLoader);

    }

    // Looks like spy doesn't exist.
    catch (ClassNotFoundException cnfe) {

        try {// Mixing spies into target class loading
            spyClassFromTargetClassLoader = defineClass(
                    targetClassLoader,
                    spyClassName,
                    toByteArray(Enhancer.class.getResourceAsStream("/" + spyClassName.replace('.', '/') + ".class"))
            );
        } catch (InvocationTargetException ite) {
            if (ite.getCause() instanceof java.lang.LinkageError) {
                // CloudEngine, because the loadClass is not available, will result in java.lang.LinkageError: loader (instance of com/alipay/cloudengine/extensions/equinox/KernelAceClassLoader): attempted duplicate class definition for name: "com/taobao/arthas/core/advisor/Spy"
                // Here try to ignore
                logger.debug("resolve #112 issues", ite);
            } else {
                throw ite;
            }
        }

    }
    // Wherever you fetch spyClass, you need to reinitialize it
    // Scenarios for compatibility with reloading
    // Of course, doing so will bring some performance overhead to the rendering process, but it will simplify the coding complexity.
    finally {

        if (null != spyClassFromTargetClassLoader) {
            // Initialization spy
            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)
            );
        }

    }

}
Next, look at Spy's implementation and find that it's nothing special. How to realize weaving? During this period, I have been fascinated for a long time.

package com.github.ompc.greys.agent;

import java.lang.reflect.Method;

/**

  • Spy < br />.
  • Hidden in various Class Loaders
  • Created by oldmanpushcart@gmail.com on 15/8/23.
    */

public class Spy {

// --hook references to various Advice s--
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;

/**
 * Agent Reset Method
 */
public static volatile Method AGENT_RESET_METHOD;

/*
 * Used for ordinary spy initialization
 */
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;
}

/*
 * Used to start thread initialization
 */
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;
}

}

Over time, I occasionally look at its method calls and discover the mystery that its real worth weaving logic was originally in the relevant methods of AdviceWeaver.

private static ClassLoader loadOrDefineClassLoader(String agentJar) throws Throwable {

    final ClassLoader classLoader;

    // If already started, return to the classloader that was started before
    if (null != greysClassLoader) {
        classLoader = greysClassLoader;
    }

    // Reload if not started
    else {
        classLoader = new AgentClassLoader(agentJar);

        // Get all kinds of Hook s
        final Class<?> adviceWeaverClass = classLoader.loadClass("com.github.ompc.greys.core.advisor.AdviceWeaver");

        // Initialize Global Spy
        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;
}
Put out a way to see that it is ultimately implemented by before hand of lister. MonitorCommand only implements an invokeCost.Begin. But without bytecode enhancement, how can it be implemented dynamically?

/**

 * Method Start<br/>
 * Used for knitting Notifier,External calls will not be made directly
 *
 * @param loader     classloader
 * @param adviceId   notice ID
 * @param className  Class name
 * @param methodName Method name
 * @param methodDesc Method Description
 * @param target     Return results
 *                   If there is no return value method(void),Then for null
 * @param args       parameter list
 */
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 {
        // Building Execution Frame Stack to Protect the Execution Site
        final GaStack<Object> frameStack = new ThreadUnsafeFixGaStack<Object>(FRAME_STACK_SIZE);
        frameStack.push(loader);
        frameStack.push(className);
        frameStack.push(methodName);
        frameStack.push(methodDesc);
        frameStack.push(target);
        frameStack.push(args);

        final AdviceListener listener = getListener(adviceId);
        frameStack.push(listener);

        // Get notifier and make pre-notification
        before(listener, loader, className, methodName, methodDesc, target, args);

        // Protect the current execution frame stack and press it into the thread frame stack
        threadFrameStackPush(frameStack);
    } finally {
        isSelfCallRef.set(false);
    }

}
In fact, all of these are pretexts. The real enhancement is achieved through visitMethod, which is implemented by AdviceAdapter. Its onMethodEnter method is a real before class enhancement (see ASM official document).
It not only uses bytecode enhancement, but also directly operates the stack. This part of the view is cloudy and foggy. You have good information to recommend me to study, I would appreciate it. If I implement it, I should like the implementation in the ASM example above, add bytecode and so on.
        @Override
        protected void onMethodEnter() {

            codeLockForTracing.lock(new CodeLock.Block() {
                @Override
                public void code() {

                    final StringBuilder append = new StringBuilder();
                    _debug(append, "debug:onMethodEnter()");

                    // Loading before method
                    loadAdviceMethod(KEY_GREYS_ADVICE_BEFORE_METHOD);
                    _debug(append, "loadAdviceMethod()");

                    // Push the first parameter of Method.invoke()
                    pushNull();

                    // Method parameters
                    loadArrayForBefore();
                    _debug(append, "loadArrayForBefore()");

                    // Call method
                    invokeVirtual(ASM_TYPE_METHOD, ASM_METHOD_METHOD_INVOKE);
                    pop();
                    _debug(append, "invokeVirtual()");

                }
            });

            mark(beginLabel);

        }
Recently, I have seen several open source frameworks or tools of Ali, hoping to have the opportunity to go to Ali Code and work with excellent people.


  [1]: https://www.ibm.com/developerworks/cn/java/j-lo-jse61/index.html
  [2]: http://download.forge.objectweb.org/asm/asm4-guide.pdf

Keywords: Java Session github socket

Added by jeffkee on Mon, 10 Jun 2019 21:57:28 +0300