>>分享Java编程技术,对《Java面向对象编程》等书籍提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 19039 个阅读者 刷新本主题
 * 贴子主题:  不修改源代码,动态注入Java代码的方法 回复文章 点赞(0)  收藏  
作者:flybird    发表时间:2020-12-12 11:05:11     消息  查看  搜索  好友  邮件  复制  引用

有时,我们需要在不修改源代码的前提下往一个第三方的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.

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


程序猿的技术大观园:www.javathinker.net
  Java面向对象编程-->泛型
  JavaWeb开发-->开发JavaMail Web应用
  JSP与Hibernate开发-->映射一对多关联关系
  Java网络编程-->用Axis发布Web服务
  精通Spring-->通过Axios访问服务器
  Vue3开发-->绑定CSS样式
  求素数
  Java集合框架学习---深入探究ArrayList源码
  Java Optional 解决空指针异常总结
  Eclipse的安装配置
  邀请您一起来祝福和祈祷,祈愿疫情早日消除,平安吉祥
  Java设计模式:解释器模式
  Java入门实用代码:集合转数组
  Java入门实用代码:修改链表LinkedList
  Java入门实用代码:100以内整数求和运算
  Java 入门实用代码:设置文件只读
  Java入门实用代码:打印平行四边形
  Java入门实用代码: 字符串格式化
  Java程序初始化顺序(一看就懂)
  Java中的main()方法详解
  Java中用动态代理实现标准的DataSource数据源连接池
  更多...
 IPIP: 已设置保密
树形列表:   
基諾彩的投注號碼總共有80個,每次都會開出20個號碼... kericnnoe 2023-03-07 00:27:59
1页 1条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。