Java HotSwap Ⅱ-ClassLoader

Java HotSwap Ⅱ-ClassLoader
1.前言
   本篇用代码示例和详细注解循序渐进讲解了Java HotSwap的核心技术-ClassLoader.

2.ClassLoder#loadClass示例

package  com.mavsplus.example.java.classloader;

/**
 * <pre>
 *      使用{ @link  java.lang.ClassLoader#loadClass(String)}
 * 1.类加载器的根本作用是从字节代码中定义出表示Java类的Class的类对象,
 *     { @link  java.lang.ClassLoader#defineClass}
 * 2.加载Java类时,虚拟机会先检查是否存在与当前类加载器对象关联在一起的名称相同的Class类的对象,如果存在则直接使用该Class对象而不会重新加载
 * 3.如果一个Java类是由某个类加载器对象的defineClass方法定义的则称这个类加载器对象为该Java类的定义类加载器;当使用类加载器的loadClass方法
 *     来加载一个Java类时,称这个类加载器对象为该Java类的初始类加载器。
 * </pre>
 * 
 *  @author  landon
 *  @since  1.7.0_51
 
*/

public  class  LoadClassExample {
    
     //  使用ClassLoader加载类
     public  void  loadClass()  throws  Exception {
        ClassLoader current  =  getClass().getClassLoader();
        Class <?>  clazz  =  current.loadClass( " java.lang.String " );

        Object str  =  clazz.newInstance();

         //  loadClazz:class java.lang.String
        System.out.println( " loadClazz: "  +  str.getClass());
    }

     //  直接使用class
     public  void  directUseClass()  throws  Exception {
        String strInstance  =  java.lang.String. class .newInstance();
        System.out.println( " directUseClazz: "  +  strInstance.getClass());
    }

     public  static  void  main(String[] args)  throws  Exception {
        LoadClassExample example  =  new  LoadClassExample();

        example.loadClass();

        example.directUseClass();
    }
}

3.查看类加载器的层次结构的示例

package  com.mavsplus.example.java.classloader;

/**
 * <pre>
 *     1.由于类加载器本身也是Java类,因为类加载器自身的Java类需要另外的类加载来加载。
 *     2.类加载器只有自身被加载到虚拟机之后才能加载其他的Java类
 *         ->Java平台提供了一个启动类加载器(bootstrap class loader),由原生代码实现。
 *     3.启动类加载器负责加载Java自身的核心类到虚拟机中,在启动类加载器完成初始的加载工作之后,其他继承自ClassLoader类的类加载器可以正常工作
 * </pre>
 * 
 * <pre>
 * 类加载器层次结构 
 *     1.Java平台默认的类加载器结构:从根节点开始依次是启动类加载器,扩展类加载器和系统类加载器
 *     2.类加载器的一个重要特征是所有类加载器对象都可以有一个其作为双亲的类加载器对象{ @link  java.lang.ClassLoader#getParent()}
 *         ->形成一个树状层次结构(landon:个人认为设计同线程组ThreadGroup设计) 
 * 3.扩展类加载器:从特定的路径加载Java平台的扩展库。
 * 4.系统类加载器(system/application,也叫应用类加载器):根据应用程序运行时的类路径CLASSPATH来加载Java类。->
 *         { @link  java.lang.ClassLoader#getSystemClassLoader()}
 * </pre>
 *
 * 
 *  @author  landon
 *  @since  1.7.0_51
 
*/

public  class  ClassLoaderParents {
     //  显示类加载器层次结构
    
//  curLoader:sun.misc.Launcher$AppClassLoader@780324ff
    
//  curLoader:sun.misc.Launcher$ExtClassLoader@16721ee7
    
//  从输出可以看出:因为扩展类加载器(Ext)的双亲类加载器是启动类加载器(顶层,原生代码实现),所以其getParent返回null
     public  void  displayParents() {
        ClassLoader current  =  getClass().getClassLoader();

         while  (current  !=  null ) {
            System.out.println( " curLoader: "  +  current);

            current  =  current.getParent();
        }
    }

     public  static  void  main(String[] args) {
        ClassLoaderParents parents  =  new  ClassLoaderParents();
        parents.displayParents();
    }
}


4.类加载器代理模式

package  com.mavsplus.example.java.classloader;

