Подтвердить что ты не робот

Как преобразовать класс во время выполнения

Я пытаюсь изменить класс, который уже загружен в jvm. Решение, которое я нашел, это:

  • 1. Прикрепите агента к jvm, указанному pid. (например, 8191) (коды: AttachTest).
  • 2nd Найдите класс, который вы хотите изменить, из тех, которые уже были загружены в jvm (например, 8191).
  • 3rd Добавить трансформатор с помощью инструмента (коды: AgentMain)
  • 4th Изменить класс (например, Person) в transform методе (коды: DemoTransformer)
  • 5th Retransform класс, используя retransformClasses

Он отлично работает с 1-го шага до 5-го шага, но есть проблемы при retransformClasses. Он снова вызвал transform, который содержит коды для изменения класса. И Он модифицирует другие классы, которые я никогда не хочу изменять. Я думаю, что проблема может возникнуть во время addTransformer или retransformClasses. Но я все еще смущен. Ну, как ретранслировать класс? Есть идеи? ТНХ

public class AttachTest {
    public static void main(String[] args) throws AttachNotSupportedException,
        IOException, AgentLoadException, AgentInitializationException { 
        String agentPath = "D:\\work\\workspace\\myjar\\loaded.jar";
        String vid = args[0]; 
        VirtualMachine vm = VirtualMachine.attach(vid);
        vm.loadAgent(agentPath);
    }
}

//Агент

public class AgentMain {
    public static void agentmain (String agentArgs, Instrumentation inst)
        throws ClassNotFoundException, UnmodifiableClassException,
        InterruptedException {
    Class<?> [] allLoadedClasses = inst.getAllLoadedClasses();
        String tmpString = null;
        for (int i = 0; i<allLoadedClasses.length; i++) {
        tmpString = allLoadedClasses[i].getName();


        if (0 != tmpString.length()) {
            if (-1 != tmpString.lastIndexOf(".")) {
                tmpString = tmpString.substring(tmpString.lastIndexOf(".")+1,tmpString.length());
            }
            if (tmpString.equals("Person")) {

                inst.addTransformer(new DemoTransformer(), true);
                inst.retransformClasses(allLoadedClasses[i]);

                }
            }
        }
    }
}

|

public class DemoTransformer implements ClassFileTransformer {

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

    ModifyMethodTest tm = new ModifyMethodTest(classfileBuffer);

    byte[] byteArray = null;
    try {
        byteArray = tm.modiySleepMethod();

    } catch (Exception e) {

        e.printStackTrace();
    }


    return byteArray;
    }
}

ВЫХОДЫ: ПРОГРАММА ATTACH

javax.management.RuntimeMBeanException: java.lang.RuntimeException: Failed to transform [Person]
    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.rethrow(DefaultMBeanServerInterceptor.java:856)
    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.rethrowMaybeMBeanException(DefaultMBeanServerInterceptor.java:869)
    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:838)
    at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:761)
    at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1427)
    at javax.management.remote.rmi.RMIConnectionImpl.access$200(RMIConnectionImpl.java:72)
    at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1265)
    at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1360)
    at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:788)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:305)
    at sun.rmi.transport.Transport$1.run(Transport.java:159)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.rmi.transport.Transport.serviceCall(Transport.java:155)
    at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:535)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:790)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:649)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:619)
    at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:255)
    at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:233)
    at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:142)
    at com.sun.jmx.remote.internal.PRef.invoke(Unknown Source)
    at javax.management.remote.rmi.RMIConnectionImpl_Stub.invoke(Unknown Source)
    at javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection.invoke(RMIConnector.java:993)
    at AttachStackOverflow.main(AttachStackOverflow.java:57)
Caused by: java.lang.RuntimeException: Failed to transform [Person]
    at loaded3.TransformerService.transform(TransformerService.java:75)
    at loaded3.TransformerService.transformClass(TransformerService.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.sun.jmx.mbeanserver.StandardMBeanIntrospector.invokeM2(StandardMBeanIntrospector.java:93)
    at com.sun.jmx.mbeanserver.StandardMBeanIntrospector.invokeM2(StandardMBeanIntrospector.java:27)
    at com.sun.jmx.mbeanserver.MBeanIntrospector.invokeM(MBeanIntrospector.java:208)
    at com.sun.jmx.mbeanserver.PerInterface.invoke(PerInterface.java:120)
    at com.sun.jmx.mbeanserver.MBeanSupport.invoke(MBeanSupport.java:262)
    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:836)
    at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:761)
    at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1427)
    at javax.management.remote.rmi.RMIConnectionImpl.access$200(RMIConnectionImpl.java:72)
    at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1265)
    at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1360)
    at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:788)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:305)
    at sun.rmi.transport.Transport$1.run(Transport.java:159)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.rmi.transport.Transport.serviceCall(Transport.java:155)
    at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:535)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:790)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:649)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
    at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
    at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:124)
    at loaded3.TransformerService.transform(TransformerService.java:72)
    ... 31 more

