实现自己的类加载时 重写方法loadClass与findClass的区别
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
Java中的类加载器,有启动类加载器(Bootstrap Classloader)、扩展类加载器(Launcher$ExtClassLoader)、应用程序类加载器(Launcher$AppClassLoader),用户还可以实现自定义的类加载器,见下图:
类加载的这种关系称为双亲委派模式,需要注意的是他们之间不是继承关系,而是组合关系,在执行类加载的动作时,首先都是交给父类去加载,如果父类无法加载再交给子类去完成,直到调用用户自定义的类加载器去加载,如果全部都无法加载,就会抛出ClassNotFoundException。
Launcher$ExtClassLoader和Launcher$AppClassLoader都是URLClassLoader的子类,但是他们的实现又是有一点不同的,Launcher$ExtClassLoader的实现是遵循的双亲委派模型,它重写的是findClass方法,如下:
protected Class findClass(String paramString) throws ClassNotFoundException { DownloadManager.getBootClassPathEntryForClass(paramString); return super.findClass(paramString);}
而Launcher$AppClassLoader的实现是没有遵循双亲委派模型的,它重的是loadClass方法,以下是AppClassLoader中的loadClass的源码:
public synchronized Class loadClass(String paramString, boolean paramBoolean) throws ClassNotFoundException { DownloadManager.getBootClassPathEntryForClass(paramString); int i = paramString.lastIndexOf(46); if (i != -1) { SecurityManager localSecurityManager = System.getSecurityManager(); if (localSecurityManager != null) { localSecurityManager. checkPackageAccess(paramString.substring(0, i)); } } return super.loadClass(paramString, paramBoolean);}
注:以上的源码是通过JD反编译过来的,可能会和实际的源码会有一点点不一样,JDK的版本是HotSpot 6U24。
他们的实现方式为什么会不同呢?因为Launcher$ExtClassLoader加载的类是属于$JRE_HOME/lib/ext下面(也可能通过系统变量java.ext.dir指定路径)的扩展类,Sun公司肯定不会写两个或者多个具有相同全限定名的类、但是功能却不相同的类的,如果真有这样的类存在,那JVM的执行肯定就会有问题了,这在它的控制范围之内的事情。而Launcher$AppClassLoader是用于加载各个不同应用下面的类,同一个JVM中可以同时存在多个应用,如同一个Tomcat中可以同时存在多个Web应用,而这些应用可能是来自不同的开发方,他们之间彼此可能都不知道是谁,但是他们写的类却可能具有相同的全限定名,所以为了确保这些应用之间互不干扰,就需要由各应用的类加载器去加载所属应用的类,这样就不会发生类冲突了。
上面说到了Launcher$ExtClassLoader和应用类加载器(Launcher$AppClassLoader)分别会用于加载哪些类,为了对类加载器有一个完整的认识,下面再介绍启动类加载器(Bootstrap Classloader)会从哪里加载类。
启动类加载器(Bootstrap Classloader),是最先启动的类加载器,默认是负责加载$JRE_HOME/lib目录下面的类,也可以通过JVM参数-Xbootclasspath来指定需要加载类的路径,不过虚拟机为了安全性以及功能的完整性,并不是任何存在于启动类加载器路径下的jar都会被加载,它是通过jar的名字来区分需要加载的类的,如rt.jar等,其它的类即使放在启动类加载器的加载目录下,也是不会被加载的,有兴趣的话可以自己编译一个jar放到$SRE_HOME/lib目录下,然后通过加上JVM参数-XX:+TraceClassLoading任意运行一个java程序,看其中是否有你jar,结论是不会存在的。
前面介绍了启动类加载器、扩展类加载器以及应用类加载器,现在回到正题,实现一个自定义的类加载器,我们可以参考扩展类加载器及应用类加载器,可分别通过重写ClassLoader中的loadClass方法或者findClass方法,但是重写不同的方法,就像扩展类加载器以及应用类加载器一样,要达到的目的是不一样的。
重写loaderClass方法
如果要想在JVM的不同类加载器中保留具有相同全限定名的类,那就要通过重写loadClass来实现,此时首先是通过用户自定义的类加载器来判断该类是否可加载,如果可以加载就由自定义的类加载器进行加载,如果不能够加载才交给父类加载器去加载。
这种情况下,就有可能有大量相同的类,被不同的自定义类加载器加载到JVM中,并且这种实现方式是不符合双亲委派模型。但是不能够说这种实现方式就一定是错误的,有可能当前的场景就需要这样的方式,如容器插件应用场景就适合。
一个插件容器,如下图所示:
要允许不同的插件增加到容器中,就需要采用这种方式,因为我们没有办法保证不同的插件中不能够有相同全限定名的类存在,如A插件中存在了test.Test这么一个类,B插件中也可能会有这么一个相同的类,不能说A插件的test.Test类被加载了,B插件的test.Test就不可再加载了,这就会导致B或/和A插件工作不正常,因为这两个不同的类实现的功能可能完全不同,或者可以说他们之间是一点关系都没有。
但是如果实现自定义类的场景不是类似上面的插件容器场景,最好还是实现findClass,这个也是Sun推荐的实现方式,并且它是符合双亲委派模型的。下面是一个自定义类加载器的实现源码:
ClassLoader myloader = new ClassLoader() { @Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException { try { // 这个getClassInputStream根据情况实现 InputStream is = getClassInputStream(name); if (is == null) { return super.loadClass(name); } byte[] bt = new byte[is.available()]; is.read(bt); return defineClass(name, bt, 0, bt.length); } catch (IOException e) { throw new ClassNotFoundException("Class " + name + " not found."); } }}
注:上面源码中的getClassInputStream(name)方法根据实际情况去实现了,如果要加载的类是在类路径下,这个方法的实现可能是这样的:
String filename = name.replace('.', '/')+".class";InputStream is = getClass().getResourceAsStream(filename);
如果要加载的类不是在类路径下,那可能要通过获取文件流的方式进行加载了,加载的的实现可能是这样的了:
InputStream is = new FileInputStream(new File(name));
也有可能是通过网络获取的,只要能够获取得到就行。
重写findClass方法
重写findClass方法的自定义类,首先会通过父类加载器进行加载,如果所有父类加载器都无法加载,再通过用户自定义的findClass方法进行加载。如果父类加载器可以加载这个类或者当前类已经存在于某个父类的容器中了,这个类是不会再次被加载的,此时用户自定义的findClass方法就不会被执行了。
重写findClass方法是符合双亲委派模式的,它保证了相同全限定名的类是不会被重复加载到JVM中,下面是JDK 6u33中ClassLoader的loadClass方法:
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { 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) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
我们可以看到其中被标红放大的findClass方法,是被悲惨的放在了Catch块中的,不出问题是不需要它出来救火的。而ClassLoader中findClass方法的默认实现是直接抛出ClassNotFoundException异常的,如下代码:
protected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name); }
如果用户没有实现自定义的findClass方法,那只要是执行到了findClass,还是直接抛出异常,那这个方法看起来怪怪的,它有什么目的呢?
这个是有一个历史原因的,因为双亲委派模型是JDK1.2以后才引用进来的,在1.1及以前用户实现自己的类加载器都是通过重写loadClass方法实现,为了兼容原来的实现方式,就选择了增加findClass这么一种妥协的方式。
实现自定义类,源码可以复用上面重写loadClass的实现,只需要将loadClass方法为findClass方法即可。
给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow
实现自己的类加载时 重写方法loadClass与findClass的区别相关推荐
- [C#]关于override和new在重写方法时的区别
规则: 在"运行时"遇到虚方法时,对象会调用虚成员派生得最远的.重写的实现. 如果是用new修饰符实现的方法,它会在基类面前隐藏派生类重新声明的成员,这时候会找到使用new修饰符的 ...
- java方法重载和重写在jvm_重载和重写在jvm运行中的区别(一)
1.重载(overload)方法 对重载方法的调用主要看静态类型,静态类型是什么类型,就调用什么类型的参数方法. 2.重写(override)方法 对重写方法的调用主要看实际类型.实际类型如果实现了该 ...
- 【Android 逆向】Dalvik 函数抽取加壳 ( 类加载流程分析 | ClassLoader#loadClass 函数分析 | BaseDexClassLoader#findClass 分析 )
文章目录 一.ClassLoader.java#loadClass 类加载函数源码分析 二.BaseDexClassLoader.java#findClass 函数源码分析 一.ClassLoader ...
- 用Java Instrumentation 在类加载时添加记录
用Java Instrumentation 在类加载时添加记录 发布者:xanadu0214 来源:网络转载 发布日期:2013年11月06日 Java学习交流群:471651004 在分 ...
- 虚方法、重写方法和抽象方法[转载]
若一个实例方法的声明中含有 virtual 修饰符,则称该方法为虚方法 (virtual method).若其中没有 virtual 修饰符,则称该方法为非虚方法 (non-virtual metho ...
- java 重写方法 访问权限_为何Java中子类重写方法的访问权限不能低于父类中权限(内含里氏替换原则)...
为何Java中子类重写方法的访问权限不能低于父类中权限 因为 向上转型及Java程序设计维护的原因 例: 假设一个父类A 拥有的方法public void setXXX(){}可以被其他任意对象调用这 ...
- js函数重写php,深入讲解js覆盖原有方法 提供重写方法
如果你做了一个编辑器,里面有提供一些光标离开事件等,最好使用此方法来操作,因为当他人使用你的编辑器时,也许要用到编辑器提供的事件方法处理些事情,其中部分事件需要根据特殊要求进行重写,因此,出现这种情况 ...
- 根据父类id查询所有的父级_父类子类抽象类,super final 重写方法,搞懂继承中复杂的知识点...
继承 继承(Inheritance)可以实现类之间共享属性和方法,是面向对象编程的另一个特性 使用继承可以最大限度地实现代码复用. 定义:继承就是在已有类的基础上构建新的类,一个类继承已有类后,可以对 ...
- PHP中类重写方法,php中重写方法有什么规则
php中重写方法的规则有:1.final修饰的类方法不能被子类重写:2.如果要重写父类方法,那么参数个数必须一致:3.在重写时,访问级别只可以等于或大于父类,不可提升访问级别. 方法重写规则: (学习 ...
最新文章
- 应用函数修饰符@来改变AI Studio的输出信息
- 面试官:react和vue有什么区别吗?
- python3精要(18)-函数主要概念
- mysql 同步更新_MySQL slave 不能同步更新
- 兰州大学第一届 飞马杯 ★★快乐苹果树★★ 树链剖分 + 懒标记 + 树状数组
- ssh(Spring+Spring mvc+hibernate)——applicationContext.xml
- jmeter数据库负载测试_JMeter:负载测试关系数据库
- 常用的几种编程语言的介绍
- php 扩展 suhosin 配置不当引发的报错及其解决方法
- Python开发者必知的 11 个 Python GUI 库,你用过几个?
- RTEMS 网络资料的部分翻译
- vue列表渲染中key的作用_vue中:key的作用
- Programer四境界-摘自《代码大全》
- 复联3观影指南丨漫威宇宙里的AI黑科技
- 美国卡尔顿学院计算机专业怎么样,美国留学,选择卡尔顿学院好么?
- 20本书让你迅速跟别人拉开差距
- 每日excel学习之分类汇总和数据有效性
- 测试用例---场景法和错误推测法
- winform怎么实现七天签到_怎么管理多个微信群?怎么提高微信群管理效率?
- Visual Studio 程序集