JAVA 双亲委派之一

JAVA 双亲委派之一

JVM类加载流程

java语言系统内置了众多类加载器,从一定程度上讲,只存在两种不同的类加载器:一种是启动类加载器,此类加载由C++实现,是JVM的一部分;另一种就是所有其他的类加载器,这些类加载器均由java实现,且全部继承自java.lang.ClassLoader

  1. Bootstrap ClassLoader 启动类加载器,最顶层的加载类,由C++实现,负责加载%JAVA_HOME%/lib目录中或-Xbootclasspath中参数指定的路径中的,并且是虚拟机识别的(按名称)类库
  2. Extention ClassLoader 扩展类加载器,由启动类加载器加载,实现为sun.misc.Launcher$ExtClassLoader,负责加载目录%JRE_HOME%/lib/ext目录中或-Djava.ext.dirs中参数指定的路径中的jar包和class文件
  3. Application ClassLoader 应用类加载器,也称为系统类加载器(System ClassLoader,可由java.lang.ClassLoader.getSystemClassLoader()获取),实现为sun.misc.Launcher$AppClassLoader,由启动类加载器加载,负责加载当前应用classpath下的所有类
双亲委派模型

java语言系统有众多类加载器,包括用户自定义类加载器,各加载器之间的加载顺序如何?首先从JVM入口应用sun.misc.Launcher聊起

Launcher

