JVM 类加载及执行子系统的案例与实战

本章内容稍作改变,首先介绍一下代理模式和动态代理模式,然后来实现本章提到的实战内容。

1. 代理

所谓代理及让别人来帮你做事,就像生产厂商在各地有代理商来帮他们卖产品(此处只关心买产品的功能)。

1.1 类结构图

JVM 类加载及执行子系统的案例与实战

1.2 代码实现

public class ProxyTest {

    interface ISales {
        void sell();
    }

    static class Manufacturer implements ISales {
    
        @Override
        public void sell() {
            System.out.println("卖产品");
        }
    }

    static class Agent implements ISales {
        private Manufacturer manufacturer;
        Agent() {
            manufacturer = new Manufacturer();
        }

        @Override
        public void sell() {
            manufacturer.sell();
        }
    }

    public static void main(String[] args) {
        Agent agent = new Agent();
        agent.sell();
    }
}

在使用第三方库的时候,可以采用代理模式对其进行封装。就图片加载库而言,如果不对其封装,那么这个图片加载库的调用将在项目中到处存在,如果某天需要更快图片加载库,那么需要改动的代码会很多,很容易出错。而采用代理模式对其封装后,就只需要修改代理类即可。

2. 动态代理模式

在上述代理模式中,代理者需要知道被代理对象。而动态代理是代理者不需要知道被代理对象。

在Java中,动态代理需要实现InvocationHandler接口,并实现invoke(Object proxy, Method method, Object[] args)方法。

对上面类进行改造,采用动态代理的方式实现:

2.1 类结构图

JVM 类加载及执行子系统的案例与实战

2.2 代码实现

package com.teemo.jvm.chapter9;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxyTest1 {

    interface  ISales {
        void sell();

        void buy();
    }

    /**
     * 需要被代理的类
     */
    static class Manufacturer implements ISales {

        @Override
        public void sell() {
            System.out.println("卖产品");
        }

        @Override
        public void buy() {
            System.out.println("买产品");
        }
    }

    /**
     * 代理类
     */
    static class DynamicProxy implements InvocationHandler {
        private Object originObj;
        Object bind(Object obj) {
            this.originObj = obj;
            // 生成代理类
            return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println(proxy.getClass().getName());
            System.out.println("代理商");
            // 调用需要被代理类的方法
            return method.invoke(originObj, args);
        }
    }

    public static void main(String[] args) {
        // 调用代理类的功能
        ISales iSales = (ISales) new DynamicProxy().bind(new Manufacturer());
        iSales.buy();
        iSales.sell();
    }
}

在上述代码中需要注意的地方有两个

  1. Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);

这代码的目的是通过Proxy.newProxyInstance()创建一个代理类。

  1. ISales iSales = (ISales) new DynamicProxy().bind(new Manufacturer());

这代码的目的是将被代理类与代理类进行绑定,并返回被代理对象,其中 new Manufacturer()指的是被代理对象。置于这里返回的对象类型是ISales的原因是, 1 中返回的类为 $Proxy0, 它实现了ISales接口。

3. 远程执行代码的实现

执行入口