/**
 * <pre>
 *     类加载器代理模式:
 * 1.ClassLoader的默认实现中,当类加载器对象需要加载一个Java类或者资源时,会先把加载请求代理给双亲类加载器对象来完成。只有在双亲类加载器
 *     无法找到Java类或者资源时,才由当前类加载器对象进行处理。在加载类的过程中,依靠双亲类加载器对象的原因是有些类的加载只有双亲类加载对象才可以完成.
 * 2.ClassLoader类的默认实现采用的是双亲优先的策略
 *     即先由双亲类加载器对象尝试进行加载,找不到的情况下再由当前类加载器对象来尝试加载。而程序可以根据
 * 需要可采用当前类加载优先的策略即由当前类加载器尝试加载,如果找不到的情况下再代理给双亲加载器尝试加载或者根据要求加载的类的名称采取不同的加载策略。
 * </pre>
 * 
 *  @author  landon
 *  @since  1.7.0_51
 
*/

public  class  ClassLoaderProxy {

     public  static  void  main(String[] args)  throws  Exception {
        NoParentClassLoader noParentClassLoader  =  new  NoParentClassLoader();
        noParentClassLoader.testLoad();
    }
}

class  NoParentClassLoader  extends  ClassLoader {
     //  设置parent加载器为null
    
//  正确的做法是如果需要自己覆写findClass方法并通过defineClass来装载类
     public  NoParentClassLoader() {
         super ( null );
    }

     //  尝试加载一个类,因为没有parent代理加载,所以会加载失败
    
//  Exception in thread "main" java.lang.ClassNotFoundException:
    
//  com.mavsplus.example.java.classloader.LoadClassExample
    
//  at java.lang.ClassLoader.findClass(Unknown Source)
    
//  at java.lang.ClassLoader.loadClass(Unknown Source)
    
//  at java.lang.ClassLoader.loadClass(Unknown Source)
    
//  at
    
//  com.mavsplus.example.java.classloader.NoParentClassLoader.testLoad(ClassLoaderProxy.java:40)
    
//  at
    
//  com.mavsplus.example.java.classloader.ClassLoaderProxy.main(ClassLoaderProxy.java:21)
     public  void  testLoad()  throws  Exception {
        loadClass( " com.mavsplus.example.java.classloader.LoadClassExample " );
    }
}


5.自定义创建ClassLoader

package  com.mavsplus.example.java.classloader;

import  java.io.File;
import  java.io.IOException;
import  java.nio.file.Files;
import  java.nio.file.Path;
import  java.nio.file.Paths;

/**
 * 自定义创建ClassLoader
 * 
 * <pre>
 *     1.大部分Java应用程序不需要使用自己的类加载器,依靠Java平台的3个类加载器已经足够了.在绝大多时候,也只有系统类加载器发挥作用。
 *  2.如果程序对加载类的方式有自己特殊的要求,就要创建自己的类加载器。如:a.对Java类的字节码进行特殊的查找和处理:如Java类的字节码文件存放在磁盘上特定的位置
 *  或者远程服务器上;或者字节代码的数据经过了加密处理 b.利用类加载的隔离特性满足特殊的需求.
 * </pre>
 * 
 * <pre>
 *     自定义加载器覆写方法
 *     1.protected Class<?> loadClass(String name, boolean resolve)
 *         封装了默认的双亲类加载器优先的代理模式的实现,第二个参数为true则表示对找到的类进行链接操作,调用resolveClass方法进行链接
 *  2.protected final Class<?> findLoadedClass(String name)
 *      查找已经加载的Java类,比较这些类的初始化类加载器对象和要加载的Java类的名称,如果一致,则直接返回结果
 *  3.protected Class<?> findClass(String name) throws ClassNotFoundException
 *      当通过双亲代理模式无法成功加载Java类时则该方法会被调用,即封装了当前类加载器对象自己的类加载逻辑
 *  4.protected final void resolveClass(Class<?> c)
 *      链接一个定义好的Class的类对象
 * </pre>
 * 
 *  @author  landon
 *  @since  1.7.0_51
 
*/

public  class  CustomClassLoader {