ВЫХОДЫ: ЦЕЛЕВАЯ ПРОГРАММА

print Call sayHello()
print Hello World!
Supported Redefine
Supported Retransform
Call transform() in TransformerService
Add transformer
support redefine. return TRUE
support retransforme. return TRUE
IsModifiable class "class Person". return TRUE
Retransform classes
Number of times to Call transform() in DemoTransformer:1
####ASM CODE####
consturct ModifyMethodTest
Call modifySleepMethod
new classreader
new classwriter
construct ModifyClassAdapter
sayHello
consturct Modifymethod
[arg1] = java/io/PrintStream  [arg2] = println  #5
[arg1] = java/io/PrintStream  [arg2] = println  #13
[arg1] = java/util/concurrent/TimeUnit  [arg2] = sleep  #22
[arg1] = java/io/PrintStream  [arg2] = println  #30
sayHello2
consturct Modifymethod
[arg1] = java/io/PrintStream  [arg2] = println  #5
[arg1] = java/io/PrintStream  [arg2] = println  #13
<init>
consturct Modifymethod
[arg1] = java/lang/Object  [arg2] = <init>  #1
main
consturct Modifymethod
[arg1] = Person  [arg2] = <init>  #4
[arg1] = Person  [arg2] = sayHello  #9
[arg1] = Person  [arg2] = sayHello2  #13
[arg1] = java/lang/InterruptedException  [arg2] = printStackTrace  #21
getName
consturct Modifymethod
setName
consturct Modifymethod
Call visitend
Finished to call modifymethodtest
####End of ASM CODE
Remove transformer
Call transform() in TransformerService
Add transformer
support redefine. return TRUE
support retransforme. return TRUE
IsModifiable class "class Person". return TRUE
Retransform classes
Number of times to Call transform() in DemoTransformer:2
####ASM CODE####
consturct ModifyMethodTest
Call modifySleepMethod
new classreader
new classwriter
construct ModifyClassAdapter
sayHello
consturct Modifymethod
[arg1] = java/io/PrintStream  [arg2] = println  #5
[arg1] = java/io/PrintStream  [arg2] = println  #13
[arg1] = java/util/concurrent/TimeUnit  [arg2] = sleep  #22
[arg1] = java/io/PrintStream  [arg2] = println  #30
sayHello2
consturct Modifymethod
[arg1] = java/io/PrintStream  [arg2] = println  #5
[arg1] = java/io/PrintStream  [arg2] = println  #13
<init>
consturct Modifymethod
[arg1] = java/lang/Object  [arg2] = <init>  #1
main
consturct Modifymethod
[arg1] = Person  [arg2] = <init>  #4
[arg1] = Person  [arg2] = sayHello  #9
[arg1] = Person  [arg2] = sayHello2  #13
[arg1] = java/lang/InterruptedException  [arg2] = printStackTrace  #21
getName
consturct Modifymethod
setName
consturct Modifymethod
Call visitend
Finished to call modifymethodtest
####End of ASM CODE
Remove transformer
print in sayHello()
print Call sayHello2()
print Hello World!2
4b9b3361

Ответ 1

