转自: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.

不修改源代码,动态注入Java代码的方法(转)的更多相关文章

  1. 【我的Android进阶之旅】Android 源代码中的Java代码中//$NON-NLS-1$ 注释是什么意思?

    1.背景 最近在负责公司基础业务和移动基础设施的开发工作,正在负责Lint代码静态检查工作.因此编写了自定义的Lint规则,在编写自定义的Lint规则前,当然是需要去把Google的关于Lint检测的 ...

  2. [改善Java代码]asList方法产生的List对象不可更改

    上一个建议之处了asList方法在转换基本类型数组时候存在的问题,在看下asList方法返回的列表有何特殊的地方.看代码: import java.util.Arrays; import java.u ...

  3. [改善Java代码]注意方法中传递的参数要求(replaceAll和replace的区别)

    有这样一个简单的需求:写一个方法,实现从原始字符串中删除与之匹配的所有子字符串,比如"蓝蓝的天,白云飘"中,删除"白云飘",输出"蓝蓝的天," ...

  4. java代码---indexOf()方法

    总结:indexOf(String str,int index)方法.从参数指定位置开始,如果index值超过了字符串长度,则返回-1 package com.a.b; import java.io. ...

  5. java代码equals方法

    package com.bc; public class Test_6 { // 我们知道java中的每个类都继承自Object类,equals是Object方法之一 String name; int ...

  6. java代码-----indexOf()方法--从字符串的某个字符的第一次出现的位子开始

    总结:方法是indedOf()方法.this  is my sister   //indexOf()方法是indexOf('m')==7 .那么就是字符m第一次出现的位置是顺数第7个,就会正常显示‘t ...

  7. java代码----substring()方法是按索引截取字符串。。。下标0开始

    总结:按照索引substring(2,5);意思是从字符串的索引为2开始(包括)到第6个字符(不包括)的位置的截取部分 package com.s.x; //substring public clas ...

  8. (转载)JAVA动态编译--字节代码的操纵

    在一般的Java应用开发过程中,开发人员使用Java的方式比较简单.打开惯用的IDE,编写Java源代码,再利用IDE提供的功能直接运行Java 程序就可以了.这种开发模式背后的过程是:开发人员编写的 ...

  9. 如何向AcmeAir注入问题代码

    为什么要注入问题代码? AcmeAir的常规代码是为了压测测试准备的,所以绝大部分的操作都是可以在几十毫秒中就可以正常返回的.为了向用户展示我们APM工具可以在源代码级别发现系统潜在问题,我们需要在A ...

随机推荐

  1. vue2.0 使用 vue-aplayer

    1.安装 npm i vue-aplayer 2.引入 import VueAplayer from 'vue-aplayer' name: "Aplayer", props: [ ...

  2. PhpStorm中如何使用database工具

    环境:ubuntu18.4 mysql5.7 一. 打开database 1.选择View—>Tool Windows—>Database单击打开. 2.新增数据连接 选择 “+”—> ...

  3. 10.VScode Debug——2019年12月12日

    title: vscode debug date: "2019-09-17 16:17:16" tags: 技巧 categories: 技术驿站 1.为什么需要调试 写了很多行代 ...

  4. C# 创建日志

    public void Log(string message) { try { string logFileName = "c:\\log\\LogName" + DateTime ...

  5. 远程桌面连接,运维工程师-必备软件【MultiDesk】

    实习时,在本地一家大型女装公司做桌面运维,服务器碰得到少,大部分时间都是在维护同事的电脑桌面,什么360全家桶了,毒霸了,都是通过远程工具 teamviewer 去搞定的. 后来做了前端开发,免不了自 ...

  6. UML-六大关系

    六大关系 依赖关系:(只要是在类中使用到了对方就存在依赖关系) 泛化关系:就是继承关系,即依赖关系的特例 实现关系:依赖关系的特例 关联关系:类于类之间的关系,即依赖关系的特例(具有导航性:双向关系或 ...

  7. BZOJ 2761: [JLOI2011]不重复数字 set

    Description 给出N个数,要求把其中重复的去掉,只保留第一次出现的数. 例如,给出的数为1 2 18 3 3 19 2 3 6 5 4,其中2和3有重复,去除后的结果为1 2 18 3 19 ...

  8. webpack对脚本和样式的处理

    一.对js处理 webpack本身支持js加载也可以用插件. 1.加载要全局使用的插件比如jquery 在页面用cdn方式引用,然后再webpack.config.js里配置.会让jquery成为全局 ...

  9. LVS负载均衡中arp_ignore和arp_annonuce参数配置

    先简单的介绍下关于LVS负载均衡 LVS(Linux  Virtual Server)Linux服务器集群系统 针对高可伸缩,高可用服务的需求,给予IP层和内容请求分发的负载均衡调度解决方法,并在Li ...

  10. 10.27 sort

    排序命令sort选项与参数:-f :忽略大小写的差异,例如 A 与 a 视为编码相同:-b :忽略最前面的空白字符部分:-M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法:-n :使用 ...