     public  static  void  main(String[] args)  throws  Exception {
         //  landon:local目录为maven工程根目录下自定义的文件夹(not source folder,eclipse source
        
//  folder编译时会被编译到target目录,即在classpath下)
        
//  加载local下的NotInClassPath.class,因为该class是在自定义的一个文件内,不是在classpath下,所以委托给双亲加载也加载不到
        
//  所以最终会调用FileSystemClassLoader#findClass->装载
        FileSystemClassLoader fileSystemClassLoader  =  new  FileSystemClassLoader(Paths.get( " local " ));
        Class <?>  loadClass  =  fileSystemClassLoader.loadClass( " NotInClassPath " );

        Object loadedInstance  =  loadClass.newInstance();

         //  从输出可以看出:确实通过FileSystemClassLoader成功加载了该类.
        
//  class com.mavsplus.example.java.classloader.FileSystemClassLoader
        
//  class NotInClassPath
        System.out.println(loadedInstance.getClass().getClassLoader().getClass());
        System.out.println(loadedInstance.getClass());
    }
}

//  从磁盘上特定的目录加载字节码
//  该类只是简单的读取了字节码的内容,实际上可在读取字节码之后,调用defineClass之前进行很多操作,如解密等
//  另外除了通过磁盘文件或者网络方式加载已有的字节码之外,还可以在类加载器中即时生成所需要的字节码(ASM)工具
class  FileSystemClassLoader  extends  ClassLoader {
     //  java7引入
     private  Path path;

     public  FileSystemClassLoader(Path path) {
         this .path  =  path;
    }

     //  覆写该方法
    @Override
     protected  Class <?>  findClass(String name)  throws  ClassNotFoundException {
         try  {
             byte [] classData  =  getClassData(name);

             //  调用defineClass方法
             return  defineClass(name, classData,  0 , classData.length);
        }  catch  (IOException e) {
             throw  new  ClassNotFoundException();
        }
    }

     //  读取class字节码数据
     private  byte [] getClassData(String className)  throws  IOException {
        Path path  =  className2Path(className);

         return  Files.readAllBytes(path);
    }

     //  将加载的类名转为对应的class文件的路径->在读取class文件以得到字节码的内容
     private  Path className2Path(String className) {
         return  path.resolve(className.replace( ' . ' , File.separatorChar)  +  " .class " );
    }
}

//  改变默认的双亲优先的代理模式-覆写loadClass方法
//  优先使用当前类加载器进行加载->
//  1.findLoadedClass->必须,查找已经加载的Java类
//  2.调用findClass方法先由当前类加载器对象进行查找
//  3.代理给双亲类加载器对象进行查找
class  ParentLastClassLoader  extends  ClassLoader {
    @Override
     protected  Class <?>  loadClass(String name,  boolean  resolve)  throws  ClassNotFoundException {
         //  首先调用该方法进行查找已经加载的Java类
        Class <?>  clazz  =  findLoadedClass(name);

         if  (clazz  !=  null ) {
             return  clazz;
        }

         //  调用当前类加载器对象进行查找
        clazz  =  findClass(name);

         if  (clazz  !=  null ) {
             return  clazz;
        }

         //  代理给parent类加载器
        ClassLoader parent  =  getParent();
         if  (parent  !=  null ) {
             return  parent.loadClass(name);
        }

         return  super .loadClass(name, resolve);
    }
}


6.类加载器的隔离作用示例代码1

package  com.mavsplus.example.java.classloader;

import  java.lang.reflect.Method;
import  java.nio.file.Path;
import  java.nio.file.Paths;

/**
 * 类加载器的隔离作用
 * 
 * <pre>
 *     1.类加载器的一个重要特性就是为它所加载的Java类创建了隔离空间,相当于添加了一个新的名称空间.
 *  2.Java虚拟机判断两个Java类是否相同:a.全名 2.Class类对象的定义类加载器是否相同。
 *      因为相同的字节码如果由不同的类加载器来加载并定义的话,则所得到的Class类对象是不相同的。
 *  3.很多场合都可以使用类加载器的这个特性为虚拟机中同名的Java类创造一个隔离的名称空间:使同名的Java类可以在虚拟机共存。一个典型的场景是:
 *      程序的版本更新。即一个程序存在多个不同的版本;用户即希望使用新版本的程序也希望基于就版本的代码也可以运行,需要两个版本的Java程序同时存在。
 *  ->实现时使用不同的类加载器对象进行加载不同版本的Java类.->不同版本的Java类的字节代码放在不同的路径中
 * </pre>
 * 
 *  @author  landon
 *  @since  1.7.0_51
 
*/

public  class  ClassLoaderIsolate {
     public  static  void  main(String[] args)  throws  Exception {
        ClassLoaderIsolate isolate  =  new  ClassLoaderIsolate();
        isolate.testClassIdentity();
    }

