ClassLoader
什么是类加载器
java类加载器是通过一个类的全限定名来获取描述此类的二进制字节流,来进行加载;
加载器是在虚拟机外部实现的,方便让程序自己决定获取所需要的类。
JAVA有哪些类加载器
对于开发人员来说,java的类加载器有四种,全都继承自抽象类java.lang.ClassLoader:
启动类加载器(Bootstrap ClassLoader)
它是虚拟机的一部分,无法被Java程序直接引用,所以System.class.getClassLoader()结果为null;
负责加载JAVA_HOME\lib目录中规定的类库,例如rt.jar的java.lang.object等核心api。
扩展类加载器(Extension ClassLoader)
负责加载<JAVA_HOME>\lib\ext目录,可以被开发人员直接使用。
程序类加载器(Application ClassLoader)
程序默认的加载器,负责加载ClassPath下的类或jar,可以被开发人员直接使用。
自定义类加载器
自定义加载器需要继承ClassLoader,可以自己指定需要加载的类,以下是一个自定义加载器的demo:
package classLoader; import java.io.IOException; import java.io.InputStream; /** * 自定义加载器测试类 * */ public class TestClassLoader extends ClassLoader{ public static void main(String[] args) throws Exception{ MyclassLoader myclassLoader = new MyclassLoader(); AObj aObj = (AObj)myclassLoader.loadClass("classLoader.AObj").newInstance(); aObj.sayHello(); } } /** * 自定义加载器类(需继承ClassLoader) */ class MyclassLoader extends ClassLoader{ /** * 自定义加载器有两种方式: * 重写findClass():此种方式遵守了双亲委派模式,符合java设计体系,推荐。 * 重写loadClass():会破坏双亲委派模式,因为双亲委派的逻辑代码就在loadClass方法中,非不得已而为之。 */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] bt = loadClassData(name); //defineClass()是必须的,作用是通过class文件字节数组去获取Class对象 return this.defineClass(name,bt,0,bt.length); } /** * 获取class的字节数组 * @param name * @return */ private byte[] loadClassData(String name) { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceAsStream(fileName); byte[] bt = null; try { bt = new byte[is.available()]; is.read(bt); } catch (IOException e) { e.printStackTrace(); }finally{ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } return bt; } } /** * 用于测试自定义加载器的对象 */ class AObj{ public void sayHello() { System.out.println("Hello Word!"); } }
需要注意的是,即使用自定义加载器也无法加载像java.lang开头这样的类;
不过我并没有做此实验,因为实在是要去洗澡了。
类加载器之间的关系
启动类加载器 > 扩展类加载器 > 程序类加载器 >自定义类加载器:
它们之间不是继承关系,而是通过组合实现的,而这种模型也被称为双亲委派,如下图:
双亲委派模型
概念
双亲委派的代码逻辑在java.lang.ClassLoader.loadClass()中;
他是说某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归;
如果父类加载器可以完成类加载任务,就成功返回;
只有父类加载器无法完成此加载任务时,才自己去加载。
为什么使用双亲委派
因为java的设计体系,比如继承,所有的类都必须继承object类,那就必须确定object是唯一的,否则我们怎么知道该继承哪个object;
而双亲委派会把加载类递归交给上一层加载器去做,如果加载不了,就自己完成,这一确保了类在系统中的唯一性。