• classloader 学习


    classloader就是把类文件加载到jvm中供虚拟机使用,先看一个magic小例子:

    首先,我定义一个alex/vicky包,然后在这个包内定义一个接口:

    public interfaceIService {

     voidservice();

    }

    然后使用这个接口定义一个实现类:

    public classService extendsIService{

     @Override

    publicvoidservice(){
    System.out.println("Alex");

    }

    }

    然后把这个类定义删掉,把这个类生成的class文件放到c:/alex/vicky文件夹下.

    再做一个实现类:

    public classMain {

     public static voidmain(String[] args) throwsException {

    URLu = newURL("file:c:/");

    URLClassLoaderucl = newURLClassLoader(newURL[] { u });

    Classc = ucl.loadClass("alex.vicky.Service");

    IServiceobj = (IService) c.newInstance();

    obj.service();

    }

    }

    可以看到,我们通过URL方式,远程加载了类文件.

    一旦一个类被载入JVM中,同一个类就不会被再次载入了(切记,同一个类)。在Java中,一个类用其完全匹配类名(fullyqualified classname)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识。

    java的几种ClassLoader:

    java中,我们可以取得这么以下三个ClassLoader类:

    一. ClassLoader基本概念

    1.ClassLoader分类

    类装载器是用来把类(class)装载进JVM的。

    JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-definedclass loader)。

    JVM在运行时会产生三个ClassLoader:BootstrapClassLoader、ExtensionClassLoader和AppClassLoader。Bootstrap是用C++编写的,我们在Java中看不到它,是Null,是JVM自带的类装载器,用来装载核心类库,如java.lang.*等。

    AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent为BootstrapClassLoader。

    Java提供了抽象类ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。系统类装载器可以通过ClassLoader.getSystemClassLoader()方法得到。

    系统为什么要分别指定这么多的ClassLoader类呢?

    答案在于因为java是动态加载类的,这样的话,可以节省内存,用到什么加载什么,就是这个道理,然而系统在运行的时候并不知道我们这个应用与需要加载些什么类,那么,就采用这种逐级加载的方式

    (1)首先加载核心API,让系统最基本的运行起来

    (2)加载扩展类

    (3)加载用户自定义的类

    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.0jrelib t.jar;E:Myeclipse 6.0jrelibi18n.jar;E:Myeclipse6.0jrelibsunrsasign.jar;E:MyEclipse6.0jrelibJSse.jar;E:MyEclipse 6.0jrelibjce.jar;E:MyEclipse6.0jrelibcharsets.jar;E:MyEclipse 6.0jreclasses

    E:MyEclipse6.0jrelibext

    E:workspaceClassLoaderDemoin

    在上面的结果中,你可以清晰看见三个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(DriverManager)是要拥有一个Implementor(Driver)的引用的,但是我们在使用过程中,并没有将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的实现类的类名就能加载之,程序显示了良好的动态性和可扩展性。

  • 相关阅读:
    log4net
    配置文件序列化到文件中
    log日志
    Quartz任务管理
    tuple
    mvc视图中使用JavaScriptSerializer
    windows服务安装
    23种设计模式之普通工厂模式代码实例
    23种设计模式之单例模式代码实例
    putty文件传输
  • 原文地址:https://www.cnblogs.com/ConfidentLiu/p/7092277.html
Copyright © 2020-2023  润新知