您的位置:首页 > 职场人生

黑马程序员——类加载器——委托机制、自定义类加载器

2015-11-07 01:49 405 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

黑马程序员java基础笔记


Java类加载器

1.在谈类加载器前,先说说java中类的加载过程

         jvm将类的加载过程分成3步:装载(load),链接(link)和初始化(initialize)。链接又分为3个步骤,如图:



装载:查找并加载类的二进制数据
链接:
          验证:确保被加载的类的二进制文件的正确性;
         准备:把类的静态文件分配到内存,并将其初始化为默认值;
         解析:把类中的符号引用转换为直接引用;
初始化:为类的静态变量赋予正确的初始值;
         验证的原因:假如是编译器自己产生的”.class”文件当然是符合JVM字节码格式的。但是如果有高手对字节码文件进行了修改,如:对class文件进行了加密处理,这样就没办法了,所以要有验证关卡。
准备和初始化:如果类中有这么一句话:private static int x = 5;它的执行顺序是先将字节码文件加载进内存,进行链接的验证,通过后,到准备阶段,给x分配内存,因为变量x是静态,类型为int,默认值为0,然后直到初始化才把真正的我们要赋予的初始值给x。

2.详解类的初始化:
         在程序运行起来的时候,类什么时候会初始化呢????
1)  创建类的实例(new)
2)  访问某个类或接口的静态变量,或者对该静态变量赋值
3)  调用类的静态方法
4)  反射(Class.forName(“类名”););
5)  初始化一个类的子类(加载子类前,会对父类先初始化)
6)  JVM启动时标明启动的类,即文件名和类名相同的类
上述的6中情况会导致类的初始化;
         类的初始化步骤:
1)  如果这个类还没被加载和链接,那么先进行加载和链接
2)  如果这个类存在直接父类,并且这个类还没有初始化(注:在一个类加载器中,类只能初始化一次),那就初始化直接父类(接口不适用)。
3)  加入类中的存在的初始化语句(如:static变量、static代码块),并依次执行这些初始化语句。
3.类的加载
         类的加载是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的java.lang.Class对象,用来封装类在方法区的对象。
4.类加载器:
 
   系统默认三个主要的类加载器,每个类加载器负责特定位置上的类
BootStrap,ExtClassLoader,AppClassLoader



 
1)  BootStrap ClassLoader
类加载器也是java类,因为其他是java类的加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这是BootStrap。java虚拟机中的所有类转载请采用具有父子关系的树形结构进行组织,在实例化每个类加载器的。
注:BootStrap ClassLoader是C++实现的,不是ClassLoader的子类。
2) Extendsion ClassLoader
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包。
3)  App ClassLoader 
负责记载classpath中指定的jar包及目录中class
4)  My ClassLoader
属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。加载过程中会先检查类是否被已加载,检查顺序是自底向上,从CustomClassLoader到BootStrapClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

类加载器的委托机制:
   首先当前线程的类加载器去加载线程中的第一个类。如果类A中引用了类B,java虚拟机将使用加载类A的类加载器来加载类B还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器器去加载某个类。 每个类加载器加载类时,又先委托给其上级的类加载器(集中管理,保证class文件只有一个),当所有的祖宗类加载器没有加载到类,或到发起者类加载器,还加载不了,抛出ClassNoFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,就算有,可能子类很多,不知道使用哪一个

简单示例:

package com.leaf.classloader;
import java.util.Date;
public class ClassLoaderDemo {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
System.out.println(
ClassLoaderDemo.class.getClassLoader().getClass().getName()
);
System.out.println("---------------开始循环----------------");
ClassLoader loader = ClassLoaderDemo.class.getClassLoader();
while(loader!=null){
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
System.out.println(loader);
System.out.println("---------------循环结束----------------");
}
}


<div style="text-align: left;"></div>

result:



查看程序代码对照结果,我们可以发现AppClassLoader的父类是ExtClassLoader,而ExtClassLoader的父类却是null;那是因为BootStrapClassLoader不是
ClassLoader的子类,是c++代码写的第一个类加载。
先写一个类,当做测试使用,重写toString方法,打印出“Hello”

package com.leaf.classloader;
import java.util.Date;
public class TestType extends Date{
@Override
public String toString() {
return "Hello!!";
}
}
自定义类加载器

