iis 网站拒绝显示此网页,上海网站建设类岗位,图怪兽海报制作官网,苏州seo报价类加载及执行子系统的案例与实战 概述类加载器案例TomcatOSGi 字节码案例动态代理Java逆向移植工具 实战——远程执行功能目标思路实现验证#xff08;未完成#xff0c;不会写JSP#xff09; 概述
Class文件以何种格式存储、字节码指令如何执行等都是由JVM控制
字节码生成… 类加载及执行子系统的案例与实战 概述类加载器案例TomcatOSGi 字节码案例动态代理Java逆向移植工具 实战——远程执行功能目标思路实现验证未完成不会写JSP 概述
Class文件以何种格式存储、字节码指令如何执行等都是由JVM控制
字节码生成与类加载器这两部分的功能可由用户自定义接下来将对一些实际应用进行介绍
类加载器案例
Tomcat
主流的Java Web服务器如Tomcat、Jetty等自定义了类加载器且不止一个为了实现如下需求
服务器上的两个Web应用的Java类库需要隔离可能使用不同版本或共享避免重复加载同一类库服务器需保证自身安全不受影响也应与应用所使用的类库互相独立JSP最终要编译成Class文件且JSP支持热替换
由于上述问题部署Web应用时需提供多个ClassPath存放第三方类库其以lib或classes命名
/common中的类库可被Tomcat和所有的Web应用程序共享/server中的类库只能被Tomcat使用/shared中的类库可被所有的Web应用程序共享/WebApp/WEB-INF的类库仅可被该Web应用程序使用 上图为Tomcat中的类加载器按照双亲委派模型实现
Common、Catalina、Shared、Webapp加载器分别对应加载上面路径的类库Common加载的类都可以被Catalina和Shared使用Catalina和Shared加载的类相互隔离WebApp可使用Shared加载的类一个Web应用程序对应一个WebAppClassLoader相互隔离一个JSP文件对应一个JasperLoader当JSP文件被修改时会生成新的JasperLoader替换以此实现HotSwap
tomcat 6之后
/common、/server和/shared合并成/lib相当于原来的/common只有指定tomcat/conf/catalina.properties中的server.loader和share.loader才会建立Catalina和Shared否则使用Common
OSGi
OSGiOpen Service Gateway Initiative是OSGi联盟制订的一个基于Java的动态模块化规范JDK 9的JPMS是静态的模块系统
OSGi中的每个模块称为Bundle与普通的Java类库类似以JAR格式进行封装内部存储的Java的Package和Class
Bundle可以声明它所依赖的PackageImport-Package也可以声明它允许导出的PackageExport-Package从传统的上层模块依赖底层模块转变为平级模块之间的依赖
OSGi的优点是可以实现模块热插拔类加载器无固定的委派关系对Package的类加载都会委派给发布它的Bundle类加载器去完成若对于
Bundle A发布了packageA依赖java.*Bundle B依赖了packageA和packageC依赖java.*Bundle C发布了packageC依赖packageA
则它们的类加载关系如下 类加载的查找规则如下
java.*开头的类委派给父类加载器加载否则委派列表名单内的类委派给父类加载器加载否则Import列表中的类委派给Export这个类的Bundle的类加载器加载。否则查找当前Bundle的Classpath使用自己的类加载器加载否则查找是否在自己的Fragment Bundle中如果是则委派给Fragment Bundle的类加载器加载否则查找Dynamic Import列表的Bundle委派给对应Bundle的类加载器加载。否则类查找失败
OSGi中的加载器不再是双亲委派模型的树形结构而是网状结构
JDK7之前加载需要锁定当前类加载器若出现循环依赖可能导致死锁而JDK7后将锁定对象降低为类名级别从而避免死锁
字节码案例
动态代理
动态代理中的“动态”是相对与编写代理类的“静态”代理而言其优势不仅是省去编码的工作量而是实现了在原始类和接口未知时就确定代理类的代理行为
public class Test {interface IHello {void sayHello();}static class Hello implements IHello {Overridepublic 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);}Overridepublic 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();}}如上代码通过代理在hello world之前加上welcome
welcome
hello worldProxy.newProxyInstance()方法返回一个实现了IHello的接口并且代理了new Hello()实例行为的对象
代理通过调用sun.misc.ProxyGenerator::generateProxyClass()方法来生成字节码在main中加入如下代码可在磁盘中生成一个名为$Proxy0的代理类Class文件
System.getProperties().put(sun.misc.ProxyGenerator.saveGeneratedFiles, true);将其反编译为如下
final class $Proxy0 extends Proxy implements IHello {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocationHandler var1) throws {super(var1);}public final boolean equals(Object var1) throws {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final void sayHello() throws {try {super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final String toString() throws {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {try {m1 Class.forName(java.lang.Object).getMethod(equals, Class.forName(java.lang.Object));m3 Class.forName(Test$IHello).getMethod(sayHello);m2 Class.forName(java.lang.Object).getMethod(toString);m0 Class.forName(java.lang.Object).getMethod(hashCode);} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}代理类为接口及Object中继承的方法都生成了对应的实现并调用了InvocationHandler对象即h的invoke()方法所以无论调用DynamicProxy的哪一个方法都会回调invoke()
Java逆向移植工具
当需要把高版本JDK编写的代码放到低版本JDK环境中去部署使用可使用 Retrotranslator将JDK5编译的Class文件转变为可在JDK1.4/3上部署的版本 Retrolambda将JDK 8的Lambda表达式和try-resources语法转变为可以在JDK5、JDK 6、JDK 7中使用的形式
每次JDK升级新增功能可分为五大类
增强Java类库如concurren、invoke包改进前端编译器称为语法糖如自动装箱拆箱字节码改动如invokedynamicJDK整体结构改动如模块化系统JVM内部改动如更换垃圾收集器
对于第一类可使用其他可代替的包去实现如Retrotranslator中存在backport-util-concurrent.jar代替concurren
对于第二类JDK在编译阶段进行的改进Retrotranslator使用ASM框架对字节码进行处理如Retrotranslator将枚举的父类Enum转为自身类库的net.sf.retrotranslator.runtime.java.lang.Enum_再去掉ACC_ENUM标志位
对于第三类invokedynamic实现LambdaRetrolambda生成一组匿名内部类来替代Lambda
而第四第五类使用逆向移植工具比较难处理
实战——远程执行功能
有时候需要在服务中执行一小段程序代码定位或排除问题如查看内存的参数值但却没有让服务器执行临时代码的途径可通过
BTrace这类JVMTI工具去动态修改程序中某一部分的运行代码JDK 6的Compiler API可以动态地编译Java程序写一个JSP文件在服务器浏览器中运行它或者在服务端程序中新增BeanShell Script、JavaScript等的执行引擎去执行动态脚本在应用程序中内置动态执行的功能
目标
实现在服务端执行临时代码
不依赖于JDK版本1.4以上不改变原有服务端程序的部署不依赖第三方类库无须改动原程序代码也不会影响源程序运行临时代码支持Java不需要依赖特定的类或实现特定的接口执行结果能返回到客户端
思路
如何编译提交到服务器的Java代码
JDK6后可使用Compiler APIJDK6前可使用tool.jar但是引入依赖直接把编译好的字节码而不是Java代码上传
如何执行编译之后的java代码
让类加载器加载这个类生成一个Class对象然后通过反射调用
如何收集Java代码的执行结果
使用System.out和System.err将输出重定向但会收集到其他应用信息可将System.out的符号引用替换为自定义PrintStream的符号引用
实现
HotSwapClassLoader用于实现用一个类的代码可以被多次加载的需求用loadByte()方法公开defineClass()把byte[]数组转变为Class对象
public class HotSwapClassLoader extends ClassLoader {public HotSwapClassLoader() {super(HotSwapClassLoader.class.getClassLoader());}public Class loadByte(byte[] classByte) {return defineClass(null, classByte, 0, classByte.length);}
}ClassModifier的modifyUTF8Constant()方法将Class数组流的常量替换为指定常量从而实现将System替换为自定义的HackSystem 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, -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);}
}ByteUtils用于对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) {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);return newBytes;}
}HackSystem将out和err改为用PrintStream对象以及增加了读取、清理ByteArrayOutputStream中内容的getBufferString()和clearBuffer()其余方法调用原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 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);}
}JavaclassExecuter组合前面的类完成类加载
public class JavaclassExecuter {public static String execute(byte[] classByte) {HackSystem.clearBuffer();ClassModifier cm new ClassModifier(classByte);byte[] modiBytes cm.modifyUTF8Constant(java/lang/System, 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();}}验证未完成不会写JSP
任意书写一个TestClass类向System.out输出信息再建立个JSP文件用于再浏览器中看TestClass的运行结果
% page importjava.lang.* %
% page importjava.io.* %
% page importorg.fenixsoft.classloading.execute.* %
%InputStream is new FileInputStream(c:/TestClass.class);byte[] b new byte[is.available()];is.read(b);is.close();out.println(textarea stylewidth:1000;height800);out.println(JavaclassExecuter.execute(b));out.println(/textarea);
%