     //  Exception in thread "main" java.lang.reflect.InvocationTargetException
    
//  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    
//  at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    
//  at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    
//  at java.lang.reflect.Method.invoke(Unknown Source)
    
//  at
    
//  com.mavsplus.example.java.classloader.ClassLoaderIsolate.testClassIdentity(ClassLoaderIsolate.java:48)
    
//  at
    
//  com.mavsplus.example.java.classloader.ClassLoaderIsolate.main(ClassLoaderIsolate.java:28)
    
//  Caused by: java.lang.ClassCastException: NotInClassPath cannot be cast to
    
//  NotInClassPath
    
//  at NotInClassPath.setObj(NotInClassPath.java:10)
    
//    6 more
    
//  landon:虽然是从同样的字节代码中创建出来的同名Java类,但是由于定义他们的类加载器不同,这两个Class类的对象仍然是不相等的
     public  void  testClassIdentity()  throws  Exception {
         //  NotInClassPath是在自定义的一个目录local的一个java类.
        
//  local目录为maven工程根目录下自定义的文件夹(not source folder,eclipse source
        
//  folder编译时会被编译到target目录,即在classpath下)
        Path samplePath  =  Paths.get( " local " );
        String clazzName  =  " NotInClassPath " ;

        FileSystemClassLoader loader1  =  new  FileSystemClassLoader(samplePath);
        FileSystemClassLoader loader2  =  new  FileSystemClassLoader(samplePath);

        Class <?>  loadClass1  =  loader1.loadClass(clazzName);
        Object obj1  =  loadClass1.newInstance();

        Class <?>  loadClass2  =  loader2.loadClass(clazzName);
        Object obj2  =  loadClass2.newInstance();

        Method setObjMethod  =  loadClass1.getMethod( " setObj " , Object. class );
         //  相当于obj1.setObj(obj2),obj1和obj是同一个字节码,两个不同的类加载器进行加载的
        setObjMethod.invoke(obj1, obj2);
    }
}


public  class  NotInClassPath {

     private  NotInClassPath obj;

     public  static  void  main(String  args) {
        System.out.println( " I'm not in classpath " );
    }

     public  void  setObj(Object obj) {
         this .obj  =  (NotInClassPath)obj;
    }
}


7.类加载器的隔离作用示例代码2

package  com.mavsplus.example.java.classloader;

import  java.nio.file.Path;
import  java.nio.file.Paths;

/**
 * <pre>
 *     1.利用类加载器的隔离特性为虚拟机中同名的Java类创建一个隔离的名称空间,使得同名的Java类可以在虚拟机中共存。
 *  2.同名Java类需要共存的一个典型场景是程序的版本更新。一个程序可能存在多个不同的版本,用户即希望使用新版本的程序,又希望基于就版本的代码可以继续运行。这就要求两个版本的Java
 *    类在虚拟机中同时存在。
 *  3.如果不使用自定义类加载来划分空间,就只能让旧版本的Java类使用不同的名称,比如正常的类名后添加类似"V1"和“V2”等后缀进行标识。这种做法使用起来很不方便。使用自定义的类加载器就
 *    可以仍然使用相同名称的Java类,在实现时使用不同的类加载器来进行加载不同版本的Java类
 *  4.在一般的程序版本更新中,会保持接口不变,只修改接口的后台实现。
 * </pre>
 * 
 *  @author  landon
 *  @since  1.7.0_51
 
*/
public  class  ClassLoaderIsolate2 {

     /**
     * 版本更新接口
     * 
     *  @author  landon
     *
     
*/
     public  interface  Versionized {
         public  String getVersion();
    }

     //  根据版本获取不同的Version实现
    
//  两个实现分别放在两个目录v1和v2下(非classpath下),因为类加载的隔离作用,使得两个同名的Java类可以共存在虚拟机中.用户可根据所需要的版本号找到对应的Java类
     public  static  Versionized getVersion(String className, String version)  throws  Exception {
        Path versionPath  =  Paths.get( " local " , version);

        FileSystemClassLoader loader  =  new  FileSystemClassLoader(versionPath);
        Class <?>  loadClazz  =  loader.loadClass(className);

         return  (Versionized) loadClazz.newInstance();
    }