package com.leaf.classloader;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/*
* 知识点:
* 	继承  覆盖ClassLoader的findClass/使用IO流得到class文件的转换成字节码,然后解码(一般情况下自定义的ClassLoader都会使用加密的代码对class文件进行加密)-->使用return字眼的时候,把要返回的数据传入defindClass。
*/
public class MyClassLoader extends ClassLoader{
private String classDir;
public MyClassLoader(){}
public MyClassLoader(String classDir){
this.classDir = classDir;
}

public static void main(String[] args) throws IOException {
String srcPath = args[0];
String destDir = args[1];
String destFileName = srcPath.substring(srcPath.lastIndexOf('\\'));
String destFilePath = destDir+"\\"+destFileName;
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destFilePath);
cypher(fis, fos);
System.out.println("加密完毕");
fis.close();
fos.close();
}
//对class文件进行简单加密,即把0-->1,1-->0;
public static void cypher(InputStream is ,OutputStream os) throws IOException{
int b = -1;
while((b=is.read())!=-1){
os.write(b^0xff);
}

}
//重写类加载器中的findClass方法,这样程序加载时,会使用里面的解码文件对class文件解码
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classFileName = classDir+"\\"+name.substring(name.lastIndexOf('.')+1)+".class";
try {
FileInputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream byteos = new ByteArrayOutputStream();
cypher(fis, byteos);
fis.close();
System.out.println(".....我是MyClassLoader.....");
byte[] buf = byteos.toByteArray();
byteos.close();
//这里运行完自定义的加载程序后,返回时会先找父类进行加载,父类加载成功就使用父类加载的结果,没有加载到就会把子类的结果返回
return defineClass(buf, 0, buf.length);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}


1)  假如要自定义加密的class文件的路径,以及要输出的路径
如:  



运行程序:
result:
         运行前:

        


         运行后:





编写一个ClassLoaderDemo2进行自定义ClassLoader文件的使用:

package com.leaf.classloader;
import java.util.Date;
public class ClassLoaderDemo2 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class clazz = new MyClassLoader("leaflib").findClass("com.leaf.classloader.TestType");
Date d1 = (Date)clazz.newInstance();
System.out.println(d1.toString());
}
}


result:

根据前面的以及加密过得TestType.class文件,类加载器加载的class文件使用newInstance方法,调用toString方法,会打印出Hello。



如果我们把leaflib中的TestType.class文件取代E:\Android\workspace\类加载器\bin\com\leaf\classloader中的TestType.class文件,会发送什么呢??



因为class文件被修改了,加载时,会先使用父类的appClassLoader来加载文件,在对应的路径(:\Android\workspace\类加载器\bin\com\leaf\classloader)下找到了TestType.class,进行解析,但是解析失败,报出上述异常。这样间接的说明了要加载一个class文件会先启用父类进行加载,父类加载后子类便不会加载(就算报出异常)。

类加载器使用的委托机制是使用了模板方法设计模式,这里简单讲解一下,方便上述代码的理解:
    概述:定义一个操作中的算法的骨架,而将步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义算法的某些特定步骤。
    
我们使用一个经典的例子:
冲泡咖啡和冲泡茶
加工流程:
咖啡冲泡法:1.把水煮沸、2.用沸水冲泡咖啡、3.把咖啡倒进杯子、4.加糖和牛奶
茶冲泡法:   1.把水煮沸、2.用沸水冲泡茶叶、3.把  茶 倒进杯子、4.加蜂蜜
    定义一个方法的模板(算法的骨架)

package com.leaf.templatedesign;
public abstract class Beverage {
/**
* 冲泡咖啡或茶的流程
*/
public final void create(){
boilWater();//把水煮沸
brew();//用沸水冲泡...
pourInCup();//把...倒进杯子
addCoundiments();//加...
}
public abstract void addCoundiments();
public abstract void brew();
public void boilWater() {
System.out.println("煮开水");
}
public void pourInCup() {
System.out.println("倒进杯子");
}
}


定义一个coffee类,继承Beverage,重写泡咖啡中关键步骤的方法

package com.leaf.templatedesign;

public class Coffee extends Beverage{

@Override
public void addCoundiments() {
System.out.println("添加糖和牛奶");	}
@Override
public void brew() {
System.out.println("用水冲咖啡");
}
}


定义一个Tea类,继承Beverage,重写泡茶中的关键步骤的方法

package com.leaf.templatedesign;

public class Tea extends Beverage{

@Override
public void addCoundiments() {
System.out.println("添加蜂蜜");
}
@Override
public void brew() {
System.out.println("用水冲茶");
}

}


Test测类:

package com.leaf.templatedesign;
public class Test {
public static void main(String[] args) {
System.out.println("--------开始泡coffee--------");
Coffee coffee = new Coffee();
coffee.create();
System.out.println("--------开始泡茶------------");
Tea tea = new Tea();//冲泡茶
tea.create();
}
}


result:



从结果可以看出,我们没有修改父类中的方法,只是把父类中要create一种饮料的流程中的某些方法进行了修改,就能得到我们需要的结果,这就是模板方法设计的最简单的应用。

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息