Java高级——类加载及执行子系统的案例与实战

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

  • 概述
  • 类加载器案例
    • Tomcat
    • OSGi
  • 字节码案例
    • 动态代理
    • 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

OSGi(Open Service Gateway Initiative)是OSGi联盟制订的一个基于Java的动态模块化规范(JDK 9的JPMS是静态的模块系统)

OSGi中的每个模块(称为Bundle)与普通的Java类库类似,以JAR格式进行封装,内部存储的Java的Package和Class

Bundle可以声明它所依赖的Package(Import-Package),也可以声明它允许导出的Package(Export-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 world

Proxy.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包
  • 改进前端编译器,称为语法糖,如自动装箱拆箱
  • 字节码改动,如invokedynamic
  • JDK整体结构改动,如模块化系统
  • JVM内部改动,如更换垃圾收集器

对于第一类,可使用其他可代替的包去实现,如Retrotranslator中存在backport-util-concurrent.jar代替concurren

对于第二类,JDK在编译阶段进行的改进,Retrotranslator使用ASM框架对字节码进行处理,如Retrotranslator将枚举的父类Enum转为自身类库的net.sf.retrotranslator.runtime.java.lang.Enum_,再去掉ACC_ENUM标志位

对于第三类,invokedynamic实现Lambda,Retrolambda生成一组匿名内部类来替代Lambda

而第四第五类,使用逆向移植工具比较难处理

实战——远程执行功能

有时候需要在服务中执行一小段程序代码定位或排除问题(如查看内存的参数值),但却没有让服务器执行临时代码的途径,可通过

  • BTrace这类JVMTI工具去动态修改程序中某一部分的运行代码
  • JDK 6的Compiler API可以动态地编译Java程序
  • 写一个JSP文件在服务器浏览器中运行它,或者在服务端程序中新增BeanShell Script、JavaScript等的执行引擎去执行动态脚本
  • 在应用程序中内置动态执行的功能

目标

实现在服务端执行临时代码

  • 不依赖于JDK版本(1.4以上)
  • 不改变原有服务端程序的部署,不依赖第三方类库
  • 无须改动原程序代码,也不会影响源程序运行
  • 临时代码支持Java
  • 不需要依赖特定的类或实现特定的接口
  • 执行结果能返回到客户端

思路

如何编译提交到服务器的Java代码?

  • JDK6后可使用Compiler API,JDK6前可使用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 import="java.lang.*" %>
<%@ page import="java.io.*" %>
<%@ page import="org.fenixsoft.classloading.execute.*" %>
<%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(JavaclassExecuter.execute(b));out.println("</textarea>");
%>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/74017.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php使用jwt作登录验证

1 在项目根目录下&#xff0c;安装jwt composer require firebase/php-jwt 2 在登录控制器中加入生成token的代码 use Firebase\JWT\JWT; use Firebase\JWT\Key; class Login extends Cross {/*** 显示资源列表** return \think\Response*/public function index(Request $r…

Lua语法结构

Lua基础 注释 print("hello.") -- 单行注释的写法 --[[ 多行注释的写法 --]]标识符 关键字 **and **break**do **else**elseif ****end **falsefor**function **ifinlocalnilnotorrepeatreturnthentrueuntil**while ** 数据类型 nil** boolean**** number**** st…

Oracle数据库概念简介

1. 数据库 一般意义上的数据库包含两个部分 库&#xff1a;就是一个存储一堆文件的文件目录数据库管理系统&#xff1a;管理库的系统 2. DBMS 数据库管理系统 数据库管理系统(Database Management System)&#xff0c;是一种操纵和管理数据库的大型软件&#xff0c;用于建立…

Bun v0.8.0 正式发布,Zig 编写的 JavaScript 运行时

Bun 是采用 Zig 语言编写的高性能 “全家桶” JavaScript 运行时&#xff0c;官方称其为 "all-in-one JavaScript runtime"。 所谓 "all in one"&#xff0c;是因为 Bun 提供了打包、转译、安装和运行 JavaScript & TypeScript 项目的功能&#xff0c…

华为mate60 上线 媒介盒子多家媒体报道

为什么你的品牌营销不见效&#xff1f;如何能推动品牌破圈&#xff1f;让媒介盒子给你一些启发。本期盒子要跟大家分享地新机上市&#xff0c;数码科技行业企业该如何做线上宣传。 HUAWEI Mate 60系列8月29日官宣发布&#xff0c;出色的拍照功能、强大的性能表现和持久的续航能…

初阶三子棋(超详解)

✨博客主页&#xff1a;小钱编程成长记 &#x1f388;博客专栏&#xff1a;C语言小游戏 初阶三子棋 1.游戏介绍2.基本思路3.实现前的准备4.实现步骤4.1 打印菜单4.2 初始化棋盘4.3 打印棋盘4.4 玩家下棋4.5 电脑下棋4.6 判断本局游戏继续还是结束4.7 优化棋盘的显示 5.游戏代码…

docker 笔记5:redis 集群分布式存储案例

尚硅谷Docker实战教程&#xff08;docker教程天花板&#xff09;_哔哩哔哩_bilibili 目录 1.cluster(集群)模式-docker版哈希槽分区进行亿级数据存储 1.1面试题 1.1.1 方案1 哈希取余分区 1.1.2 方案2 一致性哈希算法分区 原理 优点 一致性哈希算法的容错性 一致性…

MySQL的用户管理

1、MySQL的用户管理 &#xff08;1&#xff09;创建用户 create user zhang3 identified by 123123;表示创建名称为zhang3的用户&#xff0c;密码设为123123。 &#xff08;2&#xff09;了解user表 1&#xff09;查看用户 select host,user,authentication_string,select…

计算机网络的故事——确保Web安全的Https

确保Web安全的Https 文章目录 确保Web安全的Https一、HTTP 的缺点二、HTTP 加密 认证 完整性保护 HTTPS 一、HTTP 的缺点 1、明文传输 通信加密&#xff0c;HTTP协议中没有加密机制&#xff0c;但是可以通过SSL(Secure Socket Layer&#xff0c;安全套接字层)或TLE(Transpor…

PhpStorm软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 PhpStorm是一款由JetBrains开发的专业PHP集成开发环境&#xff08;IDE&#xff09;&#xff0c;旨在提供全面的PHP开发支持。它是基于IntelliJ IDEA平台构建的&#xff0c;具有强大的功能和工具&#xff0c;可以帮助开发人员提高…

springboot~自定义favicon加载问题

影响自定义favicon加载的原因 1、浏览器缓存问题2、由于favicon图标是在一个session会话中&#xff0c;所以需要关闭重开浏览器3、favicon源文件格式问题 1、浏览器缓存问题 清空浏览器缓存&#xff0c;或者是在network请求中停用缓存 2、由于favicon图标是在一个session会话中…

30天入门Python(基础篇)——第1天:为什么选择Python

文章目录 专栏导读作者有话说为什么学习Python原因1(总体得说)原因2(就业说) Python的由来(来自百度百科)Python的版本 专栏导读 &#x1f525;&#x1f525;本文已收录于《30天学习Python从入门到精通》 &#x1f251;&#x1f251;本专栏专门针对于零基础和需要重新复习巩固…

C语言共用体详解

文章目录 共用体解释代码说明小端存储 Little Endian共用体与小端存储为什么只能用第一个成员类型的值初始化一个共用体变量 共用体解释 共用体&#xff08;Union&#xff09;是一种特殊的数据类型&#xff0c; 它允许在同一个内存位置存储不同的数据类型。 共用体的所有成员共…

【C++入门】C语言的不足之处

概要 C入门主要讲的是C语言的一些不足&#xff0c;C作为补充&#xff0c;来补充C的不足之处 C的关键字有63个&#xff0c;C语言有32个&#xff08;作为了解&#xff0c;不需要专门记&#xff09; 变量的命名规则&#xff1a; 变量名必须以字母或下划线开头。变量名只能包含字…

数据结构与算法-----顺序表(链表篇)

目录 前言 顺序表 链表 概念 与数组的不同 单链表 1. 创建节点 2.插入节点 尾插节点&#xff08;形成链表结构&#xff09; 向指定位置插入节点&#xff08;链表已有&#xff09; ​编辑 3.遍历链表数据 4.获取链表长度 5.删除节点 删除尾节点 删除指定节点 …

MyBatis的逆向工程

文章目录 前言MyBatis的逆向工程创建逆向工程的步骤添加依赖和插件创建MyBatis的核心配置文件创建逆向工程的配置文件执行MBG插件的generate目标 QBC查询增改 总结 前言 MyBatis的逆向工程 正向工程&#xff1a;先创建Java实体类&#xff0c;由框架负责根据实体类生成数据库表…

微信支付-Native支付(网页二维码扫码微信支付)简单示例

目录 概述 界面展示 & 前端代码&#xff08;Vue&#xff09; 后端实现&#xff08;SpringBoot&#xff09; Maven依赖 下单接口&#xff08;主要功能代码&#xff09; 支付成功回调接口 测试&#xff08;后端线上环境&#xff09; 概述 本篇博文主要演示和说明网页扫…

腾讯云新用户有哪些优惠政策和专属活动?

腾讯云作为中国领先的云计算服务提供商&#xff0c;一直在为用户提供优质、高效且具有竞争力的服务。对于新用户&#xff0c;腾讯云更是诚意满满&#xff0c;推出了一系列优惠政策和专属活动。本文将详细介绍腾讯云新用户的优惠政策和专属活动&#xff0c;帮助大家更好地了解如…

【算法专题突破】滑动窗口 - 长度最小的子数组(9)

目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 1. 题目解析 题目链接&#xff1a;209. 长度最小的子数组 - 力扣&#xff08;Leetcode&#xff09; 要注意的是&#xff0c;题目给的是正整数&#xff0c; 而题目要求并不难理解&#xff0c;就是找最短的…

Python批处理(一)提取txt中数据存入excel

Python批处理&#xff08;一&#xff09;提取txt中数据存入excel 问题描述 现从冠层分析软件中保存了叶面积指数分析的结果&#xff0c;然而软件保存格式为txt&#xff0c;且在不同的文件夹中&#xff0c;每个文件夹的txt文件数量不固定&#xff0c;但是txt文件格式固定。现需…