书中的案例自己实现一遍,过程如下文
具体是在eclipse中实现,整体截图如下:

1.ByteUtils的代码如下:
package org.jvm;
/**
* Bytes数组处理工具
*/
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++){
int n = ((int) b[i]) & 0xff;
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;
}
}
2.ClassModifier代码如下:
package org.jvm;
/**
* 修改Class文件,暂时只提供修改常量池常量的功能
*/
public class ClassModifier {
private static final int CONSTANT_POOL_COUNT_INDEX = 8;
private static final int CONSTANT_Utf8_info = 1;
private static final int[] CONSTANT_ITEM_LENGTH = {-1,-1,-1,5,5,9,9,3,3,5,5,5,5};
private static final int u1 = 1;
private static final int u2 = 2;
private byte[] classByte;
public ClassModifier(byte[] classByte){
this.classByte = classByte;
}
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){
int len = ByteUtils.bytes2Int(classByte, offset + u1, u2);
offset += (u1 + u2);
String str = ByteUtils.bytes2String(classByte, offset, len);
if(str.equalsIgnoreCase(oldStr)){
byte[] strBytes = ByteUtils.string2Bytes(newStr);
byte[] strLen = ByteUtils.int2Bytes(newStr.length(),u2);
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;
}
public int getConstantPoolCount(){
return ByteUtils.bytes2Int(classByte, CONSTANT_POOL_COUNT_INDEX, u2);
}
}
3.HackSystem代码如下
package org.jvm;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
/**
* 为JavaClass劫持java.lang.System提供支持
* 除了out和err外,其余的都直接转发给System处理
*/
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 currentTimeMills(){
return System.currentTimeMillis();
}
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);
}
}
4.HotSwapClassLoader代码如下
package org.jvm;
/**
* 为了多次载入执行类而加入的加载器
* 把defineClass方法开放出来,只有外部显式调用的时候才会使用到loadByte方法
* 由虚拟机调用时,仍然按照原有的双亲委派规则使用loadClass方法进行类加载
*/
public class HotSwapClassLoader extends ClassLoader{
public HotSwapClassLoader() {
super(HotSwapClassLoader.class.getClassLoader());
}
public Class<?> loadByte(byte[] classByte){
return defineClass(null,classByte,0,classByte.length);
}
}
5.JavaClassExecuter代码如下
package org.jvm;
/**
* 为了多次载入执行类而加入的加载器
* 把defineClass方法开放出来,只有外部显式调用的时候才会使用到loadByte方法
* 由虚拟机调用时,仍然按照原有的双亲委派规则使用loadClass方法进行类加载
*/
public class HotSwapClassLoader extends ClassLoader{
public HotSwapClassLoader() {
super(HotSwapClassLoader.class.getClassLoader());
}
public Class<?> loadByte(byte[] classByte){
return defineClass(null,classByte,0,classByte.length);
}
}6.test.jsp代码如下
<%@ page import="java.lang.*" %>
<%@ page import="java.io.*" %>
<%@ page import="org.jvm.*" %>
<%
InputStream is = new FileInputStream("c:/TestClass.class");
byte[] b = new byte[is.available()];
is.read(b);
is.close();
out.println("<textarea style='width:1000;heigth=800'>");
out.println(JavaClassExecuter.execute(b));
out.println("</textarea>");
%>
7.测试类放到了c盘根目录下,如下

其中TestClass代码如下
/**
* 测试类
*/
public class TestClass {
public static void main(String[] args) {
System.out.println("-----this is test class out println----");
System.out.println("hahhahahaha");
}
}以管理员身份打开cmd窗口,如下:

javac编译TestClass文件,如下:

最后运行tomcat,如下:

访问结果如下:

当直接修改TestClass.java文件,不关闭tomcat,直接运行localhost:8080/classLoaderTest/test.jsp,结果随之而变。
本文介绍了一个Java应用程序热更新的实现方案,通过自定义类加载器HotSwapClassLoader支持类文件的即时替换,并利用ClassModifier类来修改类文件中的常量池。此外,还展示了如何使用HackSystem类拦截System类的方法调用。
1360

被折叠的 条评论
为什么被折叠?



