zoukankan      html  css  js  c++  java
  • 不修改源代码,动态注入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.
  • 相关阅读:
    2017/12/30Java基础学习——增强型for嵌套遍历在二维数组中的应用
    2017/12/30Java基础学习——复制数组のSystem.arraycopy()方法讲解
    2017/12/30Java基础学习——排序算法の选择法与冒泡法的比较
    2017/12/31Java基础学习——二维数组排序の数组工具类Arrays的方法综合运用
    2017/12/31Java基础学习——使用同一个值,填充整个数组のArrays.fill(a, number)方法
    2017/12/31Java基础学习——判断两个数组是否相同のArrays.equals(a, b)方法
    2017/12/31Java基础学习——查找数组元素位置のArrays.binarySearch()方法介绍
    HDU2030 汉字统计【输入输出流】
    HDU4509 湫湫系列故事——减肥记II【格式输入+存储设置+暴力+水题】
    HDU2567 寻梦【输入输出流】
  • 原文地址:https://www.cnblogs.com/weizhxa/p/9222778.html
Copyright © 2011-2022 走看看