黑马程序员---类加载器

2024-09-07 16:18
文章标签 加载 程序员 黑马

本文主要是介绍黑马程序员---类加载器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  ------- android培训、java培训、期待与您交流! ----------

 

简要介绍什么是类加载器和类加载器的作用

Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
 
类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是不是java类,这正是BootStrap。它是嵌套在java虚拟机内核里面的,Java虚拟机内核一启动,它就在里面了,它是用c++编写的。
 
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。

Class类中的方法:

 ClassLoadergetClassLoader()
          返回该类的类加载器。

ClassLoader类中的方法:

 ClassLoadergetParent()
          返回委托的父类加载器。

package cn.itcast.day2;
public class ClassLoaderTest {
public static void main(String[] args) {
System.out.println(
ClassLoaderTest.class.getClassLoader()
.getClass().getName());//sun.misc.Launcher$AppClassLoader
System.out.println(
System.class.getClassLoader() ); //null,不代表没有类加载器,而代表它是一个特殊的类加载器BootStrap。
//以下代码,验证 类加载器 的父子关系。
ClassLoader loader = ClassLoaderTest.class.getClassLoader();//AppClassLoader
while(loader!=null) {
System.out.println(loader.getClass().getName());
loader = loader.getParent();						
}
System.out.println(loader);
}
}

类加载器 的 父子关系,运行结果:

sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null  (代表:BootStrap)

类加载器之间的父子关系和管辖范围图:

