简体   繁体   English

如何在运行时重新转换类

[英]how to retransform a class at runtime

I am tring modify class which already loaded in a jvm. 我正在修改已经加载到jvm中的类。 The solution which I found is: 我找到的解决方案是:

  • 1st Attach an agent to a jvm specified by pid. 1st将代理程序附加到pid指定的jvm。 (eg 8191)(Codes: AttachTest) (例如8191)(代码:AttachTest)
  • 2nd Find the class which you want modified from those that have already been loaded in the jvm(eg 8191). 2从已经加载到jvm中的类(例如8191)中查找要修改的类。
  • 3rd Add transformer using Instrument (Codes: AgentMain) 3使用仪器添加变压器(代码:AgentMain)
  • 4th Modify the class(eg Person) in transform method(Codes: DemoTransformer) 4在transform方法中修改类(例如Person)(代码:DemoTransformer)
  • 5th Retransform the class using retransformClasses 5th使用retransformClasses重新转换

It works fine from 1st step to 5th step, but there are problems at retransformClasses . 从第1步到第5步工作正常,但在retransformClasses存在问题。 It called transform again which contains codes to modify class. 它再次调用了transform ,它包含修改类的代码。 And It modify other classes which I never wanna modify. 它修改了我从不想修改的其他类。 I think the problem may occured during addTransformer or retransformClasses . 我认为在addTransformerretransformClasses期间可能会出现问题。 But I still confused. 但我还是很困惑。 Well, how to retransform a class? 那么,如何重新转换一个类? Any ideas? 有任何想法吗? thx 谢谢

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);
    }
}

//Agent //代理

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;
    }
}

OUTPUTS: THE ATTACH PROGRAM 产出: 附件计划

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

OUTPUTS: TARGET PROGRAM 产出: 目标计划

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

Short Answer 简答

  • Don't iterate through all the loaded classes from Instrumentation. 不要遍历Instrumentation中所有已加载的类。 Instead, just examine the class name passed in to the transformer and if it matches your target class, then transform it. 相反,只需检查传入变换器的类名称,如果它与目标类匹配,则转换它。 Otherwise, simply return the passed classfileBuffer unmodified. 否则,只需返回未修改的传递的classfileBuffer。
  • Make the set up calls outside the transformer, (ie in your case, do the following from your agent) so initialize your transformer with the class name you're looking to transform (this will be the inner format so instead of foo.bar.Snafu , you will be looking to match against foo/bar/Snafu . Then add the transformer, call retransform and then remove the transformer. 在变换器外部进行设置调用(例如,在您的情况下,从您的代理执行以下操作),然后使用您要转换的类名初始化变换器(这将是内部格式 ,而不是foo.bar。 Snafu ,你将寻找匹配foo / bar / Snafu 。然后添加变换器,调用转换然后删除变压器。
  • In order to call retransform, you will need the actual [pre-transform] class which you can find by calling Class.forName (in the agentmain), or if you absolutely have to, you could find it in Intrumentation.getAllLoadedClasses() as a last resort. 为了调用转换,您将需要通过调用Class.forName (在agentmain中)找到的实际[pre-transform]类,或者如果您必须,可以在Intrumentation.getAllLoadedClasses()中找到它万不得已。 If the target class has not been loaded, you will need the classloader to call Class.forName(name, boolean, classloader) in which case you can pass the URL to the target class class-path in the agent main string args. 如果尚未加载目标类,则需要类加载器来调用Class.forName(name,boolean,classloader),在这种情况下,您可以将URL传递给代理主字符串args中的目标类class-path。

Long Answer 答案很长