     public  static  void  main(String[] args)  throws  Exception {
        String clazzName  =  " RunVersion " ;

         //  v1:1.0.0
        Versionized v1  =  getVersion(clazzName,  " v1 " );
        System.out.println( " v1: "  +  v1.getVersion());

         //  v2:2.0.0
        Versionized v2  =  getVersion(clazzName,  " v2 " );
        System.out.println( " v2: "  +  v2.getVersion());
    }
}

import  com.mavsplus.example.java.classloader.ClassLoaderIsolate2.Versionized;

/**
 * RunVersion实现V1
 * 
 * <pre>
 *     1.Compile:
 *         E:\github\mavsplus-all\mavsplus-examples\local\v1>javac -encoding UTF-8 -cp ../../target/classes RunVersion.java
 * 2.因为RunVersion.java是在mavsplus-examples\local的目录下,没有在classpath下,所以编译时需要指定Versionized接口所在的classpath下
 * 3.编译需要指定编码 UTF-8编码
 * </pre>
 * 
 *  @author  landon
 
*/
public  class  RunVersion  implements  Versionized {

    @Override
     public  String getVersion() {
         return  " RunVersion[V1] " ;
    }
}

import  com.mavsplus.example.java.classloader.ClassLoaderIsolate2.Versionized;

/**
 * RunVersion实现V2
 * 
 * <pre>
 *     1.Compile:
 *         E:\github\mavsplus-all\mavsplus-examples\local\v1>javac -encoding UTF-8 -cp ../../target/classes RunVersion.java
 * 2.因为RunVersion.java是在mavsplus-examples\local的目录下,没有在classpath下,所以编译时需要指定Versionized接口所在的classpath下
 * 3.编译需要指定编码 UTF-8编码
 * </pre>
 * 
 *  @author  landon
 
*/
public  class  RunVersion  implements  Versionized {

    @Override
     public  String getVersion() {
         return  " RunVersion[V2] " ;
    }
}


8.线程上下文类加载器

package  com.mavsplus.example.java.classloader;

/**
 * 线程上下文类加载器
 * 
 * <pre>
 * 1.{ @link  java.lang.Thread#getContextClassLoader()}
 * 2.{ @link  java.lang.Thread#setContextClassLoader(ClassLoader)}
 * 3.如果一个线程在创建之后没有显示的设置其上下文类加载器,则使用其父线程的上下文类加载器作为其上下文类加载器对象。程序启动的第一个线程的上下文类加载器默认是
 *     Java平台的系统类加载器。即默认情况下,通过当前线程的getContextClassLoader方法和使用当前类的getClassLoader方法得到的类加载器是一致的,二者
 * 均为系统类加载器。
 * 4.线程上下文类加载器提供了一种直接的方式在程序的各部分之间共享ClassLoader类对象.
 * 5.适用情况
 *     1.存在两个相互关联的Java类A和B,这两个类必须用同一个类加载器对象来加载,否则会出现内部错误.满足这个需求的做法是用加载类A的ClassLoader类的对象去加载类B,这样则
 *       需要提供额外的方式在加载类A和类B的代码之前传递ClassLoader类的对象。这样会带来额外的复杂性。只要加载类A和类B的代码在同一个线程中运行,使用线程上下文类加载器是最简单的
 *   做法。加载类A时,获取当前使用的ClassLoader类的对象,调用当前线程对象的setContextClassLoader方法把线程上下文类加载器设置为该ClassLoader类的对象。在加载类B
 *   的时候,通过当前线程对象的getContextClassLoader方法来得到之前保存的ClassLoader类的对象,再进行加载即可.
 *  2.线程上下文类加载的重要作用是解决Java平台的服务提供者接口SPI(如JDBC规范)带来的类加载问题。SPI接口相关的类本身作为Java标准库的一部分,是由启动类加载器来加载的.而在SPI的
 *    实现中一部分是在classpath中的,需要由系统类加载器负责加载的,启动类加载器无法把加载的工作代理给系统来加载器来完成,因为启动来加载器是由系统类加载器的祖先.还有一些情况是
 *    某些SPI实现类可能并非在classpath中,需要由自动以的类加载对象来加载完成,启动类加载器更是无法处理这种情况.
 *        -->为了解决这些问题,在加载类似SPI实现类时,使用的是线程上下文类加载器。在默认情况下,程序运行时的线程上下文类加载器是系统类加载器,这样也可以加载classpath中出现的spi
 *    实现类。如果需要使用自定义类加载器来加载spi实现类,可以把当前线程的上下文类加载器设置成能够加载到spi实现类的类加载器对象
 *  3.注意:如果在程序运行中改变了线程上下文类加载器的值,可能会造成spi的实现类无法加载
 * </pre>
 * 
 *  @author  landon
 *  @since  1.7.0_51
 
*/