我们将刚才写的那个类,右键 Export (以java的 jar 包的格式)导出到 jre/lib/ext/*.jar 文件夹下。

再运行程序,该类的类加载器变成了 ExtClassLoader。

此时的环境状态是classpath目录有ClassLoaderTest.classext/itcast.jar包中也有ClassLoaderTest.class

为什么由爸爸(ExtClassLoader)加载了,而没有被儿子(AppClassLoader)加载,这时候我们就需要了解类加载的具体过程和原理了。

类加载器的委托机制

先来说说ClassLoader家族的树型结构:

在这里,除了有系统自带的那些ClassLoader以外,我们还可以写自己的ClassLoader 挂到这个ClassLoader tree下面去。

我们写自己的ClassLoader的时候,都必须继承ClassLoader这个类。

你在new自己的ClassLoader实例的时候,就可以给它指定Parent。

要想把你的ClassLoader挂在家族tree下面去,就必须给它指定一个爸爸。也可以使用默认。

我们在最后也会写一个自己的类加载器,去挂到系统自带的来加载器树型结构下面。然后用我们的类加载器去加载我们特定的目录。

我们把我们加密的类放到那个特定目录下,当人家在使用 我们加密的类 的时候 就只能用 我们特定的类加载器 去加载。

我们的类加载器在加载的过程中就对这些类进行解密。解密完出来就是完好的字节码了。你如果用普通的类加载器加载进去就是一堆看不懂的没用的东西。

 
※当 Java 虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
Ø 首先当前线程的类加载器去加载线程中的第一个类。
Ø 如果类 A 中引用了类 B Java 虚拟机将使用加载类 A 的类装载器来加载类 B
Ø 还可以直接用你的那个 类加载器对象去.loadClass(要加载的类名称), 来指定某个类加载器去加载某个类。
 
※每个类加载器加载类时,又先委托给其上级类加载器。
Ø 当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛 ClassNotFoundException ,不是再去找发起者类加载器的儿子,因为没有 getChild 方法,即使有,那有多个儿子,找哪一个呢?
Ø 对着类加载器的层次结构图和委托加载原理,解释先前将 ClassLoaderTest 输出成 jre/lib/ext 目录下的 itcast.jar 包中后,运行结果为 ExtClassLoader 的原因。

 

每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。

有一道面试题:能不能自己写个类叫java.lang.System,我们要答:通常是不可以的,为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用java系统提供的System。但是我们还有一个办法是可以自己写一个类加载器,为了不让它采用类加载器的委托机制,这个类还得特殊的写。

把先前编写的类加入到jdkrt.jar中,会有怎样的效果呢?不行!!!看来是不能随意将自己的class文件加入进rt.jar文件中的。

编写自己的类加载器

※知识讲解(写类加载器的原理):

Ø 自定义的类加载器的必须继承 抽象类 ClassLoader
Ø loadClass 方法与 findClass 方法 (模板方法设计模式
loadClass方法有两部分:第一部分找爸爸;第二部分爸爸如果干不了,调用自己的findClass方法自己干。
因为每个类加载器在被运行时都要首先去找爸爸,所以找爸爸那部分代码是相同的,所以把这部分代码(loadClass方法)放在父类那里,我们只需要继承父类就可以了,自己干的代码不一样(覆盖findClass方法)。
如果你的类加载器比较特殊,就是不想去找爸爸了,要自己干。那么你就需要覆盖loadClass方法了,把找爸爸那部分代码去掉,直接让它去调用findClass方法即可。
Ø defineClass 方法
当我们得到了class file 里面那些二进制数据,怎么转换成字节码?就用defineClass方法去做。
class NetworkClassLoader extends ClassLoader {
String host;
int port;
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) {
// load the class data from the connection
. . .
}
}

小小的做个加密文件的试验: 
异或 ^ :真^真=假;真^假=真;假^真=真;假^假=假。(不同为真,相同为假)
一个数和同一个数异或两次,结果还是那个数。
package cn.itcast.day2;
import java.util.Date;
public class ClassLoaderAttachment extends Date{
public String toString() {
return "hello itcast";
}
}
package cn.itcast.day2;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyCLassLoaderTest {
public static void main(String[] args) throws Exception{
String src = args[0];
String destpath = args[1];
FileInputStream fis = new FileInputStream(src);
String dest = destpath + "\\" + src.substring(src.lastIndexOf("\\")+1);
FileOutputStream fos = new FileOutputStream(dest);
cypher(fis,fos);
fis.close();
fos.close();
}
private static void cypher(InputStream ips, OutputStream ops) throws Exception {
int b = -1;
while((b=ips.read())!=-1) {
ops.write(b^0xff);			
}		
}
}

*有包名的类 不能调用 没包名的类。 

运行此类时,先右键Run as 选下面那一项改参数:
生成加密后的class文件,拿此类文件替换bin文件夹下的类文件:
package cn.itcast.day2;
public class ClassLoaderTest {
public static void main(String[] args) {
System.out.println(new ClassLoaderAttachment().toString());
}
}
再运行此类,会报错: Incompatible magic value 889275713
说明附件类被加密,此时已经运行不了了,除非用我们自己写的类加载器加载的同时为该类解密。
接下来我们改写MyClassLoader类,来实现类加载器的功能:
编程步骤:
Ø 编写一个对类class文件内容进行简单加密的程序。(我们写了一个静态方法 cypher,加密)
Ø 编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
Ø 编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用 ClassLoader.load 方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用 Class.forName
package cn.itcast.day2;
import java.util.Date;
public class ClassLoaderAttachment extends Date{
public String toString() {
return "hello itcast";
}
}

package cn.itcast.day2;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader{
public static void main(String[] args) throws Exception{
String src = args[0];
String destpath = args[1];
FileInputStream fis = new FileInputStream(src);
String dest = destpath + "\\" + src.substring(src.lastIndexOf("\\")+1);
FileOutputStream fos = new FileOutputStream(dest);
cypher(fis,fos);
fis.close();
fos.close();
}
private static void cypher(InputStream ips, OutputStream ops) throws Exception {
int b = -1;
while((b=ips.read())!=-1) {
ops.write(b^0xff);			
}		
}
private String classDir;
@Override
//子类不能比父类抛出更广泛的异常。
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classFileName = classDir + "\\" + name.substring(name.lastIndexOf(".")+1) + ".class";
try {
FileInputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();//定义一个字节数组输出流。(自带一个数组,将输出的内容就存放到这个字节数组里)
cypher(fis,bos);//解密到bos里面。
fis.close();
System.out.println("aaa");
byte[] bytes = bos.toByteArray();//创建一个新分配的 byte 数组。其大小是此输出流的当前大小,并且缓冲区的有效内容已复制到该数组中。
return defineClass(bytes, 0, bytes.length);//将字节数组变成Class文件。			
} catch (Exception e) {			
e.printStackTrace();
}
return super.findClass(name);
}
public MyClassLoader() {}
public MyClassLoader(String classDir) {
this.classDir = classDir;		
}
}

package cn.itcast.day2;
import java.util.Date;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
System.out.println("xxx");
Class clazz = new MyClassLoader("itcastlib").loadClass("cn.itcast.day2.ClassLoaderAttachment");
Date d1 = (Date) clazz.newInstance();
System.out.println(d1);		
}
}
将D:\Workspaces\1\javaenhance\bin\cn\itcast\day2中的ClassLoaderAttachment.class文件删掉后,就会使用MyClassLoder加载。
不删掉的话默认用父类类加载器AppClassLoader加载。

 

※老师将itcast.jar删掉后,没有重启Eclipse,致使ExtClassLoader一直在找 jre/lib/ext 文件夹中的itcast.jar包,导致报错不断。

查错方法:

Window --- show view --- problems 窗口中查看 error 信息。

 

一个类加载器的高级问题分析

编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的 MyServlet ,正常发布后,看到打印结果为 WebAppClassloader
package cn.itcast.itcastweb.web.servlets;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyServlet extends HttpServlet {
/**
* The doGet method of the servlet. <br>
*
* This method is called when a form has its tag value method equals to get.
* 
* @param request the request send by the client to the server
* @param response the response send by the server to the client
* @throws ServletException if an error occurred
* @throws IOException if an error occurred
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();//这个out是往浏览器上面打。
ClassLoader loader = this.getClass().getClassLoader();
while(loader!=null) {
out.println(loader.getClass().getName() + "<br>");
loader = loader.getParent();			
}
out.flush();
out.close();
}
}

MyServlet.class 文件打 jar 包,放到 ext 目录中,重启 tomcat ,发现找不到 HttpServlet 的错误。
 
servlet.jar 也放到 ext 目录中,问题解决了,打印的结果是 ExtclassLoader
 
父级类加载器加载的类无法引用只能被子级类加载器加载的类,原理如下图:

 

 

 

 

  ------- android培训、java培训、期待与您交流! ----------

这篇关于黑马程序员---类加载器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1145538

相关文章

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

使用WebP解决网站加载速度问题,这些细节你需要了解

说到网页的图片格式,大家最常想到的可能是JPEG、PNG,毕竟这些老牌格式陪伴我们这么多年。然而,近几年,有一个格式悄悄崭露头角,那就是WebP。很多人可能听说过,但到底它好在哪?你的网站或者项目是不是也应该用WebP呢?别着急,今天咱们就来好好聊聊WebP这个图片格式的前世今生,以及它值不值得你花时间去用。 为什么会有WebP? 你有没有遇到过这样的情况?网页加载特别慢,尤其是那

LabVIEW程序员是怎样成长为大佬

成为一名LabVIEW编程领域的“大佬”需要时间、实践、学习和解决复杂问题的经验。尽管LabVIEW作为一种图形化编程语言在初期可能相对容易上手,但要真正成为精通者,需要在多个层面上深入理解。以下是LabVIEW程序员如何逐步成长为“大佬”的路径: 1. 打好基础 LabVIEW的大佬们通常在初期会打下非常坚实的基础,理解LabVIEW编程的核心概念,包括: 数据流编程模型:Lab

gazebo 已加载模型但无法显示

目录 写在前面的话问题一:robot_state_publisher 发布机器人信息失败报错一 Error: Error document empty.报错二 .xcaro 文件中有多行注释成功启动 问题二:通过 ros2 启动 gazebo 失败成功启动 问题三:gazebo 崩溃和无法显示模型问题四: 缺少 robot_description 等话题正确的输出 写在前面的话

程序员必备心理学——心流

心理学之心流 前言一、“心流”是什么?二、心流的好处二、如何进入心流心流状态的四个阶段第一个阶段:挣扎第二个阶段:放松第三个阶段:心流第四个阶段:巩固 进入心流的技巧 总结题外话 前言 你是否常常感觉自己明明学习了一整天,但是就是感觉没有太多的收获。这个时候除了你的学习方向等问题之外,也可能是你的学习方法太低效了。作者本人就经常有这种情况,好在偶然间在b站刷到一个大佬的这个心

JVM类的加载器及加载过程

类的加载器及加载过程 文章目录 类的加载器及加载过程类的加载过程加载:链接(验证、准备、解析):初始化: 类加载器的分类引导类加载器:BootstrapClassLoader 启动类加载器( C/C++实现,嵌套在JVM内部)自定义类加载器(所有派生于抽象类ClassLoader的类加载器)获取ClassLoader的途径 双亲委派机制(重点)判断两个Class对象是否为同一个类

Unity Adressables 使用说明(六)加载(Load) Addressable Assets

【概述】Load Addressable Assets Addressables类提供了加载 Addressable assets 的方法。你可以一次加载一个资源或批量加载资源。为了识别要加载的资源,你需要向加载方法传递一个键或键列表。键可以是以下对象之一: Address:包含你分配给资源的地址的字符串。Label:包含分配给一个或多个资源的标签的字符串。AssetReference Obj

Eclipse发布Maven项目到tomcat,无法加载到lib文件夹下的jar包

BMS 解决方法: 当我们发布web项目到tomcat时,访问地址时会报一个classnotfound的错误,但是eclipse中的项目中都已经添加了相应的类,有一种比较容易犯的错误是,你没有把额外所需的jar包加到tomcat中的lib文件夹中,在这里介绍一种在项目中直接添加jar包到lib目录下:  右键已创建的web项目——properties属性——点击Deployment Assem

jupyter在加载pkl文件时报错ModuleNotFoundError: No module named 'pandas.core.internals.managers'; '的解决方法

笔者当看到这个错误的时候一脸懵逼,在pycharm上正常运行的code 放在jupyter就不成了,于是就研究一翻。 一开始以为自己的pkl文件有问题,研究重点放在这里,最后发现不是。 然后取搜索pycharm和jupyter下的python的\Lib\site-packages\pandas\core\internals有什么不同 发现jupyter下没有pandas\core\intern

程序员都在使用的画图工具

大家好,我是袁庭新。 程序员都在使用的画图工具,你一定没用过这款画图工具吧!我教程中的架构图都是用它来画的。 比如我编写的RDB工作原理图就是用draw.io绘制的,如下图所示: 再例如Redis集群故障恢复原理图我也是通过draw.io工具绘制的,如下图所示: 是不是觉得draw.io绘制的图形特别简洁、美观。它的官网是: https://www.drawio.com dra