第9章 类加载及执行子系统的案例与实战

来源:互联网 发布:大数据研究生考试科目 编辑:程序博客网 时间:2024/05/29 07:56

概述:

学习《深入理解java虚拟机》

1、字节码生成技术与动态代理的实现

package com.jack;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class DynamicProxyTest {interface IHello {void sayHello();}static class Hello implements IHello {public void sayHello() {System.out.println("hello world");}}static class DynamicProxy implements InvocationHandler {Object originalObj;Object bind(Object originalObj) {this.originalObj = originalObj;return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this);}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("welcome");return method.invoke(originalObj, args);}}public static void main(String[] args){IHello hello = (IHello) new DynamicProxy().bind(new Hello());hello.sayHello();}}


日志:

welcome

hello world

总结:

  • 1、定义一个接口IHello,实现接口的实现类。
  • 2、创建实现InvocationHandler 接口代理。需要绑定对象。
  • 3、你需要告诉代理类加载器,类对应的接口。
  • 4、在调用的时候,它会自动拦截调用,先执行invoke() 方法,然后回调sayHello()方法


2、Retranslator:跨越JDK版本

这个工具就是比如更新jdk1.7版本有一些新的特性,由于项目的原因只能使用jdk1.6,如果想要使用jdk1.7的新特性该怎么办,那么就jdk1.6实现jdk1.7的功能。这就是Retrotranslator,但看官网发现没有更新,现在可能不需要这么做了。

3、实战:自己动手实现远程执行功能

排查问题的过程中,查看内存中的一些参数值,却又没有方法把这些值输出到界面或日志中,又或者定位到某个缓存数据有问题,但缺少缓存的统一管理界面,不能被重启服务才能清理这个缓存。类似的需求有一个共同的特点,就是只要在服务中执行一段程序代码,可以定位或排除问题。但就是偏偏找不到可以让服务器执行临时代码的途径。

目标:

1、不依赖JDK版本

2、不改变原有服务端部署,不依赖任何第三方类库

3、临时代码具有灵活性。

4、临时代码的执行结果能够返回客户端。

实施步骤:

1、创建一个加载类的方法。

2、重写java.lang.System方法,将结果返回

3、采用jsp加载一个执行main方法

4、在浏览器显示

工具类

package com.rinlink.intelligent.test;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 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[] 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;}/** * 字节数组替换 * @param originalBytes  需要替换原数组 * @param offset    原数组偏移位置 * @param len原数组替换的长度 (替换原数组旧内容offset 到len) * @param replaceBytes   目标数组(替换新的内容) * @return */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;}}


package com.rinlink.intelligent.test;/** * 修改Class文件,暂时提供修改常量池常量的功能 * @author Administrator * */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;/** * 常量池中11中常量所占的长度,CONSTANT_Utf8_info常量除外,因为它不是定长 *  */private static final int[] CONSTANT_ITEM_LENGTH={-1,-1,5,-1,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;}/** * 修改常量池中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){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, strLen);return classByte;} else {offset += len;}}else {offset += CONSTANT_ITEM_LENGTH[tag];}}return classByte;}/** * 获取常量池中常量的数量 * @return */private int getConstantPoolCount() {return ByteUtils.bytes2Int(classByte, CONSTANT_POOL_COUNT_INDEX, u2);}}

总结:

这个类主要目的是修改编译class字节码文件java/lang/System 常量变成你定义输入类如下定义HackSystem的类路径,不过是以/分割

package com.rinlink.intelligent.test;import java.io.ByteArrayOutputStream;import java.io.InputStream;import java.io.PrintStream;/** * 为JavaClass劫持java.lang.System提供支持 * 除了out和err以外,其余的都直接转发给System处理 * @author Administrator * */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 setSecurityManager(final SecurityManager s){System.setSecurityManager(s);}public static SecurityManager getSecurityManager(){return System.getSecurityManager();}public static long currentTimeMillis(){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);}public static void clearBuffer() {buffer.reset();}}

总结:这个主要是劫持默认java/lang/System的流,将它返回给客户端

package com.rinlink.intelligent.test;/** * 为了多次载入执行类而加入的加载器 * 把defineClass方法开放出来,只有外部显式调用的时候才会使用到loadByte方法 * 由虚拟机调用时,仍然按照原有的双亲委派规则使用loadClass方法进行类加载 * @author Administrator * */public class HotSwapClassLoader extends ClassLoader{public HotSwapClassLoader(){super(HotSwapClassLoader.class.getClassLoader());}public Class loadByte(byte[] classByte){return defineClass(null, classByte, 0, classByte.length);}}

总结:加载编译的类

package com.rinlink.intelligent.test;import java.lang.reflect.Method;/** * JavaClass执行工具 * @author Administrator * */public class JavaClassExecutor {public static String execute(byte[] classByte){HackSystem.clearBuffer();ClassModifier cm = new ClassModifier(classByte);byte[] modiBytes = cm.modifyUTF8Constant("/java/lang/System", "com/rinlink/intelligent/test/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 (Throwable e){e.printStackTrace(HackSystem.out);}return HackSystem.getBufferString();}}
总结:这是入口

  • 1、清除HackSystem缓存
  • 2、创建一个字节码修改文件
  • 3、修改字节码文件常量值/java/lang/System 变成com/rinlink/intelligent/test/HackSystem (也就是HackSystem路径)这个目的就是将会输出结果转发到客户端
  • 4、加载修改后的字节码文件
  • 5、执行字节码中main方法
  • 6、返回执行结果

创建一个jsp文件 test.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"    pageEncoding="utf-8"%><%@ page import = "java.lang.*" %><%@ page import="java.io.*" %><%@ page import="com.rinlink.intelligent.test.*" %><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Insert title here</title></head><body><%InputStream is = new FileInputStream("c:/TestClass.class");byte[] b = new byte[is.available()];is.read(b);is.close();out.println("<textarea style='width:1000; height=800'>");out.println(JavaClassExecutor.execute(b));out.println("</textarea");%></body></html>

在c盘根目录下创建一个文件 TestClass.java 然后编译成class文件

public class TestClass {public static void main(String[] args) {System.out.println("HelloWorld");}}

如何编译代码呢?

采用eclipse自带Tomcat将项目加载就编译代码,或者其它方法。

注意这些类放入位置和编译时包位置必须一致

为了测试,先用eclipse的tomcat启动项目完成编译,然后获取编译后的字节码文件单独放置某一个位置,然后删除eclipse文件源.java再一次启动,这时候就没有那个5个类字节码文件没有了,将刚才复制的编译好5个class文件复制到对应文件夹(这样做的目的模拟不重启新增字节码文件是否可以执行)。然后通过浏览器访问就可以看到效果。



最后结果:


阅读全文
0 0