@RequestMapping(name = "/execute")
    public String execute() {
        try {
            File file = new File("/Users/SinPingWu/TestClass.class");
            FileInputStream fileInputStream = new FileInputStream(file);
            BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
            int byteLength = bufferedInputStream.available();
            byte[] clazzBytes = new byte[byteLength];
            bufferedInputStream.read(clazzBytes);

            String result = JavaClassExecuter.execute(clazzBytes);
            System.out.println(result);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

执行方法

public class JavaClassExecuter {

    /**
     * 执行外部传过来的代表一个Java类的Byte数组
     *
     * 将输入类的byte数组中代表java.lang.System 的 CONSTANT_Utf8_info 常量修改为劫持后的 HackSystem类
     * 执行方法为该类的static main(String[] args)方法, 输出结果为该类向 System.out/err输出的信息
     * @param classByte 代表一个Java类的 byte数组
     * @return 执行结果
     */
    public static String execute(byte[] classByte) {
        HackSystem.clearBuffer();

        ClassModifier cm = new ClassModifier(classByte);
        byte[] modiBytes = cm.modifyUtf8Constant("java/lang/System", "com/youye/remote/HackSystem");
        HotSwapClassLoader loader = new HotSwapClassLoader();
        Class clazz = loader.loadByte(modiBytes);
        try {
            Method method = clazz.getMethod("main", new Class[] { String[].class});
            method.invoke(null, new String[] {null});
        } catch (Exception e) {
            e.printStackTrace();
        }

        return HackSystem.getBufferString();
    }
}

类文件的字节码修改器:

public class ClassModifier {

    /**
     * Class 文件中常量池的起始偏移地址
     */
    private static final int CONSTANT_POOL_COUNT_INDEX = 8;

    /**
     * CONSTANT_Utf8_info 常量的tag标志
     */
    private static final int CONSTANT_Utf8_info = 1;

    /**
     * 常量池中14中常量所占的长度,CONSTANT_Utf8_info 型常量除外,因为它不是定长的
     *
     * 每种常量都有一个tag标签,根据tag的值来获取常量所占的长度,tag最大值为18.
     */
    private static final int[] CONSTANT_ITEM_LENGTH = {-1, -1, 5, -1, 5, 9, 9, 3, 3, 5, 5, 5, 5, -1, -1, 4, 3, -1, 5};

    private static final int u1 = 1;

    private static final int u2 = 2;

    private byte[] classByte;

    public ClassModifier(byte[] classByte) {
        this.classByte = classByte;
    }

    /**
     * 修改常量池中CONSTANT_Utf8_info常量的内容
     * @param oldStr 修改前的字符串
     * @param newStr 修改后的字符串
     * @return 修改结果
     */
    public byte[] modifyUtf8Constant(String oldStr, String newStr) {
        int cpc = getConstantPoolCount();
        int offset = CONSTANT_POOL_COUNT_INDEX + u2;
        for (int i = 0; i < cpc; i++) {
            int tag = ByteUtils.bytes2Int(classByte, offset, u1);
            if (tag == CONSTANT_Utf8_info) {
                // 获取CONSTANT_Utf8_info常量的Utf-8编码的字符串占用的字节数
                int len = ByteUtils.bytes2Int(classByte, offset + u1, u2);
                offset += (u1 + u2);
                // 获取该CONSTANT_Utf8_info常量所对应的字符串内容
                String str = ByteUtils.bytes2String(classByte, offset, len);
                if (str.equalsIgnoreCase(oldStr)) {
                    // 将newStr转换成byte[]
                    byte[] strBytes = ByteUtils.string2Bytes(newStr);
                    // 获取字符串长度对应的所占u2字节的byte[]
                    byte[] strLen = ByteUtils.int2Bytes(newStr.length(), u2);
                    // 替换CONSTANT_Utf8_info常量中代表Utf-8编码的字符串所占用的字节长度
                    classByte = ByteUtils.bytesReplace(classByte, offset - u2, u2, strLen);
                    // 替换字符串内容
                    classByte = ByteUtils.bytesReplace(classByte, offset, len, strBytes);

                    return classByte;
                } else {
                    offset += len;
                }
            } else {
                offset += CONSTANT_ITEM_LENGTH[tag];
            }
        }

        return classByte;
    }

    /**
     * 获取常量池中常量的数量
     *
     * @return 常量池数量
     */
    public int getConstantPoolCount() {
        // 在常量池中,第9-10的两个字节代表常量的数量
        return ByteUtils.bytes2Int(classByte, CONSTANT_POOL_COUNT_INDEX, u2);
    }
}

byte[] 转换工具

public class ByteUtils {

    public static int bytes2Int(byte[] b, int start, int len) {
        int sum = 0;
        int end = start + len;
        for (int i = start; i < end; i++) {
            // 将b[i] 转成二进制,并取低8位,
            // 因为0xff高8位全为0,低0位全为1,任何数A与0xff,导致A的高位全为0,低8位不变。
            int n = ((int) b[i]) & 0xff;
            // 高8位左移8位,低8位不变
            n <<= (--len) * 8;
            sum = n + sum;
        }
        return sum;
    }

    public static byte[] int2Bytes(int value, int len) {
        byte[] b = new byte[len];
        for (int i = 0; i < len; i++) {
            b[len - i - 1] = (byte) ((value >> 8 * i) & 0xff);
        }

        return b;
    }

    public static String bytes2String(byte[] b, int start, int len) {
        return new String(b, start, len);
    }

    public static byte[] string2Bytes(String str) {
        return str.getBytes();
    }

    public static byte[] bytesReplace(byte[] originalBytes, int offset, int len, byte[] replaceBytes) {
        byte[] newBytes = new byte[originalBytes.length + (replaceBytes.length - len)];

        System.arraycopy(originalBytes, 0, newBytes, 0, offset);
        System.arraycopy(replaceBytes, 0, newBytes, offset, replaceBytes.length);
        System.arraycopy(originalBytes, offset + len, newBytes, offset + replaceBytes.length, originalBytes.length - offset - len);

        return newBytes;
    }
}

输出函数

public class HackSystem {

    public final static InputStream in = System.in;

    private static ByteArrayOutputStream buffer = new ByteArrayOutputStream();

    public final static PrintStream out = new PrintStream(buffer);

    public final static PrintStream err = out;

    public static String getBufferString() {
        return buffer.toString();
    }

    public static void clearBuffer() {
        buffer.reset();
    }

    public static void setSecurityManager(final SecurityManager s) {
        System.setSecurityManager(s);
    }

    public static SecurityManager getSecurityManager() {
        return System.getSecurityManager();
    }

    public static long currentTimeMillis() {
        return System.currentTimeMillis();
    }

    public static long nanoTime() {
        return System.nanoTime();
    }

    public static void arrayCopy(Object src, int srcPos, Object dest, int destPos, int length) {
        System.arraycopy(src, srcPos, dest, destPos, length);
    }

    public static int identityHashCode(Object x) {
        return System.identityHashCode(x);
    }

    public static Console console() {
        return System.console();
    }

    public static Channel inheritedChannel() throws IOException {
        return System.inheritedChannel();
    }

    public static Properties getProperties() {
        return System.getProperties();
    }

    public static String lineSeparator() {
        return System.lineSeparator();
    }

    public static void setProperties(Properties props) {
        System.setProperties(props);
    }

    public static String getProperty(String key) {
        return System.getProperty(key);
    }

    public static String getProperty(String key, String def) {
        return System.getProperty(key, def);
    }

    public static String setProperty(String key, String value) {
        return System.setProperty(key, value);
    }

    public static String clearProperty(String key) {
        return System.clearProperty(key);
    }

    public static String getenv(String name) {
        return System.getenv(name);
    }

    public static java.util.Map<String, String> getenv() {
        return System.getenv();
    }

    public static void exit(int status) {
        System.exit(status);
    }

    public static void gc() {
        System.gc();
    }

    public static void runFinalization() {
        System.runFinalization();
    }

    public static void runFinalizersOnExit(boolean value) {
        System.runFinalizersOnExit(value);
    }

    public static void load(String filename) {
        System.load(filename);
    }

    public static void loadLibrary(String libname) {
        System.loadLibrary(libname);
    }

    public static void mapLibraryName(String libname) {
        System.mapLibraryName(libname);
    }

}

类加载器

public class HotSwapClassLoader extends ClassLoader{

    public HotSwapClassLoader() {
        super(HotSwapClassLoader.class.getClassLoader());
    }

    public Class loadByte(byte[] classByte) {
        return defineClass(null, classByte, 0, classByte.length);
    }
}

具体代码请见https://github.com/SinPingWu/MiChatServer.git 的RemoteExecuteController。欢迎fork and start