/*************************************************************************************/
此博客主要是在观看张孝祥老师的教学视频的过程中,自己所做的学习笔记,在此以博客的形式分享出来,方便大家学习,建议大家观看视频,以此笔记作为回顾资料。参考资料张孝祥2010年贺岁视频:Java高新技术 视频连接疯狂Java讲义/***********************************44节视频****************************************/类加载器类加载器作用:类加载器加载硬盘上的或者网络上的class文件到内存中,并进行一定的处理,并为之生成对应的java.lang.Class对象(转化成字节码文件)
java虚拟机可以安装多个类加载器,默认有三个主要的类加载器 BootStrap,ExtClassLoader,AppClassLoader. 类加载器也是类,类加载器也需要加载器加载,优先级最高的加载器是BootStrap,他是内嵌在jvm中的C++代码,不需要类加载器加载,他负责加载比他低一优先级的类加载器
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());//打印出的是AppClassLoaderSystem.out.println(System.class.getClassLoader()); //打印出的是null,其类加载器是BootStrap
类加载器之间的父子关系BootStrap------->ExtClassLoader------->AppClassLoader ( 爷爷------->儿子------->孙子 ) 根类加载器 扩展类加载器 系统类加载器也叫应用类加载器我们可以定义自己的类加载器将自己定义的类加载器,挂在自己定义的类加载器的父类上就可以用自己定义的类加载器加载class文件。。。。。。AppClassLoader------->自己定义的类加载器
BootStrap,ExtClassLoader,AppClassLoader分别负责加载不同路径下的class文件BootStrap ---------Java/jdk/jre/lib/rt.jar ExtClassLoader ---------Java/jdk/jre/lib/ext/*jar AppClassLoader ---------classpath指定的所有jar或者目录
BootStrap 根类加载器 负责加载Java/jdk/jre/lib/路径下的java的核心类
以下代码,可以获得根类加载器所加载的核心类库 // 获取根类加载器所加载的全部URL数组 URL[] urls = sun.misc.Launcher. getBootstrapClassPath().getURLs(); // 遍历、输出根类加载器加载的全部URL for (int i = 0; i < urls.length; i++) { System.out.println(urls[i].toExternalForm()); }完整源文件 源文件
ExtClassLoader 扩展类加载器,负责加载 Java/jdk/jre/lib/ext/*jar 路径下的类,通过这种方式,就可以为java扩展核心类以外的新功能,将我们自己开发的类打包成jar文件,然后放在 Java/jdk/jre/lib/ext/目录下,就可使用我们自己开发的类
AppClassLoader 系统类加载器,也叫应用类加载器,负责加载classpath指定的所有jar或者目录,程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都将以系统类加载器作为父类加载器。
类加载器的委托机制每个加载器加载类时,又先委托给其上级的类加载器加载,当所有祖宗的类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛出ClassNotFound异常,不是再去找发起者加载器的儿子
这样的委托机制便于集中管理,当最底层的MyClassLoader加载器想要加载某个类的时候,交给父类加载器去加载,父类加载器加载了这个类,当有最底层的ItcastClassLoader加载器想要加载MyClassLoader加载器曾经加载过的某个类的时候,同样交给父类加载器去加载,父类加载器发现自己曾经加载过这个类,就直接把这个类拿出来用了。 当Java虚拟机需要加载一个类的时候首先派当前线程的类加载器(可以用setContextClassLoader()为线程指定类加载器)去加载类,如果被加载的A类中引用了B类,则用加载了A类的加载器去加载B类(全盘机制),如果不想用线程的类加载器加载,我们也可以强制指定类加载器,我们也可以直接在代码中用ClassLoader.load()来加载类
类加载器有三个机制委托机制,全盘机制,缓冲机制(已经加载过的Class都会被缓存,以方便下次使用)
以下代码示范了访问JVM的类加载器
// 获取系统类加载器 ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); System.out.println("系统类加载器:" + systemLoader); /* 获取系统类加载器的加载路径——通常由CLASSPATH环境变量指定 如果操作系统没有指定CLASSPATH环境变量,默认以当前路径作为 系统类加载器的加载路径 */ Enumeration<URL> em1 = systemLoader.getResources(""); while(em1.hasMoreElements()) { System.out.println(em1.nextElement()); } // 获取系统类加载器的父类加载器:得到扩展类加载器 ClassLoader extensionLader = systemLoader.getParent(); System.out.println("扩展类加载器:" + extensionLader); System.out.println("扩展类加载器的加载路径:System.getProperty("java.ext.dirs")); System.out.println("扩展类加载器的parent: extensionLader.getParent());//打印的值是null,因为根类加载器不是用java代码实现的
完整源文件 源文件
/*****************************45,46 , 47节视频**************************************/
编写自定义类加载器原理分析:
自定义的类加载器的必须继承ClassLoader,然后重写其findClass方法
loadClass方法与findClass方法:loadClass中封装了寻找父类加载器的方法,同时在这个方法里面调用了findClass方法,当寻找的父类加载器跟爷爷类加载器都无法加载到类时,就会回调findClass方法,所以我们自定义类加载器的时候,只需要重写findClass方法即可,这里体现了模板设计模式的思想
defineClass方法 当在重写的findClass方法里面,得到了.class文件的字节流的时候,就可以用defineClass方法将其转换成字节码
ClassLoader中的其他方法
findSystemClass(String name) 从本地的文件系统即classpath指定的所有jar或者目录装入文件。他在本地文件系统中寻找类文件,如果存在,就是用defineClass()方法将原始字节转换成Class对象,以将文件转换成类;getSystemClassLoader()静态方法,用于返回系统类加载器;getParent()返回类加载器的父类加载器;findLoadedClass(String name)如果java虚拟机已经加载过了名为name的类,则直接返回该类的Class实例,否则返回null,此方法是java类加载机制的缓存机制的体现;
剩余代码编写过程详情看视频 视频链接:视频 感觉视频讲的有点混乱
使用自定义的类加载器,可以实现如下常见的功能执行代码前,自动验证数字签名;根据用户提供的密码解密代码,从而可以实现代码混淆器来避免反编译Class文件;根据用户的需求动态的加载器;根据应用需求把其他的数据以字节码的形式加载到应用中;
有包名的类不能调用无包名的类,子类不能比父类抛出更广泛的异常
关于第48节视频----类加载器的高级问题分析,等看完web视频的servlet的时候,再回过头来看。
/***************************************************************************/
URLClassLoader类
URLClassLoader是ExtClassLoader跟AppClassLoader的父类。URLClassLoader的功能比较强大,它既可以从本地文件系统获取二进制文件来加载类,也可以直接从远程主机获取二进制文件来加载类。
在应用程序中可以直接通过URLClassLoader来加载类,URLClassLoader提供了如下两个构造器URLClassLoader(URL[] urls):使用默认的父类加载器创建一个ClassLoader对象,该对象将从urls所指定的路径来查询并加载类URLClassLoader(URL[] urls,ClassLoader parent):使用指定的父类加载器创建一个ClassLoader对象,他的功能跟前一个构造器相同
一旦得到了URLClassLoader的对象后,就可以调用该对象的loadClass()方法来加载指定的类。下面的程序示范了如何直接从文件系统加载MySQL驱动,并使用该驱动获取数据库的连接。通过这种方式来获取数据库的连接,可以无须将MySQL驱动添加到CLASSPATH环境变量中。
public class URLClassLoaderTest{ private static Connection conn; //定义一个获取数据库连接方法 public static Connection getConn(String url , String user , String pass) throws Exception { if (conn == null) { // 创建一个URL数组 URL[] urls = {new URL("file:mysql-connector-java-3.1.10-bin.jar")}; // 以默认的ClassLoader作为父ClassLoader,创建URLClassLoader URLClassLoader myClassLoader = new URLClassLoader(urls); // 加载MySQL的JDBC驱动,并创建默认实例 Driver driver = (Driver)myClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance(); // 创建一个设置JDBC连接属性的Properties对象 Properties props = new Properties(); // 至少需要为该对象传入user和password两个属性 props.setProperty("user" , user); props.setProperty("password" , pass); // 调用Driver对象的connect方法来取得数据库连接 conn = driver.connect(url , props); } return conn; } public static void main(String[] args)throws Exception { System.out.println(getConn("jdbc:mysql://localhost:3306/mysql" , "root" , "32147")); }}
完整源码地址 源码mysql-connector-java-3.1.10-bin.jar 驱动