不修改源代碼,動態注入Java代碼的方法(轉)


轉自:https://blog.csdn.net/hiphoon_sun/article/details/38707927

有時,我們需要在不修改源代碼的前提下往一個第三方的JAVA程序里注入自己的代碼邏輯。一種情況是拿不到它的源代碼,另一種情況是即使有源代碼也不想修改,想讓注入的代碼與第三方程序代碼保持相對獨立。

 
有兩種方法可以讓我們達到這樣的目標。一種方法是使用JDK 1.5引入的Java Instrumentation API. Instrumentation允許一個獨立於應用程序的代理程序(Agent),用來監測和協助運行在 JVM 上的程序,甚至能夠替換和修改某些類的定義。另一種方法是編寫一個定制的Class Loader,在合適的點注入自己的代碼。
 
下面用一個簡單的例子來描述一下如何用這兩種方法分別來達到注入代碼的目的。
 
A.java:
public class A {
  public void run() {
    System.out.println("A is running.");
  }
}
 
App.java:
public class App {
  public static void main(String... args) {
    A a = new A();
    a.run();
  }
}
 
我們的目的是替換Class A中的run方法。首先創建A的一個子類B,覆蓋run方法:
B.java:
public class B extends A {
  public void run() {
    System.out.println("B is running.");
  }
}
 
基本思路是在JVM load App類的時候,把對A的引用修改為對B的引用。我們甚至不用修改App的byte code,只需將App.class中常量池(constant pool)中類A的名字的字符串改為類B的名字,效果就是將語句A a = new A()改為A a = new B()。為了修改類的class文件,我們用到了一個開源的JAVA字節碼操作和分析框架ASM ( http://asm.ow2.org/)。為了運行這個例子,下載 asm-4.0.jar到當前目錄。
 

Java Instrumentation

 
寫一個instrumentation Agent。
InjectCodeAgent.java
import java.lang.instrument.Instrumentation;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;

class InjectCodeClassWriter extends ClassWriter {
  private static final String oldClass = "A";
  private static final String newClass = "B";

  InjectCodeClassWriter(int flags) {
    super(flags);
  }

  @Override
  public int newUTF8(final String value) {
    // 將App.class中常量池(constant pool)中類A的名字的字符串改為類B的名字
    if (value.equals(oldClass)) {
      return super.newUTF8(newClass);
    }
    return super.newUTF8(value);
  }
}

class InjectCodeTransformer implements ClassFileTransformer {
  private static final String appClass = "App";

  public byte[] transform(ClassLoader loader, String className,
          Class classBeingRedefined, ProtectionDomain protectionDomain,
          byte[] classfileBuffer) throws IllegalClassFormatException {
    if (className.equals(appClass)) {
      ClassWriter classWriter=new InjectCodeClassWriter(0);
      ClassReader classReader=new ClassReader(classfileBuffer);
      classReader.accept(classWriter, 0);
      return classWriter.toByteArray();
    } else {
      return null;
    }
  }
}

public class InjectCodeAgent {
  public static void premain(String args, Instrumentation inst) {
    inst.addTransformer(new InjectCodeTransformer());
  }
}
創建一個JAR的MANIFEST文件:
MANIFEST.MF
Premain-Class: InjectCodeAgent
 
然后將B.class和InjectCodeAgent打包成JAR:
     jar -cfm InjectCode.jar MANIFEST.MF InjectCodeAgent.class B.class
運行:
     java -javaagent:InjectCode.jar App
輸出是: 
    B is running.
 

Class Loader

寫一個定制的Class Loader:
InjectCodeClassLoader.java
InjectCodeClassLoader.java:
import java.io.InputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.concurrent.ConcurrentHashMap;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;

public class InjectCodeClassLoader extends URLClassLoader {
  private static final String appClass = "App";
  private static final String oldClass = "A";
  private static final String newClass = "B";
  private final ConcurrentHashMap<String, Object> locksMap = new ConcurrentHashMap<String, Object>();

  public InjectCodeClassLoader(ClassLoader parent) {
    super(((URLClassLoader) parent).getURLs(), parent);
  }
   
  private static class InjectCodeClassWriter extends ClassWriter {
    InjectCodeClassWriter(int flags) {
      super(flags);
    }
  
    @Override
    public int newUTF8(final String value) {
      if (value.equals(oldClass)) {
        return super.newUTF8(newClass);
      }
      return super.newUTF8(value);
    }
  }

  private Class defineClassFromClassFile(String className, byte[] classFile)
    throws ClassFormatError {
    return defineClass(className, classFile, 0, classFile.length);
  }
  
  private Class<?> replaceClass(String name)
    throws ClassNotFoundException {

    InputStream is = getResourceAsStream(name.replace('.', '/') + ".class");
    if (is == null) {
      throw new ClassNotFoundException();
    }

    ClassWriter classWriter=new InjectCodeClassWriter(0);
    try {
      ClassReader classReader=new ClassReader(is);
      classReader.accept(classWriter, 0);
    } catch (IOException e) {
      throw new ClassNotFoundException();
    }
         
    Class c = defineClassFromClassFile(name, classWriter.toByteArray());
    return c;
  }

  private Object getLock (String name) {
    Object lock = new Object();
    Object oldLock = locksMap.putIfAbsent(name, lock);
    if (oldLock == null) {
        oldLock = lock;
    }
    return oldLock;
  }
    
  @Override
  protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException {
    Object lock = getLock(name);
    synchronized(lock) {
      Class c = findLoadedClass(name);
      try {
        if (c == null) {
          if (name.equals(appClass)) {
            // 將App.class中常量池(constant pool)中類A的名字的字符串改為類B的名字
            c = replaceClass(name);
          } else {
            c = findClass(name);
          }
        }

        if (resolve) {
          resolveClass(c);
        }
        return c;
      } catch (ClassNotFoundException e) {
      }
    }
    return super.loadClass(name, resolve);
  }
}
 
在啟動JAVA時指定system class loader為定制的class loader。
    java -Djava.system.class.loader=InjectCodeClassLoader App
輸出是: 
    B is running.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM