Class.forName(String name)方法,到底会触发那个类加载器进行类加载行为?
4.2 在代码中直接调用Class.forName(String name)方法,到底会触发那个类加载器进行类加载行为?
Class.forName(String name)默认会使用调用类的类加载器来进行类加载。
如类 com.example.Outer引用了类 com.example.Inner,则由类 com.example.Outer的定义加载器负责启动类 com.example.Inner的加载过程。
如类 com.example.Outer引用了类 com.example.Inner,则由类 com.example.Outer的定义加载器负责启动类 com.example.Inner的加载过程。
如class.forName()加载驱动jar包时,就违背了类加载机制的默认委派双亲机制。而是采用当前类的加载器去加载。
如class.forName()加载驱动jar包时,就违背了类加载机制的默认委派双亲机制。而是采用当前类的加载器去加载。
如class.forName()加载驱动jar包时,就违背了类加载机制的默认委派双亲机制。而是采用当前类的加载器去加载。
我们直接来分析一下对应的jdk的代码:
- //java.lang.Class.java
- publicstatic Class<?> forName(String className) throws ClassNotFoundException {
- return forName0(className, true, ClassLoader.getCallerClassLoader());
- }
- //java.lang.ClassLoader.java
- // Returns the invoker's class loader, or null if none.
- static ClassLoader getCallerClassLoader() {
- // 获取调用类(caller)的类型
- Class caller = Reflection.getCallerClass(3);
- // This can be null if the VM is requesting it
- if (caller == null) {
- return null;
- }
- // 调用java.lang.Class中本地方法获取加载该调用类(caller)的ClassLoader
- return caller.getClassLoader0();
- }
- //java.lang.Class.java
- //虚拟机本地实现,获取当前类的类加载器,前面介绍的Class的getClassLoader()也使用此方法
- native ClassLoader getClassLoader0();
5 开发自己的类加载器
在前面介绍类加载器的代理委派模式的时候,提到过类加载器会首先代理给其它类加载器来尝试加载某个类。这就意味着真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。真正完成类的加载工作是通过调用defineClass来实现的;而启动类的加载过程是通过调用loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在Java虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。如类 com.example.Outer引用了类 com.example.Inner,则由类 com.example.Outer的定义加载器负责启动类 com.example.Inner的加载过程。
方法 loadClass()抛出的是 java.lang.ClassNotFoundException异常;方法 defineClass()抛出的是 java.lang.NoClassDefFoundError异常。
类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass方法不会被重复调用。
在绝大多数情况下,系统默认提供的类加载器实现已经可以满足需求。但是在某些情况下,您还是需要为应用开发出自己的类加载器。比如您的应用通过网络来传输Java类的字节代码,为了保证安全性,这些字节代码经过了加密处理。这个时候您就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在Java虚拟机中运行的类来。下面将通过两个具体的实例来说明类加载器的开发。
5.1 文件系统类加载器
第一个类加载器用来加载存储在文件系统上的Java字节代码。完整的实现如下所示。
- package classloader;
- import java.io.ByteArrayOutputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStream;
- // 文件系统类加载器
- public class FileSystemClassLoader extends ClassLoader {
- private String rootDir;
- public FileSystemClassLoader(String rootDir) {
- this.rootDir = rootDir;
- }
- // 获取类的字节码
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- byte[] classData = getClassData(name); // 获取类的字节数组
- if (classData == null) {
- throw new ClassNotFoundException();
- } else {
- return defineClass(name, classData, 0, classData.length);
- }
- }
- private byte[] getClassData(String className) {
- // 读取类文件的字节
- String path = classNameToPath(className);
- try {
- InputStream ins = new FileInputStream(path);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- int bufferSize = 4096;
- byte[] buffer = new byte[bufferSize];
- int bytesNumRead = 0;
- // 读取类文件的字节码
- while ((bytesNumRead = ins.read(buffer)) != -1) {
- baos.write(buffer, 0, bytesNumRead);
- }
- return baos.toByteArray();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return null;
- }
- private String classNameToPath(String className) {
- // 得到类文件的完全路径
- return rootDir + File.separatorChar
- + className.replace('.', File.separatorChar) + ".class";
- }
- }
如上所示,类 FileSystemClassLoader继承自类java.lang.ClassLoader。在java.lang.ClassLoader类的常用方法中,一般来说,自己开发的类加载器只需要覆写 findClass(String name)方法即可。java.lang.ClassLoader类的方法loadClass()封装了前面提到的代理模式的实现。该方法会首先调用findLoadedClass()方法来检查该类是否已经被加载过;如果没有加载过的话,会调用父类加载器的loadClass()方法来尝试加载该类;如果父类加载器无法加载该类的话,就调用findClass()方法来查找该类。因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写 loadClass()方法,而是覆写 findClass()方法。
类 FileSystemClassLoader的 findClass()方法首先根据类的全名在硬盘上查找类的字节代码文件(.class 文件),然后读取该文件内容,最后通过defineClass()方法来把这些字节代码转换成 java.lang.Class类的实例。
加载本地文件系统上的类,示例如下:
- package com.example;
- public class Sample {
- private Sample instance;
- public void setSample(Object instance) {
- System.out.println(instance.toString());
- this.instance = (Sample) instance;
- }
- }
- package classloader;
- import java.lang.reflect.Method;
- public class ClassIdentity {
- public static void main(String[] args) {
- new ClassIdentity().testClassIdentity();
- }
- public void testClassIdentity() {
- String classDataRootPath = "C:\\Users\\JackZhou\\Documents\\NetBeansProjects\\classloader\\build\\classes";
- FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
- FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
- String className = "com.example.Sample";
- try {
- Class<?> class1 = fscl1.loadClass(className); // 加载Sample类
- Object obj1 = class1.newInstance(); // 创建对象
- Class<?> class2 = fscl2.loadClass(className);
- Object obj2 = class2.newInstance();
- Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);
- setSampleMethod.invoke(obj1, obj2);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
运行输出:com.example.Sample@7852e922
5.2 网络类加载器
下面将通过一个网络类加载器来说明如何通过类加载器来实现组件的动态更新。即基本的场景是:Java 字节代码(.class)文件存放在服务器上,客户端通过网络的方式获取字节代码并执行。当有版本更新的时候,只需要替换掉服务器上保存的文件即可。通过类加载器可以比较简单的实现这种需求。
类 NetworkClassLoader负责通过网络下载Java类字节代码并定义出Java类。它的实现与FileSystemClassLoader类似。
- package classloader;
- import java.io.ByteArrayOutputStream;
- import java.io.InputStream;
- import java.net.URL;
- public class NetworkClassLoader extends ClassLoader {
- private String rootUrl;
- public NetworkClassLoader(String rootUrl) {
- // 指定URL
- this.rootUrl = rootUrl;
- }
- // 获取类的字节码
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- byte[] classData = getClassData(name);
- if (classData == null) {
- throw new ClassNotFoundException();
- } else {
- return defineClass(name, classData, 0, classData.length);
- }
- }
- private byte[] getClassData(String className) {
- // 从网络上读取的类的字节
- String path = classNameToPath(className);
- try {
- URL url = new URL(path);
- InputStream ins = url.openStream();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- int bufferSize = 4096;
- byte[] buffer = new byte[bufferSize];
- int bytesNumRead = 0;
- // 读取类文件的字节
- while ((bytesNumRead = ins.read(buffer)) != -1) {
- baos.write(buffer, 0, bytesNumRead);
- }
- return baos.toByteArray();
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
- private String classNameToPath(String className) {
- // 得到类文件的URL
- return rootUrl + "/"
- + className.replace('.', '/') + ".class";
- }
- }
在通过NetworkClassLoader加载了某个版本的类之后,一般有两种做法来使用它。第一种做法是使用Java反射API。另外一种做法是使用接口。需要注意的是,并不能直接在客户端代码中引用从服务器上下载的类,因为客户端代码的类加载器找不到这些类。使用Java反射API可以直接调用Java类的方法。而使用接口的做法则是把接口的类放在客户端中,从服务器上加载实现此接口的不同版本的类。在客户端通过相同的接口来使用这些实现类。我们使用接口的方式。示例如下:
客户端接口:
- package classloader;
- public interface Versioned {
- String getVersion();
- }
- package classloader;
- public interface ICalculator extends Versioned {
- String calculate(String expression);
- }
网络上的不同版本的类:
- package com.example;
- import classloader.ICalculator;
- public class CalculatorBasic implements ICalculator {
- @Override
- public String calculate(String expression) {
- return expression;
- }
- @Override
- public String getVersion() {
- return "1.0";
- }
- }
- package com.example;
- import classloader.ICalculator;
- public class CalculatorAdvanced implements ICalculator {
- @Override
- public String calculate(String expression) {
- return "Result is " + expression;
- }
- @Override
- public String getVersion() {
- return "2.0";
- }
- }
在客户端加载网络上的类的过程:
- package classloader;
- public class CalculatorTest {
- public static void main(String[] args) {
- String url = "http://localhost:8080/ClassloaderTest/classes";
- NetworkClassLoader ncl = new NetworkClassLoader(url);
- String basicClassName = "com.example.CalculatorBasic";
- String advancedClassName = "com.example.CalculatorAdvanced";
- try {
- Class<?> clazz = ncl.loadClass(basicClassName); // 加载一个版本的类
- ICalculator calculator = (ICalculator) clazz.newInstance(); // 创建对象
- System.out.println(calculator.getVersion());
- clazz = ncl.loadClass(advancedClassName); // 加载另一个版本的类
- calculator = (ICalculator) clazz.newInstance();
- System.out.println(calculator.getVersion());
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
Class.forName(String name)方法,到底会触发那个类加载器进行类加载行为?的更多相关文章
- Junit 注解 类加载器 .动态代理 jdbc 连接池 DButils 事务 Arraylist Linklist hashset 异常 哈希表的数据结构,存储过程 Map Object String Stringbufere File类 文件过滤器_原理分析 flush方法和close方法 序列号冲突问题
Junit 注解 3).其它注意事项: 1).@Test运行的方法,不能有形参: 2).@Test运行的方法,不能有返回值: 3).@Test运行的方法,不能是静态方法: 4).在一个类中,可以同时定 ...
- (转)为什么不能从静态的方法里面调用非静态方法,或变量and类加载机制
1. 程序最终都将在内存中执行,变量只有在内存中占有一席之地时才能被访问. 类的静态成员(变量和方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接去访问:非静态成员(变量和方法)属于类的对 ...
- java.lang.Class.forName(String name, boolean initialize, ClassLoader loader)方法
描述 Java.lang.Class.forName(String name, boolean initialize, ClassLoader loader) 方法返回与给定字符串名的类或接口的Cla ...
- String作为方法参数传递 与 引用传递
String作为方法参数传递 String 和 StringBuffer的区别见这里: http://wenku.baidu.com/view/bb670f2abd64783e09122bcd.htm ...
- 科普:String hashCode 方法为什么选择数字31作为乘子
1. 背景 某天,我在写代码的时候,无意中点开了 String hashCode 方法.然后大致看了一下 hashCode 的实现,发现并不是很复杂.但是我从源码中发现了一个奇怪的数字,也就是本文的主 ...
- String hashCode 方法为什么选择数字31作为乘子
1. 背景 某天,我在写代码的时候,无意中点开了 String hashCode 方法.然后大致看了一下 hashCode 的实现,发现并不是很复杂.但是我从源码中发现了一个奇怪的数字,也就是本文的主 ...
- 【Java-Method】读《重构》有感_Java方法到底是传值调用还是传引用调用(传钥匙调用)
今天读<重构>P279, Separate Query from Modifier,将查询函数和修改函数分离. 问题的产生 突然想到 Java 的传对象作为参数的方法到底是 传引用调用,还 ...
- 【转】String hashCode 方法为什么选择数字31作为乘子
某天,我在写代码的时候,无意中点开了 String hashCode 方法.然后大致看了一下 hashCode 的实现,发现并不是很复杂.但是我从源码中发现了一个奇怪的数字,也就是本文的主角31.这个 ...
- 类加载机制 + Classloader.loadClass(String name)和Class.forName(String name)
Classloader.loadClass(String name)和Class.forName(String name)的区别 Java的类在jvm中的加载大致分为加载,链接或者叫link(里面包含 ...
随机推荐
- kafka介绍与搭建(单机版)
一.kafka介绍 1.1 主要功能 根据官网的介绍,ApacheKafka®是一个分布式流媒体平台,它主要有3种功能: 1:It lets you publish and subscribe to ...
- CVE-2012-0003 Microsoft Windows Media Player ‘winmm.dll’ MIDI文件解析远程代码执行漏洞 分析
[CNNVD]Microsoft Windows Media Player ‘winmm.dll’ MIDI文件解析远程代码执行漏洞(CNNVD-201201-110) Microsoft Wi ...
- day7回顾
静态方法: 与类无关,不能访问类里的任何属性和方法 类方法: 只能访问类变量 属性@property 把一个方法变成一个静态属性 反射 getattr(obj,str) setattr(obj,str ...
- 关于C语言的几个考试编程题目
提交要求:1:邮件名称:学号后三位-题目编号-姓名-期中考试.例如:098-1-沈苗-期中考试2:不用附件提交,直接写邮件,内容包括编程思路(写一段自己对题目的认识.思路.技术细节等).源代码.运行结 ...
- 【LOJ】#2447. 「NOI2011」兔兔与蛋蛋的游戏
题解 对于75分来说,操作肯定不会成环,可以暴搜 看成空格在移动,空格移动到原来的位置肯定经历了偶数个格子,但是操作的人是两个不同的人,所以肯定不会成环 对于满分做法,要找到一种更好的方式判先手是否会 ...
- BZOJ 1036: [ZJOI2008]树的统计Count (树链剖分模板题)
1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 14982 Solved: 6081[Submit ...
- 安卓手机获取IP地址
public class IpGetUtil { public static String getIPAddress(Context context) { NetworkInfo info = ((C ...
- python数据分析之csv/txt数据的导入和保存
约定: import numpy as np import pandas as pd 1 2 3 一.CSV数据的导入和保存 csv数据一般格式为逗号分隔,可在excel中打开展示. 示例 data1 ...
- SQL注入实验
看到他们黑站感觉很有意思的样子,于是我也玩了一下午,虽然都是些狠狠狠简单的东西,不过还是记录下来啦. 虽然和我现在做的没啥关系,不过,,,挺好 浏览器的“工具”——“internet选项”——“高级” ...
- 深入理解ajax系列第八篇
前面的话 在以前,网站的用户与后端交互的主要方式是通过HTML表单的使用.表单的引入在1993年,由于其简单性和易用性,直到电子商务出现之前一直保持着重要位置.理解表单提交,对于更深入地理解ajax是 ...