public Launcher() {ExtClassLoader localExtClassLoader;try {// 加载扩展类加载器localExtClassLoader = ExtClassLoader.getExtClassLoader();} catch (IOException localIOException1) {throw new InternalError("Could not create extension class loader", localIOException1);}try {// 加载应用类加载器this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader);} catch (IOException localIOException2) {throw new InternalError("Could not create application class loader", localIOException2);}// 设置AppClassLoader为线程上下文类加载器Thread.currentThread().setContextClassLoader(this.loader);// ...static class ExtClassLoader extends java.net.URLClassLoaderstatic class AppClassLoader extends java.net.URLClassLoader
}

Launcher初始化了ExtClassLoader和AppClassLoader,并将AppClassLoader设置为线程上下文类加载器,同时,初始化AppClassLoader时传入了ExtClassLoader实例,WHY? 这里要写一个大大的问号ExtClassLoader和AppClassLoader都继承自URLClassLoader,而最终的父类则为ClassLoader。
在这里插入图片描述

查看源码可以得知,初始化AppClassLoader时传入的ExtClassLoader实例最终设置为了AppClassLoader(ClassLoader)的parent属性,parent属性的作用是什么?

父类加载器
每个类都对应一个加载它的类加载器,我们可以通过程序来验证

public class ClassLoaderDemo {public static void main(String[] args) {System.out.println("ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader());System.out.println("DNSNameService's ClassLoader is " + DNSNameService.class.getClassLoader());System.out.println("String's ClassLoader is " + String.class.getClassLoader());}
}

输出为

ClassLodarDemo's ClassLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
DNSNameService's ClassLoader is sun.misc.Launcher$ExtClassLoader@63c12fb0
String's ClassLoader is null

PathClassLoader为我们自己创建的类,其类加载器为AppClassLoader
DNSNameService为%JRE_HOME%/lib/ext目录下的类,其类加载器为ExtClassLoader
String存在于rt.jar中,但其类加载器为null,这里是应为rt.jar由Bootstrap ClassLoader加载,而Bootstrap ClassLoader是由C++编写,属于JVM的一部分

每个类加载器,都有一个父类加载器(parent),同样通过程序验证

public class ClassLoaderDemo {public static void main(String[] args) {System.out.println("PathClassLoader's ClassLoader is " + PathClassLoader.class.getClassLoader());System.out.println("PathClassLoader's ClassLoader is " + PathClassLoader.class.getClassLoader().getParent());System.out.println("PathClassLoader's ClassLoader is " + PathClassLoader.class.getClassLoader().getParent().getParent());}
}

输出:

PathClassLoader's ClassLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
PathClassLoader's ClassLoader is sun.misc.Launcher$ExtClassLoader@63c12fb0
PathClassLoader's ClassLoader is null

AppClassLoader的父类加载器为ExtClassLoader
ExtClassLoader的父类加载器为null,
源码解析

public ExtClassLoader(File[] var1) throws IOException {super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);}// 父类URLClassLoader方法
public URLClassLoader(URL[] urls, ClassLoader parent,URLStreamHandlerFactory factory)

super方法中传参就null

null并不代表ExtClassLoader没有父类加载器,而是Bootstrap ClassLoader

双亲委派

在这里插入图片描述

类加载器在加载类或者其他资源时,使用的是如上图所示的双亲委派模型,这种模型要求除了顶层的BootStrap ClassLoader外,其余的类加载器都应当有自己的父类加载器(父类加载器不是父类继承),如果一个类加载器收到了类加载请求,首先会把这个请求委派给父类加载器加载,只有父类加载器无法完成类加载请求时,子类加载器才会尝试自己去加载。

要理解双亲委派,可以查看ClassLoader.loadClass方法

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 检查是否已经加载过Class<?> c = findLoadedClass(name);if (c == null) { // 没有被加载过long t0 = System.nanoTime();// 首先委派给父类加载器加载try {if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {// 如果父类加载器无法加载,才尝试加载long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}
破坏双亲委派模型

双亲委派模型并不是一个强制性的约束模型,在java的世界中大部分的类加载器都遵循这个模型,但也有例外,一个典型的例子便是JNDI服务。

JNDI存放于rt.jar,由启动类加载器加载,JNDI的目的是对资源进行集中管理和查找,它需要调用由各厂商实现并部署在应用程序ClassPath下的JNDI接口实现的代码,如此一来,在双亲委派模型下,启动类加载器根本无法加载这些代码

针对此问题,java设计团队引入了线程上下文类加载器(Thread Context ClassLoader),这个类加载器可以通过Thread类的setContextClassLoader进行设置,默认继承父线程类加载器
通过线程上下文类加载器,JNDI服务通过此类加载器,由父类加载器请求子类加载器完成类加载动作

破坏双亲委派模型的例子还有很多,如tomcat服务、osgi、jigsaw等等,是否破坏双亲委派模型并没有对与错,只是不同场景下的具体应用而已

自定义ClassLoader

不论是AppClassLoader还是ExtClassLoader还是启动类加载器,其加载类的路径都是固定的,如果我们需要加载外部类或者资源,如某路径下或网络上,这样便需要自定义类加载器
自定义类加载器,只需要继承ClassLoader类,复写findClass方法,在findClass方法中调用defineClass方法即可

一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader
如果需要制定一个ClassLoader的父类加载器为启动类加载器,只需要将其parent指定为null即可

首先,编写一个测试用的类文件

package com.lvyuanj.standard.test;public class MyDefinedClass {public void say() {System.out.println("I'm MyDefinedClass by " + this.getClass().getClassLoader());}
}

将其编译,放入D:\TestXf\classloader目录下接下来,

编写MyDefinedClassLoader

package com.lvyuanj.standard.test;import java.net.URL;
import java.net.URLClassLoader;public class MyDefinedClassLoader extends URLClassLoader {public MyDefinedClassLoader(URL[] urls, ClassLoader parent) {super(urls, parent);}public MyDefinedClassLoader(URL[] urls) {super(urls);}
}

这里直接继承URLClassLoader类,该类findClass的实现如下

protected Class<?> findClass(final String name) throws ClassNotFoundException {final Class<?> result;try {result = AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {public Class<?> run() throws ClassNotFoundException {// 类文件全路径String path = name.replace('.', '/').concat(".class");// 指定资源目录下查找Resource res = ucp.getResource(path, false);if (res != null) {try {// 调用defineClass生成类return defineClass(name, res);} catch (IOException e) {throw new ClassNotFoundException(name, e);}} else {return null;}}}, acc);} catch (java.security.PrivilegedActionException pae) {throw (ClassNotFoundException) pae.getException();}if (result == null) {throw new ClassNotFoundException(name);}return result;
}

编写测试程序

package com.lvyuanj.standard.test;import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;public class MyDefinedClassLoaderDemo {public static void main(String[] args) throws Exception {URL path = new File("D:\\TestXf\\classloader").toURI().toURL();URL[] urls = new URL[1];urls[0] = path;MyDefinedClassLoader myDefinedClassLoaderA = new MyDefinedClassLoader(urls);Class<?> aClass = myDefinedClassLoaderA.loadClass("com.lvyuanj.standard.test.MyDefinedClass");Method sayA = aClass.getMethod("say");Object instanceA = aClass.newInstance();sayA.invoke(instanceA);System.out.println(aClass.getClassLoader().toString());System.out.println("aClass@"+aClass.hashCode());System.out.println("====================");MyDefinedClassLoader myDefinedClassLoaderB = new MyDefinedClassLoader(urls, myDefinedClassLoaderA);Class<?> bClass = myDefinedClassLoaderB.loadClass("com.lvyuanj.standard.test.MyDefinedClass");Method sayB = bClass.getMethod("say");Object instanceB = bClass.newInstance();sayB.invoke(instanceB);System.out.println(myDefinedClassLoaderB);System.out.println("bClass@"+bClass.hashCode());System.out.println("====================");MyDefinedClassLoader myDefinedClassLoaderC = new MyDefinedClassLoader(urls);Class<?> cClass = myDefinedClassLoaderC.loadClass("com.lvyuanj.standard.test.MyDefinedClass");Method sayC = cClass.getMethod("say");Object instanceC = cClass.newInstance();sayC.invoke(instanceC);System.out.println(myDefinedClassLoaderC);System.out.println("cClass@"+cClass.hashCode());System.out.println("====================");System.out.println("aClass == bClass :"+ (aClass == bClass));System.out.println("cClass == bClass :"+ (cClass == bClass));}
}

输出为

I'm MyDefinedClass by com.lvyuanj.standard.test.MyDefinedClassLoader@619a5dff
com.lvyuanj.standard.test.MyDefinedClassLoader@619a5dff
aClass@2117255219
====================
I'm MyDefinedClass by com.lvyuanj.standard.test.MyDefinedClassLoader@619a5dff
com.lvyuanj.standard.test.MyDefinedClassLoader@7ab2bfe1
bClass@2117255219
====================
I'm MyDefinedClass by com.lvyuanj.standard.test.MyDefinedClassLoader@497470ed
com.lvyuanj.standard.test.MyDefinedClassLoader@497470ed
cClass@186276003
====================
aClass == bClass :true
cClass == bClass :false

这里我们定义了3个ClassLoader,类搜索路径均为D:\TestXf\classloader,其中myDefinedClassLoaderB的父类加载器为myDefinedClassLoaderA
aClass bClass cClass 分别由 myDefinedClassLoaderA myDefinedClassLoaderB myDefinedClassLoaderC 加载
instanceA instanceB instanceC 分别由 aClass bClass cClass 创建

从输出可以看出,instanceA及instanceB所对应的class均由myDefinedClassLoaderA加载
虽然myDefinedClassLoaderA与myDefinedClassLoaderB为两个不同的类加载器,但由于myDefinedClassLoaderB的父类加载器为myDefinedClassLoaderA,从输出结果可以看出,aClass与bClass完全相同(包括内存地址),这也说明aClass与bClass均由同一类加载器加载
而instanceC所对应的class,cClass却是由myDefinedClassLoaderC加载

这个例子,能更好的帮助理解双亲委派模型

**注意:如果使用IDEA编辑器的话,一定不要创建MyDefinedClass 这个类,否则sun.misc.Launcher A p p C l a s s L o a d e r 默认的加载器。自定义 C l a s s L o a d e r 加载器,继承 C l a s s L o a d e r ,重写 f i n d C l a s s 指定路径,加载的类一定不要放到本工程目录对应的包下面,否则 f i n d C l a s s 都不会执行,如果 l o a d C l a s s ( " 包名 + 类名 " ),此类会被 s u n . m i s c . L a u n c h e r AppClassLoader默认的加载器。 自定义ClassLoader加载器,继承ClassLoader,重写findClass 指定路径,加载的类一定不要放到本工程目录对应的包下面,否则findClass都不会执行,如果loadClass("包名+类名"),此类会被sun.misc.Launcher AppClassLoader默认的加载器。自定义ClassLoader加载器,继承ClassLoader,重写findClass指定路径,加载的类一定不要放到本工程目录对应的包下面,否则findClass都不会执行,如果loadClass"包名+类名"),此类会被sun.misc.LauncherAppClassLoader默认加载器加载,是可以直接找到此类,导致自定义的findClass不执行。 使用loadClass(“类名”), 找不到才会再执行findClass方法。正好次方法验证了类加载向父类查找建议:需要加载的类,使用IDEA 写好之后,然后把文件copy到其它目录,然后删除本工程下面的的文件
**

类的唯一性

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间如上例,虽然classA(classB)与classC的类路径相同,但由于被不同的类加载器加载,其却属于两个不同的类名称空间

手动打包jar
package com.lvyuanj.standard.model;public class StudentA {private String name;private Integer age;private String address;public StudentA() {System.out.println("--------------->StudentA constructor-----------");}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public void print(){System.out.println("---------------------> excute print method");}
}
package com.lvyuanj.standard.model;public class StuMain {public static void main(String[] args) {StudentA studentA = new StudentA();studentA.print();}
}
手动编码
  1. 执行: javac -encoding utf-8 -d classes com/lvyuanj/standard/model/*.java
打jar包
  1. 创建MANIFEST.MF文件
Manifest-Version: 1.0
Main-Class: com.lvyuanj.standard.model.StuMain

请务必在文件的最后一行,至少加一个回车换行
Manifest-Version 版本号默认为1.0
Main-Class 入口的主类,也就是main方法所在的类
Class-Path 引用外部类的路径

2.打包
在该文件夹下执行:jar -cvfm stu.jar MANIFEST.MF com
jar命令是jdk自带的,可在%JAVA_HOME%\bin中找到
c 创建新档案
v 在标准输出中生成详细输出,有输出信息
f 指定档案文件名
m 包含指定清单文件中的清单信息,也就是指定我们的MANIFEST.MF 如果没有这个参数,会生成一个默认的MANIFEST.MF文件
stu.jar 指定生成jar包的名称

MANIFEST.MF com commons-lang3-3.4.jar 要打包的文件
成功之后会在当前文件夹下生成一个stu.jar
在这里插入图片描述

  1. 运行:
    执行命令: java -jar stu.jar
    输出结果:
--------------->StudentA constructor-----------
---------------------> excute print method
验证ExtClassLoader加载

把以上打包好的stu.jar 包放到 jre/lib/ext 目录下面

public static void main(String args[]) {Class<?> classz = Class.forName("com.lvyuanj.standard.model.StudentA");System.out.println("classload :"+classz.getClassLoader());
}

结果:

classload:sun.misc.Launcher$ExtClassLoader@63c12fb0

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

QT作业5

1、聊天室 服务器端 //头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer> #include <QTcpSocket> #include <QList> #include <QListWidget> #include <QMessageBox> #include <QDebug> #includ…

第十五届蓝桥杯python B组省赛

前言&#xff1a; 这是我第一次参加蓝桥杯&#xff0c;成绩并不理想&#xff0c;我反思了一下午&#xff0c;我的问题主要是知识点学不透&#xff0c;题目做的太少&#xff0c;而且学习的时候少数时间不专心&#xff0c;但是&#xff0c;我能感觉到我的学习能力并不弱&#xf…

用云手机打造海外社媒矩阵

在全球经济一体化的大背景下&#xff0c;中国出海企业及B2B外贸公司正将海外社交媒体营销作为重要的市场拓展策略。为更好地触及不同受众群体&#xff0c;构建跨平台的社媒矩阵已成为企业营销的关键步骤。本文将探讨如何利用云手机技术&#xff0c;高效管理并运营多个海外社交媒…

CSS-页面导航栏实现-每文一言(过有意义的生活,做最好的自己)

&#x1f390;每文一言 过有意义的生活,做最好的自己 目录 &#x1f390;每文一言 &#x1f6d2;盒子模型 &#x1f453;外间距 (margin) &#x1f97c;边框 &#x1f45c;内边距 切换盒子模型计算方案&#xff1a; &#x1f3a2; 浮动布局 浮动特点 &#x1f3c6;导航…

Adobe Photoshop PS 25.6.0 解锁版 (最流行的图像设计软件)

前言 Adobe Photoshop 是一款专业强大的图片处理工具&#xff0c;从照片编辑和合成到数字绘画、动画和图形设计&#xff0c;一流的图像处理和图形设计应用程序是几乎每个创意项目的核心所在。利用 Photoshop 在桌面上的强大功能&#xff0c;您可以在灵感来袭时随时随地进行创作…

推荐3个实用的github开源项目

目录&#xff1a; 1、AI生成高清短视频 2、媒体平台爬虫 3、文本转语音项目

C++对象的拷贝构造函数

如果一个构造函数的第一个参数是类本身的引用,且没有其它参数(或者其它的参数都有默认值),则该构造函数为拷贝构造函数。 拷贝(复制)构造函数:利用同类对象构造一个新的对象 ●1.函数名和类同名 (构造函数) ●2.没有返回值 (构造函数) ●3.第一个参数必…

5.12母亲节营销攻略:TikTok助力出海品牌赢得用户心

母亲节&#xff0c;作为一个全球性的节日&#xff0c;不仅是表达对母亲的感激之情的时刻&#xff0c;也是品牌们展示创意、赢得用户心的黄金机会。2024母亲节将至&#xff0c;如何利用TikTok在母亲节这一特殊时刻进行营销&#xff0c;赢得用户的心&#xff0c;成为出海品牌必须…

Oracle count的优化-避免全表扫描

Oracle count的优化-避免全表扫描 select count(*) from t1; 这句话比较简单&#xff0c;但很有玄机&#xff01;对这句话运行的理解&#xff0c;反映了你对数据库的理解深度&#xff01; 建立实验的大表他t1 SQL> conn scott/tiger 已连接。 SQL> drop table t1 purge…

会话劫持攻击就在我们身边,我们要如何防范

会话劫持攻击&#xff08;Session Hijacking&#xff09;是一种网络攻击方式&#xff0c;攻击者通过某种手段获取到用户的会话标识&#xff08;Session ID&#xff09;&#xff0c;然后使用这个会话标识冒充合法用户进行恶意操作。这种攻击方式允许攻击者以合法用户的身份访问受…

【Linux】Linux——Centos7安装Nginx

不需要安装包 1.安装依赖 #查看 C 环境是否安装gcc -v #查看 zlib 是否安装cat /usr/lib64/pkgconfig/zlib.pc #查看 pcre 是否安装pcre-config --version 2.安装C #安装C yum install gcc-c 3.安装pcre yum install -y pcre pcre-devel 4.安装zlib #安装 yum install -y zlib…

5.11学习记录

20长安杯部分 检材 1 的操作系统版本 CentOS Linux 7.6.1810 (Core) 检材 1 中&#xff0c;操作系统的内核版本是 3.10.0-957.el7.x86_64 检材 1 中磁盘包含一个 LVM 逻辑卷&#xff0c;该 LVM 开始的逻辑区块地址&#xff08;LBA&#xff09;是 2099200 物理卷&#xff…

【go项目01_学习记录10】

操作数据库 1 插入数据2 显示文章2.1 修改 articlesShowHandler() 函数2.2 代码解析 3 编辑文章3.1 添加路由3.2 编辑articlesEditHandler()3.3 新建 edit 模板3.4 代码重构3.5 完善articlesUpdateHandler()3.6 测试更新3.7 封装表单验证 1 插入数据 . . . func articlesStore…

STM32F103学习笔记 | 报错界面及解决方案 | 1.keil5中文注释的横竖(正与斜)问题

文章目录 一、报错界面二、解决方案参考文献 一、报错界面 二、解决方案 打开设置 在打开的设置选项卡中&#xff0c;图中Font显示的是这个软件当前设置的字体&#xff0c;可以看到字体是仿宋&#xff0c;这就是问题出现的原因&#xff0c;将之改成没有的字体就行了。 可以看…

一文了解spring事务特性

推荐工具 objectlog 对于重要的一些数据&#xff0c;我们需要记录一条记录的所有版本变化过程&#xff0c;做到持续追踪&#xff0c;为后续问题追踪提供思路。objectlog工具是一个记录单个对象属性变化的日志工具,工具采用spring切面和mybatis拦截器相关技术编写了api依赖包&a…

【.Net动态Web API】参数模型绑定来源

🚀前言 本文是《.Net Core进阶编程课程》教程专栏的导航站(点击链接,跳转到专栏主页,欢迎订阅,持续更新…) 专栏介绍:通过源码实例来讲解Asp.Net Core进阶知识点,让大家完全掌握每一个知识点。 专栏适用于人群:Web后端开发人员 ———————————————— …

ABB机器人IRB360介绍

随着自动化技术的不断发展&#xff0c;分拣和包装行业的应用也越来越广泛。 工业机器人扮演的角色也随之不断增加&#xff0c;其中ABB机器人的一款产品IRB 360 FlexPicker 在抓取和包装技术方面占有重要的地位。与传统的刚性自动化技术相比较&#xff0c;IRB 360具有高灵活性、…

Navicat Data Modeler Ess for Mac:强大的数据库建模设计软件

Navicat Data Modeler Ess for Mac是一款专为Mac用户设计的数据库建模与设计工具&#xff0c;凭借其强大的功能和直观的界面&#xff0c;帮助用户轻松构建和管理复杂的数据库模型。 Navicat Data Modeler Ess for Mac v3.3.17中文直装版下载 这款软件支持多种数据库系统&#x…

SQL注入(sqli-labs第一关)

sqli-labs第一关 方法一&#xff1a;手工注入 来到第一关&#xff0c;图上说我们需要一个数字的参数 于是我们先手工注入?id1 and 11 跟?id1 and 12发现页面没有报错 每张截图上面页面中有select查询语句&#xff0c;这是我在第一关的源码中加上了echo "$sql ";…

信息技术自主可控的意义,针对国产化替换,服务器虚拟化或比公有云更具优势

我们之前在文章《博通收购VMware后&#xff0c;经销商和用户如何应对&#xff1f;新出路&#xff1a;虚拟化国产替代&#xff0c;融入信创云生态》中提到&#xff1a; 从信创整体发展和政策标准来看&#xff0c;供应商必须满足两个条件&#xff1a;一是融入国产信息技术生态&am…