读书人

ClassLoader 学习(一)

发布时间: 2012-08-27 21:21:57 作者: rapoo

ClassLoader 学习(1)

?

部分内容转自:http://www.cnblogs.com/realviv/articles/1906110.html

?

    publicstatic void main(String[] args) {System.out.println(System.getProperty("sun.boot.class.path"));System.out.println(System.getProperty("java.ext.dirs"));System.out.println(System.getProperty("java.class.path"));}?

    ?

    程序结果为:

    E:\Myeclipse6.0\jre\lib\rt.jar;E:\Myeclipse 6.0\jre\lib\i18n.jar;E:\Myeclipse6.0\jre\lib\sunrsasign.jar;E:\MyEclipse6.0\jre\lib\JSse.jar;E:\MyEclipse 6.0\jre\lib\jce.jar;E:\MyEclipse6.0\jre\lib\charsets.jar;E:\MyEclipse 6.0\jre\classes

    E:\MyEclipse6.0\jre\lib\ext

    E:\workspace\ClassLoaderDemo\bin

    ?

    在上面的结果中,你可以清晰看见三个ClassLoader分别加载类的路径;也知道为什么我们在编写程序的时候,要把用到的jar包放在工程的classpath下面啦,也知道我们为什么可以不加载java.lang.*包啦!其中java.lang.*就在rt.jar包中;

    ?

    ClassLoader的加载机制

    现在我们设计这种一下Demo:

    packagejava.net;

    publicclass URL {

    privateString path;

    publicURL(String path) {

    this.path= path;

    }

    publicString toString() {

    returnthis.path + " new Path";

    }

    }

    packagejava.net;

    importjava.net.*;

    publicclass TheSameClsDemo {

    publicstatic void main(String[] args) {

    URLurl = new URL("http://www.baidu.com");

    System.out.println(url.toString());

    }

    }

    ?

    在这种情况下,系统会提示我们出现异常,因为我们有两个相同的类,一个是真正的URL,一个是我在上面实现的伪类;出现异常是正常的,因为你想想,如果我们在执行一个applet的时候,程序自己实现了一个String的类覆盖了我们虚拟机上面的真正的String类,那么在这个String里面,不怀好意的人可以任意的实现一些功能;这就造成极不安全的隐患;所以java采用了一种名为“双亲委托”的加载模式;

    ?

    以下是jdk源代码:

    ?

    protectedsynchronized Class<?> loadClass(String name, boolean resolve)

    throwsClassNotFoundException

    {

    //First, check if the class has already been loaded

    Classc = findLoadedClass(name);

    if(c == null) {

    try{

    if(parent != null) {

    c= parent.loadClass(name, false);

    }else {

    c= findBootstrapClass0(name);

    }

    }catch (ClassNotFoundException e) {

    //If still not found, then invoke findClass in order to find theclass.

    c= findClass(name);

    }

    }

    if(resolve) {

    resolveClass(c);

    }

    returnc;

    }

    ?

    ?

    在上面的代码中,我们可以清晰的看见,我们调用一个ClassLoader加载程序的时候,这个ClassLoader会先调用设置好的parentClassLoader来加载这个类,如果parent是null的话,则默认为BootClassLoader类,只有在parent没有找的情况下,自己才会加载,这就避免我们重写一些系统类,来破坏系统的安全;

    ?

    类与它所依赖的类的classloader机制:

    如果一个类是由某个classloader加载,那么这个类依赖的类(非显式的通过某个classloader加载)必须也由该classloader或其父classloader加载,无视子classloader

    ?

    通过thread.getContextClassloader:

    thread.getContextClassloader默认返回AppClassLoader,除非你显式setContextClassloader

    ?

    来看一下jdbc中如何使用classloader:

    一般我们写一个jdbc程序都会这样:

    Class.forName("com.mysql.jdbc.Driver");

    Stringurl ="jdbc:mysql://127.0.0.1/test?useUnicode=true&characterEncoding=utf-8";

    Stringuser =?"root";

    Stringpsw =?"yanyan";

    Connectioncon = DriverManager.getConnection(url,user, psw);

    为什么需要第一句话?

    其实第一句话可以用下面这句话替代:

    com.mysql.jdbc.Driverdriver = new com.mysql.jdbc.Driver();

    其他都不用变化,有人会问,driver对象从来没有用到.对,它的效果就是在调用DriverManager的getConnection方法之前,保证相应的Driver类已经被加载到jvm中,并且完成了类的初始化工作就行了.注意了,如果我们进行如下操作,程序是不能正常运行的,因为这样仅仅使Driver类被装载到jvm中,却没有进行相应的初始化工作。

    com.mysql.jdbc.Driverdriver = null;

    //or:

    ClassLoadercl = new ClassLoader();

    cl.loadClass("com.mysql.jdbc.Driver");

    ?

    我们都知道JDBC是使用Bridge模式进行设计的,DriverManager就是其中的Abstraction,java.sql.Driver是Implementor,com.mysql.jdbc.Driver是Implementor的一个具体实现(请参考GOF的Bridge模式的描述)。大家注意了,前一个Driver是一个接口,后者却是一个类,它实现了前面的Driver接口。

    Bridge模式中,Abstraction—riverManager)是要拥有一个Implementor—river)的引用的,但是我们在使用过程中,并没有将Driver对象注册到DriverManager中去啊,这是怎么回事呢?jdk文档对Driver的描述中有这么一句:

    Whena Driver class is loaded, it should create an instance of itself andregister it with the DriverManager

    哦,原来是com.mysql.jdbc.Driver在装载完后自动帮我们完成了这一步骤。源代码是这样的:

    packagecom.mysql.jdbc

    ?

    publicclass Driver extends NonRegisteringDriver implements java.sql.Driver{

    static{

    try{

    java.sql.DriverManager.registerDriver(newDriver());

    }catch (SQLException E) {

    thrownew RuntimeException("Can't register driver!");

    }

    }

    ?

    publicDriver() throws SQLException {

    //Required for Class.forName().newInstance()

    }

    }

    ?

    再看一下DriverManager.getConnection(url,user, psw);方法:

    ClassLoadercallerCL = DriverManager.getCallerClassLoader();

    getConnection(url,info, callerCL)

    ==>

    if(callerCL==?null){

    ?callerCL= Thread.currentThread().getContextClassLoader();

    }

    上面的意思是:代码意思是如果DriverManager类的类加载器为空的话,就使用当前线程的类加载器。仔细想想,DriverManager在rt.jar包中,它是由JDK的启动类加载器加载的,而启动类加载器是C编写的,所以取得的都是空,再者,使用当前线程类加载器的话,那么交由程序编写者来保证能够加载驱动类。而不至于驱动器类无法加载。非常高明的手段~!?

    ?

    ?

    Class的这种设计引入了一个有趣的模式:

    某个框架制定某个API,而这些api的实现是有其他供应商来提供,为了能让框架类(处于较高层次的classloader)使用api的实现(处于较低层次的classloader)

    通过thread.getContextClassloader是来传递classloader(有时候需要thread.setContextClassloader设置好api实现的classloader),用此classloader.getResources找出所有的api实现的具体类名,再用classloader加载之,此时框架都不需要知道api的实现类的类名就能加载之,程序显示了良好的动态性和可扩展性。

读书人网 >编程

热点推荐