Короткий ответ

  • Не перебирайте все загруженные классы из Instrumentation. Вместо этого просто просмотрите имя класса, переданное в трансформатор, и если оно соответствует вашему целевому классу, а затем преобразуйте его. В противном случае просто верните переданный classfileBuffer немодифицированный.
  • Выполните настройки вызовов вне трансформатора (т.е. в вашем случае выполните следующие действия со своим агентом), чтобы инициализировать ваш трансформатор с именем класса, которое вы хотите преобразовать (это будет внутренний формат, поэтому вместо foo.bar.Snafu, вы увидите совпадение с foo/bar/Snafu. Затем добавьте трансформатор, переадресацию вызова и затем удалите трансформатор.
  • Для вызова ретрансляции вам понадобится фактический класс [pre-transform], который вы можете найти, вызвав Class.forName(в agentmain), или если вам это абсолютно необходимо, вы можете найти его в Intrumentation.getAllLoadedClasses() в крайнем случае. Если целевой класс не был загружен, вам понадобится загрузчик классов для вызова Class.forName(name, boolean, classloader), и в этом случае вы можете передать URL-адрес целевому классу класса в основной строке arg агента.

Длинный ответ

Вот несколько рекомендаций:

  • Отделите операцию, которую вы вызываете, на две отдельные операции:
    • Установите агент. Это нужно сделать только один раз.
    • Преобразование целевого класса [es]. Вы можете сделать это n раз.
  • Я бы выполнил 1.2, зарегистрировав простой JMX MBean при установке агента. Этот MBean должен обеспечить такую ​​операцию, как public void transformClass(String className). и должен быть инициализирован ссылкой на экземпляр Instrumentation агента. Класс MBean, интерфейс и любые необходимые сторонние классы должны быть включены в ваш агент load.jar. Он также должен содержать ваш класс ModifyMethodTest (который, как я предполагаю, он уже делает).
  • В то же время, когда вы устанавливаете баннер агента, установите агента управления из $JAVA_HOME/lib/management-agent.jar, который активирует агент управления, чтобы вы могли вызвать операцию преобразования в MBean, который вы собираетесь зарегистрировать.
  • Параметрируйте класс DemoTransformer, чтобы принять внутреннюю форму имени класса, который вы хотите преобразовать. (т.е. если ваше двоичное имя класса является foo.bar.Snafu, внутренняя форма будет foo/bar/Snafu. Поскольку ваш экземпляр DemoTransformer начинает получать обратные вызовы преобразования, игнорируйте все имена классов, которые не соответствуют указанному внутреннему имени класса формы. (т.е. просто вернуть classfileBuffer немодифицированный)
  • Транспондер MBean transformClass должен затем:
    • Преобразовать переданное имя класса во внутреннюю форму.
    • Создайте новый DemoTransformer, передав имя внутреннего класса формы.
    • Зарегистрируйте экземпляр DemoTransformer с помощью Instrumentation.addTransformer(theNewDemoTransformer, true).
    • Вызов Instrumentation.retransformClasses(ClassForName(className)) (с именем двоичного класса, переданным в операцию MBean). Когда этот вызов будет возвращен, ваш класс будет преобразован.
    • Извлеките трансформатор с помощью Intrumentation.removeTransformer(theNewDemoTransformer).

Ниже приведена непроверенная аппроксимация того, что я имею в виду:

Трансформатор MBean

public interface TransformerServiceMBean {
    /**
     * Transforms the target class name
     * @param className The binary name of the target class
     */
    public void transformClass(String className);
}

Трансформаторная служба

public class TransformerService implements TransformerServiceMBean {
    /** The JVM instrumentation instance */
    protected final Instrumentation instrumentation;

    /**
     * Creates a new TransformerService
     * @param instrumentation  The JVM instrumentation instance 
     */
    public TransformerService(Instrumentation instrumentation) {
        this.instrumentation = instrumentation;
    }

    /**
     * {@inheritDoc}
     * @see com.heliosapm.shorthandexamples.TransformerServiceMBean#transformClass(java.lang.String)
     */
    @Override
    public void transformClass(String className) {
        Class<?> targetClazz = null;
        ClassLoader targetClassLoader = null;
        // first see if we can locate the class through normal means
        try {
            targetClazz = Class.forName(className);
            targetClassLoader = targetClazz.getClassLoader();
            transform(targetClazz, targetClassLoader);
            return;
        } catch (Exception ex) { /* Nope */ }
        // now try the hard/slow way
        for(Class<?> clazz: instrumentation.getAllLoadedClasses()) {
            if(clazz.getName().equals(className)) {
                targetClazz = clazz;
                targetClassLoader = targetClazz.getClassLoader();
                transform(targetClazz, targetClassLoader);
                return;             
            }
        }
        throw new RuntimeException("Failed to locate class [" + className + "]");
    }

    /**
     * Registers a transformer and executes the transform
     * @param clazz The class to transform
     * @param classLoader The classloader the class was loaded from
     */
    protected void transform(Class<?> clazz, ClassLoader classLoader) {
        DemoTransformer dt = new DemoTransformer(clazz.getName(), classLoader);
        instrumentation.addTransformer(dt, true);
        try {
            instrumentation.retransformClasses(clazz);
        } catch (Exception ex) {
            throw new RuntimeException("Failed to transform [" + clazz.getName() + "]", ex);
        } finally {
            instrumentation.removeTransformer(dt);
        }       
    }
}

Трансформатор класса

public class DemoTransformer implements ClassFileTransformer {
    /** The internal form class name of the class to transform */
    protected String className;
    /** The class loader of the class */
    protected ClassLoader classLoader;
    /**
     * Creates a new DemoTransformer
     * @param className The binary class name of the class to transform
     * @param classLoader The class loader of the class
     */
    public DemoTransformer(String className, ClassLoader classLoader) {
        this.className = className.replace('.', '/');
        this.classLoader = classLoader;
    }

    /**
     * {@inheritDoc}
     * @see java.lang.instrument.ClassFileTransformer#transform(java.lang.ClassLoader, java.lang.String, java.lang.Class, java.security.ProtectionDomain, byte[])
     */
    @Override
    public byte[] transform(ClassLoader loader, String className,
            Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
            byte[] classfileBuffer) throws IllegalClassFormatException {
        if(className.equals(this.className) && loader.equals(classLoader)) {
            return new ModifyMethodTest(classfileBuffer).modiySleepMethod();
        }
        return classfileBuffer;
    }

}

Агент

public class AgentMain {

    public static void agentmain (String agentArgs, Instrumentation inst) throws Exception {
        TransformerService ts = new TransformerService(inst);
        ObjectName on = new ObjectName("transformer:service=DemoTransformer");
        // Could be a different MBeanServer. If so, pass a JMX Default Domain Name in agentArgs
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        server.registerMBean(ts, on);
        // Set this property so the installer knows we're already here
        System.setProperty("demo.agent.installed", "true");     
    }

}

Установщик агентов

public class AgentInstaller {
    /**
     * Installs the loader agent on the target JVM identified in <code>args[0]</code>
     * and then transforms all the classes identified in <code>args[1..n]</code>.
     * @param args The target JVM pid in [0] followed by the classnames to transform
     */
    public static void main(String[] args)  {
        String agentPath = "D:\\work\\workspace\\myjar\\loaded.jar";
        String vid = args[0]; 
        VirtualMachine vm = VirtualMachine.attach(vid);
        // Check to see if transformer agent is installed
        if(!vm.getSystemProperties().contains("demo.agent.installed")) {
            vm.loadAgent(agentPath);  
            // that property will be set now, 
            // and the transformer MBean will be installed
        }
        // Check to see if connector is installed
        String connectorAddress = vm.getAgentProperties().getProperty("com.sun.management.jmxremote.localConnectorAddress", null);
        if(connectorAddress==null) {
            // It not, so install the management agent
            String javaHome = vm.getSystemProperties().getProperty("java.home");
            File managementAgentJarFile = new File(javaHome + File.separator + "lib" + File.separator + "management-agent.jar");
            vm.loadAgent(managementAgentJarFile.getAbsolutePath());
            connectorAddress = vm.getAgentProperties().getProperty("com.sun.management.jmxremote.localConnectorAddress", null);
            // Now it installed
        }
        // Now connect and transform the classnames provided in the remaining args.
        JMXConnector connector = null;
        try {
            // This is the ObjectName of the MBean registered when loaded.jar was installed.
            ObjectName on = new ObjectName("transformer:service=DemoTransformer");
            // Here we're connecting to the target JVM through the management agent
            connector = JMXConnectorFactory.connect(new JMXServiceURL(connectorAddress));
            MBeanServerConnection server = connector.getMBeanServerConnection();
            for(int i = 1; i < args.length; i++) {
                String className = args[i];
                // Call transformClass on the transformer MBean
                server.invoke(on, "transformClass", new Object[]{className}, new String[]{String.class.getName()});
            }
        } catch (Exception ex) {
            ex.printStackTrace(System.err);
        } finally {
            if(connector!=null) try { connector.close(); } catch (Exception e) {}
        }
        // Done. (Hopefully)
    }
}

================= UPDATE =========================

Эй, Ник; Да, это одно из ограничений существующих (т.е. Трансформаторов класса Java 5-8). Процитировать Инструментарий javadoc:

"Ретрансформация может изменять тела методов, постоянный пул и атрибутов. Ретрансформация не должна добавлять, удалять или переименовывать поля или методы, изменить подписи методов или изменить наследование. Эти ограничения могут быть отменены в будущих версиях. Файл класса байты не проверяются, проверяются и не устанавливаются до тех пор, пока были применены преобразования, если результирующие байты ошибочны этот метод генерирует исключение".

Как и в стороне, это же ограничение документируется дословно для переопределения классов.

Таким образом, у вас есть 2 варианта:

  • Не добавляйте новые методы. Это обычно очень ограничивает и дисквалифицирует использование очень распространенных шаблонов AOP байтового кода, таких как метод оберточная бумага. В зависимости от используемой вами библиотеки манипулирования байтов вы можете вставлять все необходимые функции в существующие методы. Некоторые библиотеки делают это проще других. Или, я бы сказал, некоторые библиотеки сделают это легче, чем другие.

  • Преобразуйте класс перед загрузкой класса. Это использует тот же общий шаблон кода, который мы уже обсуждали, за исключением того, что вы не запускаете преобразование через вызовы reansformClasses. Скорее, вы регистрируете ClassFileTransformer для выполнения преобразования до загрузки класса и ваш целевой класс будет изменен при загрузке первого класса. В этом случае вы в значительной степени можете модифицировать класс любым способом, например, при условии, что конечный продукт все еще может быть проверен. Избиение приложения на удар (т.е. Получение вашего ClassFileTransformer зарегистрированный до загрузки приложения классом), скорее всего, потребуется команда типа javaagent, хотя, если у вас жесткий контроль жизненного цикла вашего приложения, это можно сделать в более традиционном коде уровня приложения. Как я уже сказал, вам просто нужно сделать убедитесь, что вы получили трансформатор, зарегистрированный до загрузки целевого класса.

Еще одна вариация №2, которую вы можете использовать, - это имитировать новый класс с помощью нового загрузчика классов. Если вы создаете новый изолированный classloader, который не будет делегировать существующий [загруженный] класс, но имеет доступ к [unloaded] байт-кода целевого класса, вы по существу воспроизводите требования № 2 выше, поскольку JVM считает это совершенно новым классом.

================ ОБНОВЛЕНИЕ =================

В ваших последних комментариях, я чувствую, что немного потерял информацию о вашем местонахождении. Во всяком случае, Oracle JDK 1.6 наиболее определенно поддерживает ретрансляцию. Я не очень хорошо знаком с ASM, но последняя ошибка, которую вы указали, указывает на то, что преобразование ASM каким-то образом изменило схему класса, которая не разрешена, поэтому сбой повторной передачи.

Я понял, что рабочий пример добавит большей ясности. Те же классы, что и выше (плюс один тестовый класс под названием Person), здесь. Там есть несколько модификаций/дополнений:

  • Операция преобразования в TransformerService теперь имеет 3 параметра:
    • Имя двоичного класса
    • Имя метода для инструмента
    • [регулярное] выражение для соответствия сигнатуре метода. (если нулевой или пустой, соответствует всем подписям)
    • Фактическая модификация байт-кода выполняется с помощью Javassist в классе ModifyMethodTest. Все инструменты - это добавить System.out.println, который выглядит так: -->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
  • AgentInstaller (у которого есть демонстрационный файл Main), просто самостоятельно устанавливает агент и службу преобразования. (Проще для Dev/Demo, но все равно будет работать с другими JVM).
  • После того, как агент будет установлен самостоятельно, основной поток создает экземпляр Person и просто петли, вызывая методы Person Two sayHello.

До преобразования этот вывод выглядит следующим образом.

Temp File:c:\temp\com.heliosapm.shorthandexamples.AgentMain8724970986698386534.jar
Installing AgentMain...
AgentMain Installed
Agent Loaded
Instrumentation Deployed:true
Hello [0]
Hello [0]
Hello [1]
Hello [-1]
Hello [2]
Hello [-2]

У человека есть 2 метода sayHello, один принимает int, другой принимает String. (Строка только печатает отрицательный индекс цикла).

Как только я запустил AgentInstaller, агент установлен, и Person вызывается в цикле, я подключаюсь к JVM с помощью JConsole:

Finding the AgentInstaller JVM

Я перехожу к MBean TransformerService и вызываю операцию transformClass. Я предоставляю полное имя класса [binary], имя метода для инструмента и выражение регулярного выражения (I) V, которое соответствует только методу sayHello, который принимает int как аргумент. (Или я мог бы поставить . *, или ничего, чтобы соответствовать всем перегрузкам). Я выполняю операцию.

Invoking the operation

Теперь, когда я возвращаюсь к запущенной JVM и просматриваю вывод:

Examining class [com/heliosapm/shorthandexamples/Person]
Instrumenting class [com/heliosapm/shorthandexamples/Person]
[ModifyMethodTest] Adding [System.out.println("\n\t-->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]");]
[ModifyMethodTest] Intrumented [1] methods

    -->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
Hello [108]
Hello [-108]

    -->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
Hello [109]
Hello [-109]

Готово. Метод с инструментами.

Имейте в виду, причина, по которой разрешена ретрансляция, заключается в том, что модификация байт-кода Javassist не вносила никаких изменений, кроме как вводить код в существующий метод.

Имеют смысл?