public  class  ThreadContextClassLoader {
     public  static  void  main(String[] args) {
        ThreadContextClassLoader loader  =  new  ThreadContextClassLoader();

         //  sun.misc.Launcher$AppClassLoader@c387f44
        
//  sun.misc.Launcher$AppClassLoader@c387f44
        
//  从输出可以看出,二者为同一个class_loader
        loader.defaultClassLoader();
        loader.defaultThreadContextClassLoader();
    }

     public  void  defaultThreadContextClassLoader() {
        ClassLoader defaultThreadContextClassLoader  =  Thread.currentThread().getContextClassLoader();
        System.out.println(defaultThreadContextClassLoader);
    }

     public  void  defaultClassLoader() {
        ClassLoader defaultClassLoader  =  getClass().getClassLoader();
        System.out.println(defaultClassLoader);
    }
}


9.Class.forName方法

package  com.mavsplus.example.java.classloader;

/**
 * Class.forName()
 * 
 * <pre>
 * 1.public static Class<?> forName(String name, boolean initialize,
 *                                    ClassLoader loader)
 *     第二个参数表示类是否初始化
 * 2.该方法与ClassLoader的主要区别是前者可以初始化Java类,而后者不行。初始化意味Java类的静态变量会被初始化,同时静态代码块也会被执行。
 * 3.Jdbc4.0以前用其加载数据库驱动,因为用来在静态代码块中添加必要的驱动注册和初始化的逻辑->JDBC4.0以后则不需要,java.sql.DriverManager
 *     支持了使用服务发现机制来自动查找可用的数据库驱动.
 * 4.详细可看"类的生命周期",Load->Link->Init->
 * 5.注意例子不要用错:
 *     如果你在ClassForName(main所在的类)有一个静态代码代码块,即使main中是空实现,那么也会执行静态代码块
 *         --->因为ClassForName是入口类-->main->入口静态函数->执行静态函数的时候,肯定会进行初始化,即会执行静态代码块
 * </pre>
 * 
 *  @author  landon
 *  @since  1.7.0_51
 
*/

public  class  ClassForName {

     public  static  void  main(String[] args)  throws  Exception {
        ClassForName forName  =  new  ClassForName();

         //  从输出可以看出,只有用默认的Class.forName方法才会执行静态代码块
        forName.classForName1VSLoader();
         //  forName.classForName1VSLoader2();
        
//  forName.classForName1VSLoader3();
    }

     public  void  classForName1VSLoader()  throws  Exception {
         //  默认initialize参数为true,即会执行类的初始化
        Class.forName( " com.mavsplus.example.java.classloader.ClassForName$StaticBlock " );
    }

     public  void  classForName1VSLoader2()  throws  Exception {
         //  将initialize参数传为了false,则不会进行类的初始化
        Class.forName( " com.mavsplus.example.java.classloader.ClassForName$StaticBlock " false , Thread.currentThread()
                .getContextClassLoader());
    }

     public  void  classForName1VSLoader3()  throws  Exception {
         //  用ClassLoader load class只会进行load,并不会进行初始化
        ClassLoader loader  =  ClassForName. class .getClassLoader();
        loader.loadClass( " com.mavsplus.example.java.classloader.ClassForName$StaticBlock " );
    }

     private  static  class  StaticBlock {
         static  {
            System.out.println( " StaticBlock.init " );
        }
    }
}


10.利用ClassLoader加载资源

package  com.mavsplus.example.java.classloader;

import  java.io.IOException;
import  java.io.InputStream;
import  java.util.Properties;

