<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    I want to fly higher
    programming Explorer
    posts - 114,comments - 263,trackbacks - 0
    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:核心技術與最佳實踐》,不過本人增加了很多自己的見解和注釋...
         后續會繼續深入學習!
    posted on 2015-07-06 20:24 landon 閱讀(6188) 評論(0)  編輯  收藏 所屬分類: JVM 、HotSwap 、ClassLoader
    主站蜘蛛池模板: 一级做a爰全过程免费视频毛片| 国产高清对白在线观看免费91 | 亚洲AV无码男人的天堂| 亚洲免费一区二区| 国产福利视精品永久免费| 又大又粗又爽a级毛片免费看| 亚洲av午夜福利精品一区| 亚洲国产欧洲综合997久久| 国产午夜成人免费看片无遮挡 | 免费在线视频你懂的| 亚洲国产精品成人久久蜜臀| 亚洲特级aaaaaa毛片| 人成免费在线视频| 国产免费久久精品99re丫y| 在线亚洲人成电影网站色www| 久久精品亚洲AV久久久无码 | 成年免费大片黄在线观看岛国 | 久99精品视频在线观看婷亚洲片国产一区一级在线 | 日韩a毛片免费观看| 69堂人成无码免费视频果冻传媒| 亚洲视频在线免费| 国产日本亚洲一区二区三区| 中文字幕在线免费播放| 免费看大黄高清网站视频在线| 亚洲AV无码久久精品狠狠爱浪潮| 久久人午夜亚洲精品无码区| 一级毛片在线观看免费| 国产一级特黄高清免费大片| 亚洲欧洲春色校园另类小说| 中文字幕av免费专区| 国产极品美女高潮抽搐免费网站| 91久久亚洲国产成人精品性色| 久久嫩草影院免费看夜色| 日韩免费高清一级毛片在线| 亚洲最大黄色网址| 免费的全黄一级录像带| 亚洲综合区小说区激情区| 亚洲综合成人婷婷五月网址| 99久久久国产精品免费牛牛| 亚洲午夜无码久久久久| 免费国产污网站在线观看不要卡|