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

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

相关文章

SpringBoot项目启动后自动加载系统配置的多种实现方式

《SpringBoot项目启动后自动加载系统配置的多种实现方式》:本文主要介绍SpringBoot项目启动后自动加载系统配置的多种实现方式,并通过代码示例讲解的非常详细,对大家的学习或工作有一定的... 目录1. 使用 CommandLineRunner实现方式:2. 使用 ApplicationRunne

SpringBoot项目删除Bean或者不加载Bean的问题解决

《SpringBoot项目删除Bean或者不加载Bean的问题解决》文章介绍了在SpringBoot项目中如何使用@ComponentScan注解和自定义过滤器实现不加载某些Bean的方法,本文通过实... 使用@ComponentScan注解中的@ComponentScan.Filter标记不加载。@C

springboot 加载本地jar到maven的实现方法

《springboot加载本地jar到maven的实现方法》如何在SpringBoot项目中加载本地jar到Maven本地仓库,使用Maven的install-file目标来实现,本文结合实例代码给... 在Spring Boothttp://www.chinasem.cn项目中,如果你想要加载一个本地的ja

最好用的WPF加载动画功能

《最好用的WPF加载动画功能》当开发应用程序时,提供良好的用户体验(UX)是至关重要的,加载动画作为一种有效的沟通工具,它不仅能告知用户系统正在工作,还能够通过视觉上的吸引力来增强整体用户体验,本文给... 目录前言需求分析高级用法综合案例总结最后前言当开发应用程序时,提供良好的用户体验(UX)是至关重要

MyBatis延迟加载的处理方案

《MyBatis延迟加载的处理方案》MyBatis支持延迟加载(LazyLoading),允许在需要数据时才从数据库加载,而不是在查询结果第一次返回时就立即加载所有数据,延迟加载的核心思想是,将关联对... 目录MyBATis如何处理延迟加载?延迟加载的原理1. 开启延迟加载2. 延迟加载的配置2.1 使用

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

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 等话题正确的输出 写在前面的话