/**
 * 使用ClassLoader加载资源
 * 
 * <pre>
 *  1.类加载器还可以加载与Java类相关的资源文件,资源名称可以由多个部分构成.->每个部分之间用"/"分离,如resources/images/logo.gif
 *  2.使用类加载器的好处是可以解决资源文件存放时路径不固定的问题。
 *  3.{ @link  java.lang.ClassLoader#getResource(String)},{ @link  java.lang.ClassLoader#getResourceAsStream(String)}
 *      ->双亲查找优先
 *          ->1.调用双亲类加载器的getResource方法进行查找;如果为null,则通过启动类加载器来查找.
 *            2.如果找不到对应资源,则调用ClassLoader的findResource方法进行查找。(这种实现方式类似于ClassLoader类在加载Java类时默认的双亲优先的代理模式)
 *            3.在ClassLoader类中声明为protected的findResource方法的作用类似于findClass方法。如果类加载器有自定义的资源查找机制,那么需要覆写此方法
 *       landon:加载资源和加载class可以理解为完全一致,classloader加载资源的路径一样.
 *  4.{ @link  java.lang.ClassLoader#getResources(String)}
 *      ->返回所有具有该名称的资源文件
 *  5.{ @link  java.lang.ClassLoader#getSystemResource(String)},{ @link  java.lang.ClassLoader#getSystemResourceAsStream(String)}
 *      ->使用系统类加载器进行加载资源
 *      ->静态方法,实现上是先得到系统类加载器,再调用系统类加载器的对应方法来进行加载。如果当前系统类加载为null,则通过启动类加载器来进行加载。
 *  6.{ @link  java.lang.Class#getResource(String)},{ @link  java.lang.Class#getResourceAsStream(String)}
 *      ->1.首先通过getClassLoader方法,调用相关.
 *        2.如果getClassLoader返回null,则调用ClassLoader类中的getSystemResource/getSystemResourceAsStream方法进行加载
 *                    -->相当于使用系统类加载器来进行加载
 *        3.调用之前,会进行资源名称的转换->
 *            如果名称以"/"开头则会去掉/;否则自动在资源面前加上Class对象所在包的名称.
 * </pre>
 * 
 *  @author  landon
 *  @since  1.7.0_51
 
*/

public  class  ClassLoadResource {

     public  Properties loadConfig()  throws  IOException {
         //  因为编译后loaderInSrcMainResources.properties在根目录下
        
//  maven项目:loader.properties文件在src/main/resources目录
        
//  编译后:在\target\classes_在classpath下,系统类加载器可以直接获取得到
        InputStream input  =  ClassLoader.getSystemResourceAsStream( " loaderInSrcMainResources.properties " );

        Properties properties  =  new  Properties();
        properties.load(input);

         return  properties;
    }

     public  Properties loadConfig2()  throws  IOException {
         //  这个调用的是Class#getResourceAsStream方法
        
//  loaderInPackage.properties存在和该类的同一级目录下
        
//  编译是在:target\classes\com\mavsplus\example\java\classloader
        
//  参数指定loaderInPackage.properties,会自动将class所在包名补全,所以这样方式更灵活一些
        InputStream input  =  getClass().getResourceAsStream( " loaderInPackage.properties " );

        Properties properties  =  new  Properties();
        properties.load(input);

         return  properties;
    }

     public  static  void  main(String[] args)  throws  IOException {
        ClassLoadResource loadResource  =  new  ClassLoadResource();

        Properties properties  =  loadResource.loadConfig();
        System.out.println(properties.get( " loader.name " ));
        
        Properties properties2  =  loadResource.loadConfig2();
        System.out.println(properties2.get( " loader.name " ));
    }
}


loaderInPackage.properties,和 ClassLoadResource在同一级目录
loader.name =  loaderInPackage

loaderInSrcMainResources.properties,在src\main\resources目录
loader.name =  loaderInSrcMainResources


11.ClassLoader类加载路径

package  com.mavsplus.example.java.classloader;

/**
 * classloader类加载路径
 * 
 *  @author  landon
 *  @since  1.8.0_25
 
*/
public  class  ClassLoaderPath {

     public  static  void  main(String[] args) {
        ClassLoaderPath example  =  new  ClassLoaderPath();
        example.printLoadPath();
    }

     public  void  printLoadPath() {
         //  BootStrapClassLoader加载的类路径:sun.boot.class.path/-Xbootclasspath选项指定
        System.out.println(System.getProperty( " sun.boot.class.path " ));
         //  ExtClassLoader加载的类路径:java.ext.dirs/ -Djava.ext.dirs选项指定
        System.out.println(System.getProperty( " java.ext.dirs " ));
         //  AppClassLoader加载的类路径: java.class.path/ -classpath选项指定
        System.out.println(System.getProperty( " java.class.path " ));
    }
}


ps:本篇介绍的例子大部分是来自 《深入理解Java7:核心技术与最佳实践》 ,不过本人增加了很多自己的见解和注释...
     后续会继续深入学习!

你可能感兴趣的:(Java HotSwap Ⅱ-ClassLoader)