JVM基础(二) 实现自己的ClassLoader
为何要花时间实现自己的ClassLoader
尽管人生的乐趣非常大一部分来自于将时间花在有意思可是无意义的事情上,可是这件事绝对是有意思并且有意义的,有下面几个情景是值得我们花费时间实现自己的classLoader的:
- 我们须要的类不一定存放在已经设置好的classPath下(有系统类载入器AppClassLoader载入的路径),对于自己定义路径中的class类文件的载入,我们须要自己的ClassLoader
- 有时我们不一定是从类文件里读取类,可能是从网络的输入流中读取类,这就须要做一些加密和解密操作,这就须要自己实现载入类的逻辑,当然其它的特殊处理也相同适用。
- 能够定义类的实现机制。实现类的热部署,如OSGi中的bundle模块就是通过实现自己的ClassLoader实现的。
理解ClassLoader类的结构
载入class文件
ClassLoader的loadClass採用双亲托付型实现。由于我们实现的ClassLoader都继承于java.lang.ClassLoader类,父载入器都是AppClassLoader。所以在上层逻辑中依然要保证该模型,所以一般不覆盖loadClass函数
protected synchronized Class<?> loadClass ( String name , boolean resolve ) throws ClassNotFoundException{
//检查指定类是否被当前类载入器载入过
Class c = findLoadedClass(name);
if( c == null ){//假设没被载入过。委派给父载入器载入
try{
if( parent != null )
c = parent.loadClass(name,resolve);
else
c = findBootstrapClassOrNull(name);
}catch ( ClassNotFoundException e ){
//假设父载入器无法载入
}
if( c == null ){//父类不能载入,由当前的类载入器载入
c = findClass(name);
}
}
if( resolve ){//假设要求马上链接,那么载入完类直接链接
resolveClass();
}
//将载入过这个类对象直接返回
return c;
}
从上面的代码中。我们能够看到在父载入器不能完毕载入任务时,会调用findClass(name)函数,这个就是我们自己实现的ClassLoader的查找类文件的规则。所以在继承后。我们仅仅须要覆盖findClass()这个函数,实现我们在本载入器中的查找逻辑,并且还不会破坏双亲托付模型
载入资源文件(URL)
我们有时会用Class.getResource():URL来获取对应的资源文件。假设仅仅使用上面的ClassLoader是找不到这个资源的,对应的返回值为null。
下面我们来看Class.getResource()的源代码:
public java.net.URL getResource(String name) {
name = resolveName(name);//解析资源
ClassLoader cl = getClassLoader();//获取到当前类的classLoader
if (cl==null) {//假设为空,那么利用系统类载入器载入
// A system class.
return ClassLoader.getSystemResource(name);
}
//假设获取到classLoader,利用指定的classLoader载入资源
return cl.getResource(name);
}
我们发现Class.getResource()是通过托付给ClassLoader的getResource()实现的,所以我们来看classLoader对于资源文件的获取的详细实现例如以下:
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);//这里
}
return url;
}
通过代码我们easy发现。也是双亲委派模型的实现,在不破坏模型的前提下,我们发现我们须要覆写的仅仅是findResource(name)函数
综上
我们在创建自己的ClassLoader时仅仅须要覆写findClass(name)和findResource()就可以
例讲ClassLoader的实现
下面的实现均基于对于ClassLoader抽象类的继承(仅仅给出对于findClass的覆写。由于常理上处理逻辑基本一致)
载入自己定义路径下的class文件
package com.company;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.ByteBuffer;
/**
* Created by liulin on 16-4-20.
*/
public class MyClassLoader extends ClassLoader {
private String classpath;
public MyClassLoader( String classpath){
this.classpath = classpath;
}
@Override
protected Class<?
> findClass(String name) throws ClassNotFoundException {
String fileName = getClassFile( name );
byte[] classByte=null;
try {
classByte = getClassBytes(fileName);
}catch( IOException e ){
e.printStackTrace();
}
//利用自身的载入器载入类
Class retClass = defineClass( null,classByte , 0 , classByte.length);
if( retClass != null ) {
System.out.println("由我载入");
return retClass;
}
//System.out.println("非我载入");
//在classPath中找不到类文件,托付给父载入器载入,父类会返回null,由于可载入的话在
//委派的过程中就已经被载入了
return super.findClass(name);
}
/***
* 获取指定类文件的字节数组
* @param name
* @return 类文件的字节数组
* @throws IOException
*/
private byte [] getClassBytes ( String name ) throws IOException{
FileInputStream fileInput = new FileInputStream(name);
FileChannel channel = fileInput.getChannel();
ByteArrayOutputStream output = new ByteArrayOutputStream();
WritableByteChannel byteChannel = Channels.newChannel(output);
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
int flag;
while ((flag = channel.read(buffer)) != -1) {
if (flag == 0) break;
//将buffer写入byteChannel
buffer.flip();
byteChannel.write(buffer);
buffer.clear();
}
}catch ( IOException e ){
System.out.println("can't read!");
throw e;
}
fileInput.close();
channel.close();
byteChannel.close();
return output.toByteArray();
}
/***
* 获取当前操作系统下的类文件合法路径
* @param name
* @return 合法的路径文件名称
*/
private String getClassFile ( String name ){
//利用StringBuilder将包形式的类名转化为Unix形式的路径
StringBuilder sb = new StringBuilder(classpath);
sb.append("/")
.append ( name.replace('.','/'))
.append(".class");
return sb.toString();
}
public static void main ( String [] args ) throws ClassNotFoundException {
MyClassLoader myClassLoader = new MyClassLoader("/home/liulin/byj");
try {
myClassLoader.loadClass("java.io.InputStream");
myClassLoader.loadClass("TestServer");
myClassLoader.loadClass("noClass");
}catch ( ClassNotFoundException e ){
e.printStackTrace();
}
}
}
结果例如以下:
从结果我们看,由于我们载入的类的父载入器是系统载入器,所以调用双亲托付的loadClass,会直接载入掉java.io.InputStream类。仅仅有在载入双亲中没有的TestServer类,才会用到我们自己的findClass载入逻辑载入指定路径下的类文件,满足双亲委派模型详细前面已经讲述过。不再赘述
热部署和加密解密的ClassLoader实现,大同小异。
仅仅是findClass的逻辑发生改变而已
JVM基础(二) 实现自己的ClassLoader的更多相关文章
- 别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析【JVM篇二】
目录 1.什么是类的加载(类初始化) 2.类的生命周期 3.接口的加载过程 4.解开开篇的面试题 5.理解首次主动使用 6.类加载器 7.关于命名空间 8.JVM类加载机制 9.双亲委派模型 10.C ...
- JVM 基础知识
JVM 基础知识(GC) 2013-12-10 00:16 3190人阅读 评论(1) 收藏 举报 分类: Java(49) 目录(?)[+] 几年前写过一篇关于JVM调优的文章,前段时间拿出来看了看 ...
- JVM(二)Java虚拟机组成详解
导读:详细而深入的总结,是对知识"豁然开朗"之后的"刻骨铭心",想忘记都难. Java虚拟机(Java Virtual Machine)下文简称jvm,上一篇我 ...
- Java面试题总结之Java基础(二)
Java面试题总结之Java基础(二) 1.写clone()方法时,通常都有一行代码,是什么? 答:super.clone(),他负责产生正确大小的空间,并逐位复制. 2.GC 是什么? 为什么要有G ...
- JVM基础系列第15讲:JDK性能监控命令
查看虚拟机进程:jps 命令 jps 命令可以列出所有的 Java 进程.如果 jps 不加任何参数,可以列出 Java 程序的进程 ID 以及 Main 函数短名称,如下所示. $ jps 6540 ...
- 你的 JVM 基础“大厦”稳健吗?
[从 1 开始学 JVM 系列] JVM 对于每位 Java 语言编程者来说无疑是"重中之重",尽管我们每天都在与它打交道,却很少来审视它.了解它,慢慢地,它成为了我们" ...
- Python全栈开发【基础二】
Python全栈开发[基础二] 本节内容: Python 运算符(算术运算.比较运算.赋值运算.逻辑运算.成员运算) 基本数据类型(数字.布尔值.字符串.列表.元组.字典) 其他(编码,range,f ...
- Bootstrap <基础二十九>面板(Panels)
Bootstrap 面板(Panels).面板组件用于把 DOM 组件插入到一个盒子中.创建一个基本的面板,只需要向 <div> 元素添加 class .panel 和 class .pa ...
- Bootstrap <基础二十八>列表组
列表组.列表组件用于以列表形式呈现复杂的和自定义的内容.创建一个基本的列表组的步骤如下: 向元素 <ul> 添加 class .list-group. 向 <li> 添加 cl ...
随机推荐
- BZOJ1067 [SCOI2007]降雨量 RMQ???
求救!!!神犇帮我瞅瞅呗...未完...调了2个半小时线段树,没调出来,大家帮帮我啊!!! 小詹用st表写. 我的思路就是把中间空着的年份设为无限,然后一点点特判就行了...然而没出来... [SCO ...
- 78.员工个人信息保镖页面 Extjs 页面
1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8" ...
- PCB MS SQL 行转列
一.原数据: SELECT inman,indate FROM [fp_db].[dbo].[ppezhpbb] WHERE indate > '2016-5-1' AND indate < ...
- Truck History(prim)
http://poj.org/problem?id=1789 读不懂题再简单也不会做,英语是硬伤到哪都是真理,sad++. 此题就是一个最小生成树,两点之间的权值是毎两串之间的不同字母数. #incl ...
- 0420-mysql命令(数据库操作层级,建表,对表的操作)
注意事项: 符号必须为英文. 数据库操作层级: 建表大全: #新建表zuoye1:drop table if exists zuoye1;create table zuoye1( id int ...
- angular的directive指令的link方法
比如 指令标签 <mylink myLoad="try()"></mylink> link:function(scope,element,attr){ el ...
- POJ 3468 线段树+状压
题意:给你n个数,有对区间的加减操作,问某个区间的和是多少. 思路:状压+线段树(要用lazy标记,否则会TLE) //By SiriusRen #include <cstdio> #in ...
- jquery插件之倒计时-团购秒杀
1.1 帮助文档关键字 倒计时 秒杀 timer 1.2. 使用场景 这样的倒计时在购物网站中会经常使用到,比如秒杀,限时抢购,确认收货倒计时. 这个功能并不难实现,就是利用js的定时执行,搜了 ...
- Assembly之instruction之JUMP
JMP Jump unconditionally Syntax JMP label Operation PC + 2 × offset −> PC Description The 10- ...
- 实验8 标准模板库STL
一.实验目的与要求: 了解标准模板库STL中的容器.迭代器.函数对象和算法等基本概念. 掌握STL,并能应用STL解决实际问题. 二.实验过程: 完成实验8标准模板库STL中练习题,见:http:// ...