Here's a few recommendations: 以下是一些建议:

  1. Separate out the operation you're calling into 2 separate operations: 将您正在调用的操作分为两个单独的操作:
    1. Install the agent. 安装代理。 This only needs to be done once. 这只需要做一次。
    2. Transform the target class[es]. 转换目标类[es]。 You might want to do this n times. 您可能想要这样做n次。
  2. I would implement 1.2 by registering a simple JMX MBean when you install the agent. 我将通过在安装代理时注册一个简单的JMX MBean来实现1.2。 This MBean should provide an operation like public void transformClass(String className) . 此MBean应提供类似public void transformClass(String className) and should be initialized with a reference to the agent's acquired Instrumentation instance. 并且应该通过引用代理程序获取的Instrumentation实例进行初始化。 The MBean class, interface and any required 3rd party classes should be included in your agent's loaded.jar . MBean类,接口和任何所需的第三方类应包含在代理程序的loaded.jar中 It should also contain your ModifyMethodTest class (which I assume it already does). 它还应该包含您的ModifyMethodTest类(我假设它已经这样做了)。
  3. At the same time that you install your agent jar, also install the management agent from $JAVA_HOME/lib/management-agent.jar which will activate the management agent so you can invoke the transform operation in the MBean you're about to register. 在安装代理jar的同时,还要从$ JAVA_HOME / lib / management-agent.jar安装管理代理程序,它将激活管理代理程序,以便您可以在要注册的MBean中调用转换操作。
  4. Parameterize your DemoTransformer class to accept the internal form of the name of the class you want to transform. 参数化您的DemoTransformer类以接受要转换的类的名称的内部形式 (ie if your binary class name is foo.bar.Snafu , the internal form will be foo/bar/Snafu . As your DemoTransformer instance starts getting transform callbacks, ignore all class names that do not match the internal form class name you specified. (ie simply return the classfileBuffer unmodified) (即如果您的二进制类名称为foo.bar.Snafu ,则内部表单将为foo / bar / Snafu 。当您的DemoTransformer实例开始获取转换回调时,请忽略与您指定的内部表单类名称不匹配的所有类名称。 (即只是简单地返回未修改的classfileBuffer)
  5. Your tranformer MBean transformClass operation should then: 然后,您的转换器MBean transformClass操作应该:
    1. Convert the passed class name to internal form. 将传递的类名转换为内部表单。
    2. Create a new DemoTransformer, passing the internal form class name. 创建一个新的DemoTransformer,传递内部表单类名。
    3. Register the DemoTransformer instance using Instrumentation.addTransformer(theNewDemoTransformer, true) . 使用Instrumentation.addTransformer(theNewDemoTransformer, true)注册DemoTransformer实例。
    4. Call Instrumentation.retransformClasses(ClassForName(className)) (with the binary class name passed to the MBean operation). 调用Instrumentation.retransformClasses(ClassForName(className)) (将二进制类名传递给MBean操作)。 When this call returns, your class will be transformed. 当此调用返回时,您的类将被转换。
    5. Remove the transformer with Intrumentation.removeTransformer(theNewDemoTransformer) . 使用Intrumentation.removeTransformer(theNewDemoTransformer)删除变换器。

The following is an untested approximation of what I mean: 以下是我未经测试的近似值:

Transformer MBean 变形金刚MBean

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

Transformer Service 变压器服务

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

    /**
     * Creates a new TransformerService
     * @param instrumentation  The JVM's 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);
        }       
    }
}

The Class Transformer 类变压器

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;
    }

}

The Agent 中介

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

}

The Agent Installer 代理安装程序

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's 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's 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 ================= =================更新=================

Hey Nick; 嘿尼克; Yep, that's one of the limitations of the current (ie Java 5-8) class transformers. 是的,这是当前(即Java 5-8)类变压器的局限之一。 To quote from the Instrumentation javadoc : 引用Instrumentation javadoc

"The retransformation may change method bodies, the constant pool and attributes. The retransformation must not add, remove or rename fields or methods, change the signatures of methods, or change inheritance. These restrictions maybe be lifted in future versions. The class file bytes are not checked, verified and installed until after the transformations have been applied, if the resultant bytes are in error this method will throw an exception." “重新转换可能会改变方法体,常量池和属性。重新转换不得添加,删除或重命名字段或方法,更改方法的签名或更改继承。这些限制可能会在将来的版本中解除。类文件字节不会检查,验证和安装,直到应用了转换后,如果结果字节错误,则此方法将引发异常。“

As an aside, this same limitation is documented verbatim for redefining classes too. 另外,对于重新定义类也逐字记录了同样的限制。

As such, you have 2 options: 因此,您有两种选择:

  1. Don't add new methods. 不要添加新方法。 This is commonly very limiting and disqualifies the use of very common byte-code AOP patterns like method wrapping. 这通常是非常有限的,并且取消了使用非常常见的字节码AOP模式(如方法包装)的资格。 Depending on which byte-code manipulation library you're using, you may be able to inject all the functionality you want into the existing methods. 根据您正在使用的字节码操作库,您可以将所需的所有功能注入现有方法。 Some libraries make this easier than others. 有些图书馆比其他图书馆更容易。 Or, I should say, some libraries will make this easier than others. 或者,我应该说,有些库会比其他库更容易。

  2. Transform the class before it is class loaded. 在类加载之前转换类。 This uses the same general pattern of the code we already discussed, except you don't trigger the transform through calling retransformClasses. 这使用了与我们已经讨论过的代码相同的一般模式,除了您不通过调用retransformClasses触发转换。 Rather, you register the ClassFileTransformer to perform the transform before the class is loaded and your target class will be modified when it is first class loaded. 相反,您注册ClassFileTransformer以加载类之前执行转换并且在第一个类加载时将修改目标类。 In this case, you're pretty much free to modify the class any way you like, provided the end product can still be validated. 在这种情况下,您可以随意修改类,只要最终产品仍然可以验证。 Beating the application to the punch (ie getting your ClassFileTransformer registered before the application loads the class) will most likely require a command like javaagent , although if you have tight control of the lifecycle of your application, it is possible to do this in more traditional application layer code. 击败应用程序(即在应用程序加载类之前注册ClassFileTransformer)很可能需要像javaagent这样的命令,尽管如果你能够严格控制应用程序的生命周期,就可以在更传统的环境中执行此操作。应用层代码。 As I said, you just need to make sure you get the transformer registered before the target class is loaded. 正如我所说,你只需要确保在加载目标类之前注册变换器。

One other variation on #2 you may be able to use is to simulate a brand new class by using a new classloader. 您可以使用的#2的另一个变体是使用新的类加载器来模拟全新的类。 If you create a new isolated classloader which will not delegate to the existing [loaded] class, but does have access to the [unloaded] target class byte-code, you're essentially reproducing the requirements of #2 above since the JVM considers this to be a brand new class. 如果你创建一个新的独立类加载器,它不会委托给现有的[loaded]类,但是可以访问[unloaded]目标类字节代码,那么你基本上可以重现上面#2的要求,因为JVM认为这个成为一个全新的课程。

================ UPDATE ================ ================更新================

In your last comments, I feel like I've lost track a bit of where you are. 在你上次的评论中,我觉得我已经失去了一些你在哪里的轨道。 At any rate, Oracle JDK 1.6 most definitely supports retransform. 无论如何,Oracle JDK 1.6绝对支持转换。 I am not too familiar with ASM, but the last error you posted indicates that the ASM transformation somehow modified the class schema which is not allowed, so the retransform failed. 我对ASM不太熟悉,但是您发布的最后一个错误表明ASM转换以某种方式修改了不允许的类模式,因此重新转换失败。

I figured a working example would add more clarity. 我认为一个工作的例子会增加更多的清晰度。 The same classes as above (plus one test class called Person) are here . 与上面相同的类(加上一个名为Person的测试类)就在这里 There's a couple of modifications/additions: 有几个修改/补充:

  • The transform operation in the TransformerService now has 3 parameters: TransformerService中的转换操作现在有3个参数:
    1. The binary class name 二进制类名称
    2. The method name to instrument 仪器的方法名称
    3. A [regular] expression to match the method signature. 用于匹配方法签名的[常规]表达式。 (if null or empty, matches all signatures) (如果为null或为空,则匹配所有签名)
    4. The actual bytecode modification is done using Javassist in the ModifyMethodTest class. 实际的字节码修改是使用ModifyMethodTest类中的Javassist完成的。 All the instrumentation does is add a System.out.println that looks like this: -->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)] 所有的工具都是添加一个如下所示的System.out.println-->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
  • The AgentInstaller (which has the demo's Main) just self-installs the agent and the transform service. AgentInstaller (具有demo的Main)只是自行安装代理和转换服务。 (Easier for Dev/Demo purposes, but will still work with other JVMs) (更容易进行开发/演示,但仍可与其他JVM一起使用)
  • Once the agent is self-installed, the main thread creates a Person instance and just loops, calling the Person's two sayHello methods. 一旦代理自行安装,主线程就会创建一个Person实例并且只是循环,调用Person的两个sayHello方法。

Prior to transform, that output looks as follows. 在转换之前,该输出如下所示。

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]

Person has 2 sayHello methods, one takes an int , the other takes a String . Person有2个sayHello方法,一个接受一个int ,另一个接受一个String (The String one just prints the negative of the loop index). (字符串1只打印循环索引的负数)。

Once I start the AgentInstaller, the agent is installed and Person is being invoked in a loop, I connect to the JVM using JConsole: 一旦启动AgentInstaller,就会安装代理并在循环中调用Person,我使用JConsole连接到JVM:

查找AgentInstaller JVM

I navigate to the TransformerService MBean and invoke the transformClass operation. 我导航到TransformerService MBean并调用transformClass操作。 I supply the fully qualified class [binary] name, the method name to instrument, and a regex expression (I)V which matches only the sayHello method which takes an int as an argument. 我提供了完全限定的类[二进制]名称,仪器的方法名称和正则表达式(I)V它只匹配以int作为参数的sayHello方法。 (Or I could supply .* , or nothing to match all overloads). (或者我可以提供。* ,或者没有任何东西可以匹配所有重载)。 I execute the operation. 我执行操作。

调用操作

Now when I go back to the running JVM and examine the output: 现在,当我回到正在运行的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]

Done. 完成。 Method instrumented. 方法检测。

Keep in mind, the reason the retransform is allowed is because the Javassist bytecode modification made no changes other than to inject code into an existing method. 请记住,允许重新转换的原因是因为Javassist字节码修改除了将代码注入现有方法之外没有做任何更改。

Make sense ? 合理 ?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM