Java學習筆記十二

今日整理記錄內容:
1、類加載器(classLoader)
2、代理類(proxy)

一、類加載器(classLoader)
1、類加載器的作用就是將硬盤中的.class文件中的二進制數據加載進內存並將二進制數據轉換爲字節碼,判斷某個.class文件是否被加載過是基於類加載器的,也就是說不同的類加載器可以加載同一個.class文件,這時候內存中就有同一個類的兩份字節碼。
2、BootStrap—>(Jar/lib/rt.jar) 注意: 這不是一個類,而是C語言編寫的底層代碼
ExtClassLoader—>(Jar/lib/ext/*.jar)
AppClassLoader—>(ClassPath指定的所有jar或目錄)
3、類加載器的向上委託機制:當我們用AppClassLoader加載器加載某個類時,這時AppClassLoader會先委託父類ExtClassLoader加載器去加載,而ExtClassLoader又會委託其父BootStrap去加載,如果BootStrap能加載這個類,就讓BootStrap這個加載器去加載,如果不能就讓其子類ExtClassLoader去加載,同理是否讓AppClassLoader加載。
4、如果A中引用了類B,Java虛擬機將使用加載類A的類加載器來加載類B
還可以直接調用ClassLoadeer.loadClass()方法來指定某個類加載器去加載某個類。
5、自定義類加載器的用處:
當class文件不在ClassPath路徑下默認系統類加載器無法找到該class文件,在這種情況下我們需要實現一個自定義的ClassLoader來加載特定路徑下的class文件生成class對象。
當一個class文件是通過網絡傳輸並且可能會進行相應的加密操作時,需要先對class文件進行相應的解密後再加載到JVM內存中,這種情況下也需要編寫自定義的ClassLoader並實現相應的邏輯。
當需要實現熱部署功能時(一個class文件通過不同的類加載器產生不同class對象從而實現熱部署功能),需要實現自定義ClassLoader的邏輯。
6、類加載器間的關係(並非指繼承關係):
啓動類加載器(BootStrap),由C++實現,沒有父類。
拓展類加載器(ExtClassLoader),由Java語言實現,父類加載器爲null
系統類加載器(AppClassLoader),**由Java語言實現,父類加載器爲**ExtClassLoader
7、注意這裏所指的父類並不是Java繼承關係中的那種父子關係
8、自定義類加載器,父類加載器肯定爲AppClassLoader。
解釋:

自定義加載器類:
public class MyClassLoaderTest extends ClassLoader{
     private String rootDir;

        public MyClassLoaderTest(String rootDir) {
            this.rootDir = rootDir;
        }

        /**
         * 編寫findClass方法的邏輯
         * @param name
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // 獲取類的class文件字節數組
            System.out.println("調用了findClass()");
            byte[] classData = getClassData(name);
            if (classData == null) {
                throw new ClassNotFoundException();
            } else {
                //直接生成class對象
                return defineClass(name, classData, 0, classData.length);
            }
        }

        /**
         * 編寫獲取class文件並轉換爲字節碼流的邏輯
         * @param className
         * @return
         */
        private byte[] getClassData(String className) {
            // 讀取類文件的字節
            String path = classNameToPath(className);
            try {
                InputStream ins = new FileInputStream(path);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int bufferSize = 4096;
                byte[] buffer = new byte[bufferSize];
                int bytesNumRead = 0;
                // 讀取類文件的字節碼
                while ((bytesNumRead = ins.read(buffer)) != -1) {
                    baos.write(buffer, 0, bytesNumRead);
                }
                return baos.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        /**
         * 類文件的完全路徑
         * @param className
         * @return
         */
        private String classNameToPath(String className) {
            return rootDir + File.separatorChar
                    + className.replace('.', File.separatorChar) + ".class";
        }

        public static void main(String[] args) throws IllegalAccessException, InstantiationException{
            MyClassLoaderTest myClazzLoader1 = new MyClassLoaderTest("F:/");
            MyClassLoaderTest myClazzLoader2 = new MyClassLoaderTest("F:/");
                try {
                    //直接調用findClass方法可以避免向上委託機制的發生和讀取內存的過程;
                    Class clazz1 = myClazzLoader1.findClass("com.hbbfxy.ClassTest");
                    Class clazz2 = myClazzLoader2.findClass("com.hbbfxy.ClassTest");
                    //Class clazz3 = myClazzLoader1.findClass("com.hbbfxy.ClassTest"); //這裏會報錯,因爲內存中已經有了myClazzLoader1對象的類對象,報告重複定義錯誤。
                    System.out.println("clazz1:"+clazz1.hashCode());
                    System.out.println("clazz2:"+clazz2.hashCode());
                    //調用loadClass方法會執行向上委託機制和讀取內存過程
                    Class clazz4 = myClazzLoader1.loadClass("com.hbbfxy.ClassTest");
                    Class clazz5 = myClazzLoader2.loadClass("com.hbbfxy.ClassTest");
                    //Class clazz6 = myClazzLoader2.loadClass("com.hbbfxy.ClassTest");//這裏不會報錯,因爲這是從內存中直接取出,沒有向內存中存儲。
                    System.out.println("clazz4:"+clazz4.hashCode());
                    System.out.println("clazz5:"+clazz5.hashCode());
                } catch (ClassNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

        }
}
輸出結果:
調用了findClass()
調用了findClass()
clazz1:118352462
clazz2:1550089733
clazz4:118352462   //因爲之前myClazzLoader1調用findClass()已經將myClazzLoader1類的類對象存儲到內存,所以這裏直接獲取。
clazz5:1550089733 //因爲之前myClazzLoader2調用findClass()已經將myClazzLoader2類的類對象存儲到內存,所以這裏直接獲取。

上述自定義加載器類(摘自深入理解Java類加載器(ClassLoader))

測試類加載器之間的關係:
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        /*獲得Bootstrap類加載器*/
        ClassLoader clazzLoader = System.class.getClassLoader();
        System.out.println("返回null就說明使用的是BootStrap類加載器:"+clazzLoader);

        /*查看自定義加載器的相關加載器之間的關係*/
        MyClassLoaderTest myClazzLoader = new MyClassLoaderTest(" ");
        System.out.println("加載自定義加載器類的加載器:"+MyClassLoaderTest.class.getClassLoader());
        System.out.println("自定義類加載器的父類:"+myClazzLoader.getParent());    
        System.out.println("自定義類加載器的父類的父類:"+myClazzLoader.getParent().getParent());
        System.out.println("自定義類加載器的父類的父類的父類:"+myClazzLoader.getParent().getParent().getParent());
    }
輸出結果:
返回null就說明使用的是BootStrap類加載器:null
加載自定義加載器類的加載器:sun.misc.Launcher$AppClassLoader@73d16e93
自定義類加載器的父類:sun.misc.Launcher$AppClassLoader@73d16e93
自定義類加載器的父類的父類:sun.misc.Launcher$ExtClassLoader@6d06d69c
自定義類加載器的父類的父類的父類:null

二、代理Proxy
JVM動態生成的類所用的接口生成的類所代理的類用的接口一致,這樣保證方法存在主要用於想在調用一個方法的前後自動完成某件事情。
解釋:

動態生成代理類:
1、創建功能擴展方法的接口類
publicinterface Advice{
        void before();
        void after();
    }
2、創建代理框架
    /*生成代理框架*/
    private static Object getProxy(Object target, Advice advice){
        Object proxy = Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // TODO Auto-generated method stub
                advice.before();
                Object retVal = method.invoke(target, args);
                advice.after();
                return retVal;
            }
        });             
        return proxy;
    }
3、使用代理類
        Collection<String> list = (Collection)getProxy(new ArrayList(), new Advice(){
            @Override
            public void before() {
                // TODO Auto-generated method stub
                 System.out.println("執行方法前調用的代碼。");
            }

            @Override
            public void after() {
                // TODO Auto-generated method stub
                 System.out.println("執行方法後調用的代碼。");
            }

        });
        list.add("1");
輸出結果:
執行方法前調用的代碼。
執行方法後調用的代碼。

這裏我們需要明白動態生成代理類需要類加載器和要實現的接口,類加載器和要實現的接口和被代理的類相同。
Object invoke(Object proxy, Method method, Object[] args)三個參數:
proxy: 動態生成的代理類;
method:調用的方法;
args:方法中的參數。
注意:被代理的類一定要有接口。

學習心得:觀看資料–》思考問題–》實踐證明–》整理記錄 = 有思想的技術大牛!

發佈了38 篇原創文章 · 獲贊 9 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章