UP

MBean

GoodChildMBean

package lsieun.management.bean;

public interface GoodChildMBean {
    void study(String className, String methodName, String methodDesc, String options);
}

GoodChild

package lsieun.management.bean;

import lsieun.asm.visitor.MethodInfo;
import lsieun.cst.Const;
import lsieun.instrument.InabilityTransformer;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.util.Formatter;
import java.util.HashSet;
import java.util.Set;

public class GoodChild implements GoodChildMBean {
    protected final Instrumentation instrumentation;

    public GoodChild(Instrumentation instrumentation) {
        this.instrumentation = instrumentation;
    }

    @Override
    public void study(String className, String methodName, String methodDesc, String option) {
        StringBuilder sb = new StringBuilder();
        Formatter fm = new Formatter(sb);
        fm.format("%s%n", Const.SEPARATOR);
        fm.format("GoodChild.study%n");
        fm.format("    class  : %s%n", className);
        fm.format("    method : %s:%s%n", methodName, methodDesc);
        fm.format("    option : %s%n", option);
        fm.format("    thread : %s@%s(%s)%n",
                Thread.currentThread().getName(),
                Thread.currentThread().getId(),
                Thread.currentThread().isDaemon()
        );
        fm.format("%s%n", Const.SEPARATOR);
        System.out.println(sb);
        
        Set<MethodInfo> flags = new HashSet<>();
        if (option != null) {
            String[] array = option.split(",");
            for (String element : array) {
                if ("".equals(element)) continue;
                MethodInfo methodInfo = Enum.valueOf(MethodInfo.class, element);
                flags.add(methodInfo);
            }
        }

        // 第一种方式,用 Class.forName()方法,速度较快
        try {
            Class<?> clazz = Class.forName(className);
            transform(clazz, methodName, methodDesc, flags);
            return;
        } catch (Exception ex) { /* Nope */ }

        // 第二种方式,用 Instrumentation.getAllLoadedClasses()方法,速度较慢
        Class<?>[] allLoadedClasses = instrumentation.getAllLoadedClasses();
        for (Class<?> clazz : allLoadedClasses) {
            if (clazz.getName().equals(className)) {
                transform(clazz, methodName, methodDesc, flags);
                return;
            }
        }
        throw new RuntimeException("Failed to locate class [" + className + "]");
    }

    /**
     * Registers a transformer and executes the transform
     *
     * @param clazz      The class to transform
     * @param methodName The method name to instrument
     * @param methodDesc The method signature to match
     */
    protected void transform(Class<?> clazz, String methodName, String methodDesc, Set<MethodInfo> flags) {
        ClassLoader classLoader = clazz.getClassLoader();
        ClassFileTransformer transformer = new InabilityTransformer(classLoader, clazz.getName(), methodName, methodDesc, flags);
        instrumentation.addTransformer(transformer, true);
        try {
            instrumentation.retransformClasses(clazz);
        } catch (Exception ex) {
            throw new RuntimeException("Failed to transform [" + clazz.getName() + "]", ex);
        } finally {
            instrumentation.removeTransformer(transformer);
        }
    }
}

Agent Jar

DynamicAgent

package lsieun.agent;

import lsieun.cst.Const;
import lsieun.management.bean.GoodChild;
import lsieun.utils.*;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;

public class DynamicAgent {
    public static void agentmain(String agentArgs, Instrumentation inst) throws Exception {
        // 第一步,打印信息:agentArgs, inst, classloader, thread
        PrintUtils.printAgentInfo(DynamicAgent.class, "Agent-Class", agentArgs, inst);

        // 第二步,创建 MBean
        System.out.println("Installing JMX Agent...");
        GoodChild child = new GoodChild(inst);
        ObjectName objectName = new ObjectName(Const.GOOD_CHILD_BEAN);

        // 第三步,注册 MBean
        MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
        beanServer.registerMBean(child, objectName);

        // 第四步,设置属性
        System.setProperty(Const.AGENT_MANAGEMENT_PROP, "true");
        System.out.println("JMX Agent Installed");
    }
}

JMX Client

AgentInstaller

package run.jmx;

import com.sun.tools.attach.VirtualMachine;
import lsieun.cst.Const;
import lsieun.utils.JarUtils;
import lsieun.utils.VMAttachUtils;

import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.util.Properties;

public class AgentInstaller {
    public static void main(String[] args) throws Exception {
        // 第一步,获取 PID
        String displayName = "sample.Program";
        String pid = VMAttachUtils.findPID(displayName);
        System.out.println("pid: " + pid);

        // 第二步,利用 Attach 机制,加载两个 Agent Jar
        VirtualMachine vm = VirtualMachine.attach(pid);
        Properties properties = vm.getSystemProperties();
        String value = properties.getProperty(Const.AGENT_MANAGEMENT_PROP);
        if (value == null) {
            // 加载第一个 Agent Jar
            String jarPath = JarUtils.getJarPath();
            vm.loadAgent(jarPath);
        }

        String connectorAddress = vm.getAgentProperties().getProperty(Const.LOCAL_CONNECTOR_ADDRESS_PROP, null);
        vm.getAgentProperties().list(System.out);
        if (connectorAddress == null) {
            // 加载第二个 Agent Jar
            String home = vm.getSystemProperties().getProperty("java.home");
            String managementAgentJarPath = JarUtils.getManagementAgentJarPath(home);
            vm.loadAgent(managementAgentJarPath);
            connectorAddress = vm.getAgentProperties().getProperty(Const.LOCAL_CONNECTOR_ADDRESS_PROP, null);
            vm.getAgentProperties().list(System.out);
        }
        System.out.println(connectorAddress);
        vm.detach();

        // 第三步,准备参数
        String beanName = Const.GOOD_CHILD_BEAN;
        String beanMethodName = "study";
        String[] beanMethodArgArray = new String[]{
//                "sample.HelloWorld", "add", "(II)I", "",
                "sample.HelloWorld", "add", "(II)I", "NAME_AND_DESC,PARAMETER_VALUES",
//                "sample.HelloWorld", "add", "(II)I", "NAME_AND_DESC,PARAMETER_VALUES,RETURN_VALUE",
        };

        // 第四步,借助 JMXConnector,调用 MBean 的方法
        ObjectName objectName = new ObjectName(beanName);
        JMXServiceURL serviceURL = new JMXServiceURL(connectorAddress);
        try (JMXConnector connector = JMXConnectorFactory.connect(serviceURL)) {
            MBeanServerConnection server = connector.getMBeanServerConnection();
            server.invoke(objectName, beanMethodName, beanMethodArgArray,
                    new String[]{
                            String.class.getName(),
                            String.class.getName(),
                            String.class.getName(),
                            String.class.getName(),
                    });
        }
    }
}

从下面的输出结果当中,我们可以看到 GoodChild.study() 方法运行在不同的线程(thread):

GoodChild.study
    class  : sample.HelloWorld
    method : add:(II)I
    option : NAME_AND_DESC,PARAMETER_VALUES
    thread : RMI TCP Connection(6)-192.168.200.1@20(true)
GoodChild.study
    class  : sample.HelloWorld
    method : sub:(II)I
    option : RETURN_VALUE
    thread : RMI TCP Connection(4)-192.168.200.1@18(true)
GoodChild.study
    class  : sample.HelloWorld
    method : sub:(II)I
    option : NAME_AND_DESC
    thread : RMI TCP Connection(3)-192.168.200.1@17(true)

JConsole

在下面的 jconsole 当中,study 方法的参数值:

  • p1: sample.HelloWorld
  • p2: add
  • p3: (II)I
  • p4: NAME_AND_DESC