本章内容稍作改变,首先介绍一下代理模式和动态代理模式,然后来实现本章提到的实战内容。
1. 代理
所谓代理及让别人来帮你做事,就像生产厂商在各地有代理商来帮他们卖产品(此处只关心买产品的功能)。
1.1 类结构图
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 类结构图
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();
}
}
在上述代码中需要注意的地方有两个
- Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
这代码的目的是通过Proxy.newProxyInstance()创建一个代理类。
- 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