3atv精品不卡视频,97人人超碰国产精品最新,中文字幕av一区二区三区人妻少妇,久久久精品波多野结衣,日韩一区二区三区精品

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

深入理解Java类加载器:Java类加载原理解析

發(fā)布時間:2024/4/17 java 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入理解Java类加载器:Java类加载原理解析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

http://blog.csdn.net/zhoudaxia/article/details/35824249

1 基本信息


  每個開發(fā)人員對java.lang.ClassNotFoundExcetpion這個異常肯定都不陌生,這背后就涉及到了java技術(shù)體系中的類加載。Java的類加載機(jī)制是技術(shù)體系中比較核心的部分,雖然和大部分開發(fā)人員直接打交道不多,但是對其背后的機(jī)理有一定理解有助于排查程序中出現(xiàn)的類加載失敗等技術(shù)問題,對理解java虛擬機(jī)的連接模型和java語言的動態(tài)性都有很大幫助。


2 Java虛擬機(jī)類加載器結(jié)構(gòu)簡述


2.1 JVM三種預(yù)定義類型類加載器


  我們首先看一下JVM預(yù)定義的三種類型類加載器,當(dāng)一個 JVM啟動的時候,Java缺省開始使用如下三種類型類裝入器:

  啟動(Bootstrap)類加載器:引導(dǎo)類裝入器是用本地代碼實(shí)現(xiàn)的類裝入器,它負(fù)責(zé)將 <Java_Runtime_Home>/lib下面的核心類庫或-Xbootclasspath選項(xiàng)指定的jar包加載到內(nèi)存中。由于引導(dǎo)類加載器涉及到虛擬機(jī)本地實(shí)現(xiàn)細(xì)節(jié),開發(fā)者無法直接獲取到啟動類加載器的引用,所以不允許直接通過引用進(jìn)行操作。

  擴(kuò)展(Extension)類加載器:擴(kuò)展類加載器是由SunExtClassLoadersun.misc.Launcher$ExtClassLoader實(shí)現(xiàn)的。它負(fù)責(zé)將< Java_Runtime_Home >/lib/ext或者由系統(tǒng)變量-Djava.ext.dir指定位置中的類庫加載到內(nèi)存中。開發(fā)者可以直接使用標(biāo)準(zhǔn)擴(kuò)展類加載器。

  系統(tǒng)(System)類加載器:系統(tǒng)類加載器是由 Sun AppClassLoadersun.misc.Launcher$AppClassLoader)實(shí)現(xiàn)的。它負(fù)責(zé)將系統(tǒng)類路徑java -classpath或-Djava.class.path變量所指的目錄下的類庫加載到內(nèi)存中。開發(fā)者可以直接使用系統(tǒng)類加載器。

  除了以上列舉的三種類加載器,還有一種比較特殊的類型就是線程上下文類加載器,這個將在后面單獨(dú)介紹。


2.2 類加載雙親委派機(jī)制介紹和分析


?????? 在這里,需要著重說明的是,JVM在加載類時默認(rèn)采用的是雙親委派機(jī)制。通俗的講,就是某個特定的類加載器在接到加載類的請求時,首先將加載任務(wù)委托給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務(wù),就成功返回;只有父類加載器無法完成此加載任務(wù)時,才自己去加載。關(guān)于虛擬機(jī)默認(rèn)的雙親委派機(jī)制,我們可以從系統(tǒng)類加載器和擴(kuò)展類加載器為例作簡單分析。

圖一 標(biāo)準(zhǔn)擴(kuò)展類加載器繼承層次圖

圖二系統(tǒng)類加載器繼承層次圖

通過圖一和圖二我們可以看出,類加載器均是繼承自java.lang.ClassLoader抽象類。我們下面我們就看簡要介紹一下java.lang.ClassLoader中幾個最重要的方法

[java] view plaincopyprint?
  • //加載指定名稱(包括包名)的二進(jìn)制類型,供用戶調(diào)用的接口?
  • public Class<?> loadClass(String name) throws ClassNotFoundException{ … }?
  • ?
  • //加載指定名稱(包括包名)的二進(jìn)制類型,同時指定是否解析(但是這里的resolve參數(shù)不一定真正能達(dá)到解析的效果),供繼承用?
  • protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ … }?
  • ?
  • //findClass方法一般被loadClass方法調(diào)用去加載指定名稱類,供繼承用?
  • protected Class<?> findClass(String name) throws ClassNotFoundException { … }?
  • ?
  • //定義類型,一般在findClass方法中讀取到對應(yīng)字節(jié)碼后調(diào)用,可以看出不可繼承?
  • //(說明:JVM已經(jīng)實(shí)現(xiàn)了對應(yīng)的具體功能,解析對應(yīng)的字節(jié)碼,產(chǎn)生對應(yīng)的內(nèi)部數(shù)據(jù)結(jié)構(gòu)放置到方法區(qū),所以無需覆寫,直接調(diào)用就可以了)?
  • protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{ … }?
  • //加載指定名稱(包括包名)的二進(jìn)制類型,供用戶調(diào)用的接口 public Class<?> loadClass(String name) throws ClassNotFoundException{ … }//加載指定名稱(包括包名)的二進(jìn)制類型,同時指定是否解析(但是這里的resolve參數(shù)不一定真正能達(dá)到解析的效果),供繼承用 protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ … }//findClass方法一般被loadClass方法調(diào)用去加載指定名稱類,供繼承用 protected Class<?> findClass(String name) throws ClassNotFoundException { … }//定義類型,一般在findClass方法中讀取到對應(yīng)字節(jié)碼后調(diào)用,可以看出不可繼承 //(說明:JVM已經(jīng)實(shí)現(xiàn)了對應(yīng)的具體功能,解析對應(yīng)的字節(jié)碼,產(chǎn)生對應(yīng)的內(nèi)部數(shù)據(jù)結(jié)構(gòu)放置到方法區(qū),所以無需覆寫,直接調(diào)用就可以了) protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{ … }

      通過進(jìn)一步分析標(biāo)準(zhǔn)擴(kuò)展類加載器(sun.misc.Launcher$ExtClassLoader)和系統(tǒng)類加載器(sun.misc.Launcher$AppClassLoader)的代碼以及其公共父類(java.net.URLClassLoaderjava.security.SecureClassLoader)的代碼可以看出,都沒有覆寫java.lang.ClassLoader中默認(rèn)的加載委派規(guī)則---loadClass)方法。既然這樣,我們就可以通過分析java.lang.ClassLoader中的loadClassString name)方法的代碼就可以分析出虛擬機(jī)默認(rèn)采用的雙親委派機(jī)制到底是什么模樣:

    [java] view plaincopyprint?
  • public Class<?> loadClass(String name) throws ClassNotFoundException {?
  • ??? return loadClass(name, false);?
  • }?
  • ?
  • protected synchronized Class<?> loadClass(String name, boolean resolve)?
  • ??????? throws ClassNotFoundException {?
  • ?
  • ??? // 首先判斷該類型是否已經(jīng)被加載?
  • ??? Class c = findLoadedClass(name);?
  • ??? if (c == null) {?
  • ??????? //如果沒有被加載,就委托給父類加載或者委派給啟動類加載器加載?
  • ??????? try {?
  • ??????????? if (parent != null) {?
  • ??????????????? //如果存在父類加載器,就委派給父類加載器加載?
  • ??????????????? c = parent.loadClass(name, false);?
  • ??????????? } else {?
  • ??????????????? //如果不存在父類加載器,就檢查是否是由啟動類加載器加載的類,?
  • ??????????????? //通過調(diào)用本地方法native findBootstrapClass0(String name)?
  • ??????????????? c = findBootstrapClass0(name);?
  • ??????????? }?
  • ??????? } catch (ClassNotFoundException e) {?
  • ??????????? // 如果父類加載器和啟動類加載器都不能完成加載任務(wù),才調(diào)用自身的加載功能?
  • ??????????? c = findClass(name);?
  • ??????? }?
  • ??? }?
  • ??? if (resolve) {?
  • ??????? resolveClass(c);?
  • ??? }?
  • ??? return c;?
  • }?
  • public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);}protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {// 首先判斷該類型是否已經(jīng)被加載Class c = findLoadedClass(name);if (c == null) {//如果沒有被加載,就委托給父類加載或者委派給啟動類加載器加載try {if (parent != null) {//如果存在父類加載器,就委派給父類加載器加載c = parent.loadClass(name, false);} else {//如果不存在父類加載器,就檢查是否是由啟動類加載器加載的類,//通過調(diào)用本地方法native findBootstrapClass0(String name)c = findBootstrapClass0(name);}} catch (ClassNotFoundException e) {// 如果父類加載器和啟動類加載器都不能完成加載任務(wù),才調(diào)用自身的加載功能c = findClass(name);}}if (resolve) {resolveClass(c);}return c;}

      通過上面的代碼分析,我們可以對JVM采用的雙親委派類加載機(jī)制有了更感性的認(rèn)識,下面我們就接著分析一下啟動類加載器、標(biāo)準(zhǔn)擴(kuò)展類加載器和系統(tǒng)類加載器三者之間的關(guān)系。可能大家已經(jīng)從各種資料上面看到了如下類似的一幅圖片:


    圖三 類加載器默認(rèn)委派關(guān)系圖

      上面圖片給人的直觀印象是系統(tǒng)類加載器的父類加載器是標(biāo)準(zhǔn)擴(kuò)展類加載器,標(biāo)準(zhǔn)擴(kuò)展類加載器的父類加載器是啟動類加載器,下面我們就用代碼具體測試一下:

    [java] view plaincopyprint?
  • public class LoaderTest {?
  • ?
  • ??? public static void main(String[] args) {?
  • ??????? try {?
  • ??????????? System.out.println(ClassLoader.getSystemClassLoader());?
  • ??????????? System.out.println(ClassLoader.getSystemClassLoader().getParent());?
  • ??????????? System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());?
  • ??????? } catch (Exception e) {?
  • ??????????? e.printStackTrace();?
  • ??????? }?
  • ??? }?
  • }?
  • public class LoaderTest {public static void main(String[] args) {try {System.out.println(ClassLoader.getSystemClassLoader());System.out.println(ClassLoader.getSystemClassLoader().getParent());System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());} catch (Exception e) {e.printStackTrace();}} }

      說明:通過java.lang.ClassLoader.getSystemClassLoader()可以直接獲取到系統(tǒng)類加載器。
      代碼輸出如下:

    [plain] view plaincopyprint?
  • sun.misc.Launcher$AppClassLoader@6d06d69c?
  • sun.misc.Launcher$ExtClassLoader@70dea4e?
  • null?
  • sun.misc.Launcher$AppClassLoader@6d06d69c sun.misc.Launcher$ExtClassLoader@70dea4e null

      通過以上的代碼輸出,我們可以判定系統(tǒng)類加載器的父加載器是標(biāo)準(zhǔn)擴(kuò)展類加載器,但是我們試圖獲取標(biāo)準(zhǔn)擴(kuò)展類加載器的父類加載器時確得到了null,就是說標(biāo)準(zhǔn)擴(kuò)展類加載器本身強(qiáng)制設(shè)定父類加載器為null我們還是借助于代碼分析一下。

      我們首先看一下java.lang.ClassLoader抽象類中默認(rèn)實(shí)現(xiàn)的兩個構(gòu)造函數(shù):

    [java] view plaincopyprint?
  • protected ClassLoader() {?
  • ??? SecurityManager security = System.getSecurityManager();?
  • ??? if (security != null) {?
  • ??????? security.checkCreateClassLoader();?
  • ??? }?
  • ??? //默認(rèn)將父類加載器設(shè)置為系統(tǒng)類加載器,getSystemClassLoader()獲取系統(tǒng)類加載器?
  • ??? this.parent = getSystemClassLoader();?
  • ??? initialized = true;?
  • }?
  • ?
  • protected ClassLoader(ClassLoader parent) {?
  • ??? SecurityManager security = System.getSecurityManager();?
  • ??? if (security != null) {?
  • ??????? security.checkCreateClassLoader();?
  • ??? }?
  • ??? //強(qiáng)制設(shè)置父類加載器?
  • ??? this.parent = parent;?
  • ??? initialized = true;?
  • }?
  • protected ClassLoader() {SecurityManager security = System.getSecurityManager();if (security != null) {security.checkCreateClassLoader();}//默認(rèn)將父類加載器設(shè)置為系統(tǒng)類加載器,getSystemClassLoader()獲取系統(tǒng)類加載器this.parent = getSystemClassLoader();initialized = true;}protected ClassLoader(ClassLoader parent) {SecurityManager security = System.getSecurityManager();if (security != null) {security.checkCreateClassLoader();}//強(qiáng)制設(shè)置父類加載器this.parent = parent;initialized = true;}

      我們再看一下ClassLoader抽象類中parent成員的聲明:

    [java] view plaincopyprint?
  • // The parent class loader for delegation?
  • private ClassLoader parent;?
  • // The parent class loader for delegation private ClassLoader parent;

      聲明為私有變量的同時并沒有對外提供可供派生類訪問的public或者protected設(shè)置器接口(對應(yīng)的setter方法),結(jié)合前面的測試代碼的輸出,我們可以推斷出:
      1. 系統(tǒng)類加載器(AppClassLoader)調(diào)用ClassLoader(ClassLoader parent)構(gòu)造函數(shù)將父類加載器設(shè)置為標(biāo)準(zhǔn)擴(kuò)展類加載器(ExtClassLoader)。(因?yàn)槿绻粡?qiáng)制設(shè)置,默認(rèn)會通過調(diào)用getSystemClassLoader()方法獲取并設(shè)置成系統(tǒng)類加載器,這顯然和測試輸出結(jié)果不符。)
      2. 擴(kuò)展類加載器(ExtClassLoader)調(diào)用ClassLoader(ClassLoader parent)構(gòu)造函數(shù)將父類加載器設(shè)置為null。(因?yàn)槿绻粡?qiáng)制設(shè)置,默認(rèn)會通過調(diào)用getSystemClassLoader()方法獲取并設(shè)置成系統(tǒng)類加載器,這顯然和測試輸出結(jié)果不符。)
      現(xiàn)在我們可能會有這樣的疑問:擴(kuò)展類加載器(ExtClassLoader)的父類加載器被強(qiáng)制設(shè)置為null了,那么擴(kuò)展類加載器為什么還能將加載任務(wù)委派給啟動類加載器呢?

    圖四 標(biāo)準(zhǔn)擴(kuò)展類加載器和系統(tǒng)類加載器成員大綱視圖


    圖五 擴(kuò)展類加載器和系統(tǒng)類加載器公共父類成員大綱視圖

      通過圖四和圖五可以看出,標(biāo)準(zhǔn)擴(kuò)展類加載器和系統(tǒng)類加載器及其父類(java.net.URLClassLoader和java.security.SecureClassLoader)都沒有覆寫java.lang.ClassLoader中默認(rèn)的加載委派規(guī)則---loadClass(…)方法。有關(guān)java.lang.ClassLoader中默認(rèn)的加載委派規(guī)則前面已經(jīng)分析過,如果父加載器為null,則會調(diào)用本地方法進(jìn)行啟動類加載嘗試。所以,圖三中,啟動類加載器、標(biāo)準(zhǔn)擴(kuò)展類加載器和系統(tǒng)類加載器之間的委派關(guān)系事實(shí)上是仍就成立的。(在后面的用戶自定義類加載器部分,還會做更深入的分析)。


    2.3 類加載雙親委派示例


      以上已經(jīng)簡要介紹了虛擬機(jī)默認(rèn)使用的啟動類加載器、標(biāo)準(zhǔn)擴(kuò)展類加載器和系統(tǒng)類加載器,并以三者為例結(jié)合JDK代碼對JVM默認(rèn)使用的雙親委派類加載機(jī)制做了分析。下面我們就來看一個綜合的例子。首先在IDE中建立一個簡單的java應(yīng)用工程,然后寫一個簡單的JavaBean如下:

    [java] view plaincopyprint?
  • package classloader.test.bean;?
  • ?
  • public class TestBean {?
  • ?????
  • ??? public TestBean() { }?
  • }?
  • package classloader.test.bean;public class TestBean {public TestBean() { } }

      在現(xiàn)有當(dāng)前工程中另外建立一測試類(ClassLoaderTest.java)內(nèi)容如下:

      測試一:

    [java] view plaincopyprint?
  • package classloader.test.bean;?
  • ?
  • public class ClassLoaderTest {?
  • ?
  • ??? public static void main(String[] args) {?
  • ??????? try {?
  • ??????????? //查看當(dāng)前系統(tǒng)類路徑中包含的路徑條目?
  • ??????????? System.out.println(System.getProperty("java.class.path"));?
  • ??????????? //調(diào)用加載當(dāng)前類的類加載器(這里即為系統(tǒng)類加載器)加載TestBean?
  • ??????????? Class typeLoaded = Class.forName("classloader.test.bean.TestBean");?
  • ??????????? //查看被加載的TestBean類型是被那個類加載器加載的?
  • ??????????? System.out.println(typeLoaded.getClassLoader());?
  • ??????? } catch (Exception e) {?
  • ??????????? e.printStackTrace();?
  • ??????? }?
  • ??? }?
  • }?
  • package classloader.test.bean;public class ClassLoaderTest {public static void main(String[] args) {try {//查看當(dāng)前系統(tǒng)類路徑中包含的路徑條目System.out.println(System.getProperty("java.class.path"));//調(diào)用加載當(dāng)前類的類加載器(這里即為系統(tǒng)類加載器)加載TestBeanClass typeLoaded = Class.forName("classloader.test.bean.TestBean");//查看被加載的TestBean類型是被那個類加載器加載的System.out.println(typeLoaded.getClassLoader());} catch (Exception e) {e.printStackTrace();}} }

      對應(yīng)的輸出如下:

    [plain] view plaincopyprint?
  • C:\Users\JackZhou\Documents\NetBeansProjects\ClassLoaderTest\build\classes?
  • sun.misc.Launcher$AppClassLoader@73d16e93?
  • C:\Users\JackZhou\Documents\NetBeansProjects\ClassLoaderTest\build\classes sun.misc.Launcher$AppClassLoader@73d16e93

      說明:當(dāng)前類路徑默認(rèn)的含有的一個條目就是工程的輸出目錄。
      測試二:

      將當(dāng)前工程輸出目錄下的TestBean.class打包進(jìn)test.jar剪貼<Java_Runtime_Home>/lib/ext目錄下(現(xiàn)在工程輸出目錄下和JRE擴(kuò)展目錄下都有待加載類型的class文件)。再運(yùn)行測試一測試代碼,結(jié)果如下:

    [plain] view plaincopyprint?
  • C:\Users\JackZhou\Documents\NetBeansProjects\ClassLoaderTest\build\classes?
  • sun.misc.Launcher$ExtClassLoader@15db9742?
  • C:\Users\JackZhou\Documents\NetBeansProjects\ClassLoaderTest\build\classes sun.misc.Launcher$ExtClassLoader@15db9742

      對比測試一和測試二,我們明顯可以驗(yàn)證前面說的雙親委派機(jī)制,系統(tǒng)類加載器在接到加載classloader.test.bean.TestBean類型的請求時,首先將請求委派給父類加載器(標(biāo)準(zhǔn)擴(kuò)展類加載器),標(biāo)準(zhǔn)擴(kuò)展類加載器搶先完成了加載請求。
      測試三:
      將test.jar拷貝一份到<Java_Runtime_Home>/lib下,運(yùn)行測試代碼,輸出如下:

    [plain] view plaincopyprint?
  • C:\Users\JackZhou\Documents\NetBeansProjects\ClassLoaderTest\build\classes?
  • sun.misc.Launcher$ExtClassLoader@15db9742?
  • C:\Users\JackZhou\Documents\NetBeansProjects\ClassLoaderTest\build\classes sun.misc.Launcher$ExtClassLoader@15db9742

      測試三和測試二輸出結(jié)果一致。那就是說,放置到<Java_Runtime_Home>/lib目錄下的TestBean對應(yīng)的class字節(jié)碼并沒有被加載,這其實(shí)和前面講的雙親委派機(jī)制并不矛盾。虛擬機(jī)出于安全等因素考慮,不會加載<Java_Runtime_Home>/lib存在的陌生類,開發(fā)者通過將要加載的非JDK自身的類放置到此目錄下期待啟動類加載器加載是不可能的。做個進(jìn)一步驗(yàn)證,刪除<Java_Runtime_Home>/lib/ext目錄下和工程輸出目錄下的TestBean對應(yīng)的class文件,然后再運(yùn)行測試代碼,則將會有ClassNotFoundException異常拋出。有關(guān)這個問題,大家可以在java.lang.ClassLoader中的loadClass(String name, boolean resolve)方法中設(shè)置相應(yīng)斷點(diǎn)運(yùn)行測試三進(jìn)行調(diào)試,會發(fā)現(xiàn)findBootstrapClass0()會拋出異常,然后在下面的findClass方法中被加載,當(dāng)前運(yùn)行的類加載器正是擴(kuò)展類加載器(sun.misc.Launcher$ExtClassLoader),這一點(diǎn)可以通過JDT中變量視圖查看驗(yàn)證。


    3 java程序動態(tài)擴(kuò)展方式


      Java的連接模型允許用戶運(yùn)行時擴(kuò)展引用程序,既可以通過當(dāng)前虛擬機(jī)中預(yù)定義的加載器加載編譯時已知的類或者接口,又允許用戶自行定義類裝載器,在運(yùn)行時動態(tài)擴(kuò)展用戶的程序。通過用戶自定義的類裝載器,你的程序可以裝載在編譯時并不知道或者尚未存在的類或者接口,并動態(tài)連接它們并進(jìn)行有選擇的解析。
      運(yùn)行時動態(tài)擴(kuò)展java應(yīng)用程序有如下兩個途徑:


    3.1 調(diào)用java.lang.Class.forName(…)加載類


      這個方法其實(shí)在前面已經(jīng)討論過,在后面的問題2解答中說明了該方法調(diào)用會觸發(fā)哪個類加載器開始加載任務(wù)。這里需要說明的是多參數(shù)版本的forName(…)方法:

    [java] view plaincopyprint?
  • public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException?
  • public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException

      這里的initialize參數(shù)是很重要的。它表示在加載同時是否完成初始化的工作(說明:單參數(shù)版本的forName方法默認(rèn)是完成初始化的)。有些場景下需要將initialize設(shè)置為true來強(qiáng)制加載同時完成初始化。例如典型的就是利用DriverManager進(jìn)行JDBC驅(qū)動程序類注冊的問題。因?yàn)槊恳粋€JDBC驅(qū)動程序類的靜態(tài)初始化方法都用DriverManager注冊驅(qū)動程序,這樣才能被應(yīng)用程序使用。這就要求驅(qū)動程序類必須被初始化,而不單單被加載。Class.forName的一個很常見的用法就是在加載數(shù)據(jù)庫驅(qū)動的時候。如 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()用來加載 Apache Derby 數(shù)據(jù)庫的驅(qū)動。


    3.2 用戶自定義類加載器


      通過前面的分析,我們可以看出,除了和本地實(shí)現(xiàn)密切相關(guān)的啟動類加載器之外,包括標(biāo)準(zhǔn)擴(kuò)展類加載器和系統(tǒng)類加載器在內(nèi)的所有其他類加載器我們都可以當(dāng)做自定義類加載器來對待,唯一區(qū)別是是否被虛擬機(jī)默認(rèn)使用。前面的內(nèi)容中已經(jīng)對java.lang.ClassLoader抽象類中的幾個重要的方法做了介紹,這里就簡要敘述一下一般用戶自定義類加載器的工作流程吧(可以結(jié)合后面問題解答一起看):
      1、首先檢查請求的類型是否已經(jīng)被這個類裝載器裝載到命名空間中了,如果已經(jīng)裝載,直接返回;否則轉(zhuǎn)入步驟2;
      2、委派類加載請求給父類加載器(更準(zhǔn)確的說應(yīng)該是雙親類加載器,真實(shí)虛擬機(jī)中各種類加載器最終會呈現(xiàn)樹狀結(jié)構(gòu)),如果父類加載器能夠完成,則返回父類加載器加載的Class實(shí)例;否則轉(zhuǎn)入步驟3;
      3、調(diào)用本類加載器的findClass(…)方法,試圖獲取對應(yīng)的字節(jié)碼,如果獲取的到,則調(diào)用defineClass(…)導(dǎo)入類型到方法區(qū);如果獲取不到對應(yīng)的字節(jié)碼或者其他原因失敗,返回異常給loadClass(…), loadClass(…)轉(zhuǎn)而拋異常,終止加載過程(注意:這里的異常種類不止一種)。
      說明:這里說的自定義類加載器是指JDK 1.2以后版本的寫法,即不覆寫改變java.lang.loadClass(…)已有委派邏輯情況下。

      整個加載類的過程如下圖:

    圖六 自定義類加載器加載類的過程


    4 常見問題分析


    4.1 由不同的類加載器加載的指定類還是相同的類型嗎?


      在Java中,一個類用其完全匹配類名(fully qualified class name)作為標(biāo)識,這里指的完全匹配類名包括包名和類名。但在JVM中一個類用其全名和一個加載類ClassLoader的實(shí)例作為唯一標(biāo)識,不同類加載器加載的類將被置于不同的命名空間。我們可以用兩個自定義類加載器去加載某自定義類型(注意不要將自定義類型的字節(jié)碼放置到系統(tǒng)路徑或者擴(kuò)展路徑中,否則會被系統(tǒng)類加載器或擴(kuò)展類加載器搶先加載),然后用獲取到的兩個Class實(shí)例進(jìn)行java.lang.Object.equals(…)判斷,將會得到不相等的結(jié)果。這個大家可以寫兩個自定義的類加載器去加載相同的自定義類型,然后做個判斷;同時,可以測試加載java.*類型,然后再對比測試一下測試結(jié)果。


    4.2 在代碼中直接調(diào)用Class.forName(String name)方法,到底會觸發(fā)那個類加載器進(jìn)行類加載行為?


      Class.forName(String name)默認(rèn)會使用調(diào)用類的類加載器來進(jìn)行類加載。我們直接來分析一下對應(yīng)的jdk的代碼:

    [java] view plaincopyprint?
  • //java.lang.Class.java?
  • publicstatic Class<?> forName(String className) throws ClassNotFoundException {?
  • ??? return forName0(className, true, ClassLoader.getCallerClassLoader());?
  • }?
  • ?
  • //java.lang.ClassLoader.java?
  • // Returns the invoker's class loader, or null if none.?
  • static ClassLoader getCallerClassLoader() {?
  • ??? // 獲取調(diào)用類(caller)的類型?
  • ??? Class caller = Reflection.getCallerClass(3);?
  • ??? // This can be null if the VM is requesting it?
  • ??? if (caller == null) {?
  • ??????? return null;?
  • ??? }?
  • ??? // 調(diào)用java.lang.Class中本地方法獲取加載該調(diào)用類(caller)的ClassLoader?
  • ??? return caller.getClassLoader0();?
  • }?
  • ?
  • //java.lang.Class.java?
  • //虛擬機(jī)本地實(shí)現(xiàn),獲取當(dāng)前類的類加載器,前面介紹的Class的getClassLoader()也使用此方法?
  • native ClassLoader getClassLoader0();?
  • //java.lang.Class.javapublicstatic Class<?> forName(String className) throws ClassNotFoundException {return forName0(className, true, ClassLoader.getCallerClassLoader());}//java.lang.ClassLoader.java// Returns the invoker's class loader, or null if none.static ClassLoader getCallerClassLoader() {// 獲取調(diào)用類(caller)的類型Class caller = Reflection.getCallerClass(3);// This can be null if the VM is requesting itif (caller == null) {return null;}// 調(diào)用java.lang.Class中本地方法獲取加載該調(diào)用類(caller)的ClassLoaderreturn caller.getClassLoader0();}//java.lang.Class.java//虛擬機(jī)本地實(shí)現(xiàn),獲取當(dāng)前類的類加載器,前面介紹的Class的getClassLoader()也使用此方法native ClassLoader getClassLoader0();


    4.3 在編寫自定義類加載器時,如果沒有設(shè)定父加載器,那么父加載器是誰?


      前面講過,在不指定父類加載器的情況下,默認(rèn)采用系統(tǒng)類加載器。可能有人覺得不明白,現(xiàn)在我們來看一下JDK對應(yīng)的代碼實(shí)現(xiàn)。眾所周知,我們編寫自定義的類加載器直接或者間接繼承自java.lang.ClassLoader抽象類,對應(yīng)的無參默認(rèn)構(gòu)造函數(shù)實(shí)現(xiàn)如下:

    [java] view plaincopyprint?
  • //摘自java.lang.ClassLoader.java?
  • protected ClassLoader() {?
  • ??? SecurityManager security = System.getSecurityManager();?
  • ??? if (security != null) {?
  • ??????? security.checkCreateClassLoader();?
  • ??? }?
  • ??? this.parent = getSystemClassLoader();?
  • ??? initialized = true;?
  • }?
  • //摘自java.lang.ClassLoader.javaprotected ClassLoader() {SecurityManager security = System.getSecurityManager();if (security != null) {security.checkCreateClassLoader();}this.parent = getSystemClassLoader();initialized = true;}

      我們再來看一下對應(yīng)的getSystemClassLoader()方法的實(shí)現(xiàn):

    [java] view plaincopyprint?
  • private static synchronized void initSystemClassLoader() {?
  • ??? //...?
  • ??? sun.misc.Launcher l = sun.misc.Launcher.getLauncher();?
  • ??? scl = l.getClassLoader();?
  • ??? //...?
  • }?
  • private static synchronized void initSystemClassLoader() {//...sun.misc.Launcher l = sun.misc.Launcher.getLauncher();scl = l.getClassLoader();//...}

      我們可以寫簡單的測試代碼來測試一下:

    [java] view plaincopyprint?
  • System.out.println(sun.misc.Launcher.getLauncher().getClassLoader());?
  • System.out.println(sun.misc.Launcher.getLauncher().getClassLoader());

      本機(jī)對應(yīng)輸出如下:

    [plain] view plaincopyprint?
  • sun.misc.Launcher$AppClassLoader@73d16e93?
  • sun.misc.Launcher$AppClassLoader@73d16e93

      所以,我們現(xiàn)在可以相信當(dāng)自定義類加載器沒有指定父類加載器的情況下,默認(rèn)的父類加載器即為系統(tǒng)類加載器。同時,我們可以得出如下結(jié)論:即使用戶自定義類加載器不指定父類加載器,那么,同樣可以加載如下三個地方的類:
      1. <Java_Runtime_Home>/lib下的類;
      2. < Java_Runtime_Home >/lib/ext下或者由系統(tǒng)變量java.ext.dir指定位置中的類;
      3. 當(dāng)前工程類路徑下或者由系統(tǒng)變量java.class.path指定位置中的類。


    4.4 在編寫自定義類加載器時,如果將父類加載器強(qiáng)制設(shè)置為null,那么會有什么影響?如果自定義的類加載器不能加載指定類,就肯定會加載失敗嗎?


      JVM規(guī)范中規(guī)定如果用戶自定義的類加載器將父類加載器強(qiáng)制設(shè)置為null,那么會自動將啟動類加載器設(shè)置為當(dāng)前用戶自定義類加載器的父類加載器(這個問題前面已經(jīng)分析過了)。同時,我們可以得出如下結(jié)論:
      即使用戶自定義類加載器不指定父類加載器,那么,同樣可以加載到<Java_Runtime_Home>/lib下的類,但此時就不能夠加載<Java_Runtime_Home>/lib/ext目錄下的類了。
      說明:問題3和問題4的推斷結(jié)論是基于用戶自定義的類加載器本身延續(xù)了java.lang.ClassLoader.loadClass(…)默認(rèn)委派邏輯,如果用戶對這一默認(rèn)委派邏輯進(jìn)行了改變,以上推斷結(jié)論就不一定成立了,詳見問題5。


    4.5 編寫自定義類加載器時,一般有哪些注意點(diǎn)?


      1、一般盡量不要覆寫已有的loadClass(...)方法中的委派邏輯
      一般在JDK 1.2之前的版本才這樣做,而且事實(shí)證明,這樣做極有可能引起系統(tǒng)默認(rèn)的類加載器不能正常工作。在JVM規(guī)范和JDK文檔中(1.2或者以后版本中),都沒有建議用戶覆寫loadClass(…)方法,相比而言,明確提示開發(fā)者在開發(fā)自定義的類加載器時覆寫findClass(…)邏輯。舉一個例子來驗(yàn)證該問題:

    [java] view plaincopyprint?
  • //用戶自定義類加載器WrongClassLoader.Java(覆寫loadClass邏輯)?
  • public class WrongClassLoader extends ClassLoader {?
  • ?
  • ??? public Class<?> loadClass(String name) throws ClassNotFoundException {?
  • ??????? return this.findClass(name);?
  • ??? }?
  • ?
  • ??? protected Class<?> findClass(String name) throws ClassNotFoundException {?
  • ??????? // 假設(shè)此處只是到工程以外的特定目錄D:\library下去加載類?
  • ??????? // 具體實(shí)現(xiàn)代碼省略?
  • ??? }?
  • }?
  • //用戶自定義類加載器WrongClassLoader.Java(覆寫loadClass邏輯) public class WrongClassLoader extends ClassLoader {public Class<?> loadClass(String name) throws ClassNotFoundException {return this.findClass(name);}protected Class<?> findClass(String name) throws ClassNotFoundException {// 假設(shè)此處只是到工程以外的特定目錄D:\library下去加載類// 具體實(shí)現(xiàn)代碼省略} }

      通過前面的分析我們已經(jīng)知道,這個自定義類加載器WrongClassLoader的默認(rèn)類加載器是系統(tǒng)類加載器,但是現(xiàn)在問題4種的結(jié)論就不成立了。大家可以簡單測試一下,現(xiàn)在<Java_Runtime_Home>/lib、< Java_Runtime_Home >/lib/ext和工程類路徑上的類都加載不上了。

    [java] view plaincopyprint?
  • //問題5測試代碼一?
  • public class WrongClassLoaderTest {?
  • ?
  • ??? publicstaticvoid main(String[] args) {?
  • ??????? try {?
  • ??????????? WrongClassLoader loader = new WrongClassLoader();?
  • ??????????? Class classLoaded = loader.loadClass("beans.Account");?
  • ??????????? System.out.println(classLoaded.getName());?
  • ??????????? System.out.println(classLoaded.getClassLoader());?
  • ??????? } catch (Exception e) {?
  • ??????????? e.printStackTrace();?
  • ??????? }?
  • ??? }?
  • }?
  • //問題5測試代碼一 public class WrongClassLoaderTest {publicstaticvoid main(String[] args) {try {WrongClassLoader loader = new WrongClassLoader();Class classLoaded = loader.loadClass("beans.Account");System.out.println(classLoaded.getName());System.out.println(classLoaded.getClassLoader());} catch (Exception e) {e.printStackTrace();}} }

      這里D:"classes"beans"Account.class是物理存在的。輸出結(jié)果:

    [plain] view plaincopyprint?
  • java.io.FileNotFoundException: D:"classes"java"lang"Object.class (系統(tǒng)找不到指定的路徑。)?
  • ??? at java.io.FileInputStream.open(Native Method)?
  • ??? at java.io.FileInputStream.<init>(FileInputStream.java:106)?
  • ??? at WrongClassLoader.findClass(WrongClassLoader.java:40)?
  • ??? at WrongClassLoader.loadClass(WrongClassLoader.java:29)?
  • ??? at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)?
  • ??? at java.lang.ClassLoader.defineClass1(Native Method)?
  • ??? at java.lang.ClassLoader.defineClass(ClassLoader.java:620)?
  • ??? at java.lang.ClassLoader.defineClass(ClassLoader.java:400)?
  • ??? at WrongClassLoader.findClass(WrongClassLoader.java:43)?
  • ??? at WrongClassLoader.loadClass(WrongClassLoader.java:29)?
  • ??? at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)?
  • Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object?
  • ??? at java.lang.ClassLoader.defineClass1(Native Method)?
  • ??? at java.lang.ClassLoader.defineClass(ClassLoader.java:620)?
  • ??? at java.lang.ClassLoader.defineClass(ClassLoader.java:400)?
  • ??? at WrongClassLoader.findClass(WrongClassLoader.java:43)?
  • ??? at WrongClassLoader.loadClass(WrongClassLoader.java:29)?
  • ??? at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)?
  • java.io.FileNotFoundException: D:"classes"java"lang"Object.class (系統(tǒng)找不到指定的路徑。)at java.io.FileInputStream.open(Native Method)at java.io.FileInputStream.<init>(FileInputStream.java:106)at WrongClassLoader.findClass(WrongClassLoader.java:40)at WrongClassLoader.loadClass(WrongClassLoader.java:29)at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)at java.lang.ClassLoader.defineClass1(Native Method)at java.lang.ClassLoader.defineClass(ClassLoader.java:620)at java.lang.ClassLoader.defineClass(ClassLoader.java:400)at WrongClassLoader.findClass(WrongClassLoader.java:43)at WrongClassLoader.loadClass(WrongClassLoader.java:29)at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27) Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Objectat java.lang.ClassLoader.defineClass1(Native Method)at java.lang.ClassLoader.defineClass(ClassLoader.java:620)at java.lang.ClassLoader.defineClass(ClassLoader.java:400)at WrongClassLoader.findClass(WrongClassLoader.java:43)at WrongClassLoader.loadClass(WrongClassLoader.java:29)at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)

      這說明,連要加載的類型的超類型java.lang.Object都加載不到了。這里列舉的由于覆寫loadClass()引起的邏輯錯誤明顯是比較簡單的,實(shí)際引起的邏輯錯誤可能復(fù)雜的多。

    [java] view plaincopyprint?
  • //問題5測試二?
  • //用戶自定義類加載器WrongClassLoader.Java(不覆寫loadClass邏輯)?
  • public class WrongClassLoader extends ClassLoader {?
  • ?
  • ??? protected Class<?> findClass(String name) throws ClassNotFoundException {?
  • ??????? //假設(shè)此處只是到工程以外的特定目錄D:\library下去加載類?
  • ??????? //具體實(shí)現(xiàn)代碼省略?
  • ??? }?
  • }?
  • //問題5測試二 //用戶自定義類加載器WrongClassLoader.Java(不覆寫loadClass邏輯) public class WrongClassLoader extends ClassLoader {protected Class<?> findClass(String name) throws ClassNotFoundException {//假設(shè)此處只是到工程以外的特定目錄D:\library下去加載類//具體實(shí)現(xiàn)代碼省略} }

      將自定義類加載器代碼WrongClassLoader.Java做以上修改后,再運(yùn)行測試代碼,輸出結(jié)果如下:

    [plain] view plaincopyprint?
  • beans.Account?
  • WrongClassLoader@1c78e57?
  • beans.Account WrongClassLoader@1c78e57

      2、正確設(shè)置父類加載器
      通過上面問題4和問題5的分析我們應(yīng)該已經(jīng)理解,個人覺得這是自定義用戶類加載器時最重要的一點(diǎn),但常常被忽略或者輕易帶過。有了前面JDK代碼的分析作為基礎(chǔ),我想現(xiàn)在大家都可以隨便舉出例子了。
      3、保證findClass(String name)方法的邏輯正確性
      事先盡量準(zhǔn)確理解待定義的類加載器要完成的加載任務(wù),確保最大程度上能夠獲取到對應(yīng)的字節(jié)碼內(nèi)容。


    4.6 如何在運(yùn)行時判斷系統(tǒng)類加載器能加載哪些路徑下的類?


      一是可以直接調(diào)用ClassLoader.getSystemClassLoader()或者其他方式獲取到系統(tǒng)類加載器(系統(tǒng)類加載器和擴(kuò)展類加載器本身都派生自URLClassLoader),調(diào)用URLClassLoader中的getURLs()方法可以獲取到。
      二是可以直接通過獲取系統(tǒng)屬性java.class.path來查看當(dāng)前類路徑上的條目信息 :System.getProperty("java.class.path")。


    4.7 如何在運(yùn)行時判斷標(biāo)準(zhǔn)擴(kuò)展類加載器能加載哪些路徑下的類?


      方法之一:

    [java] view plaincopyprint?
  • import java.net.URL;?
  • import java.net.URLClassLoader;?
  • ?
  • public class ClassLoaderTest {?
  • ?
  • ??? /**
  • ???? * @param args the command line arguments
  • ???? */?
  • ??? public static void main(String[] args) {?
  • ??????? try {?
  • ??????????? URL[] extURLs = ((URLClassLoader) ClassLoader.getSystemClassLoader().getParent()).getURLs();?
  • ??????????? for (int i = 0; i < extURLs.length; i++) {?
  • ??????????????? System.out.println(extURLs[i]);?
  • ??????????? }?
  • ??????? } catch (Exception e) {?
  • ??????????? //…?
  • ??????? }?
  • ??? }?
  • }?
  • import java.net.URL; import java.net.URLClassLoader;public class ClassLoaderTest {/*** @param args the command line arguments*/public static void main(String[] args) {try {URL[] extURLs = ((URLClassLoader) ClassLoader.getSystemClassLoader().getParent()).getURLs();for (int i = 0; i < extURLs.length; i++) {System.out.println(extURLs[i]);}} catch (Exception e) {//…}} }

      本機(jī)對應(yīng)輸出如下:

    [plain] view plaincopyprint?
  • file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/access-bridge-64.jar?
  • file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/cldrdata.jar?
  • file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/dnsns.jar?
  • file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/jaccess.jar?
  • file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/jfxrt.jar?
  • file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/localedata.jar?
  • file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/nashorn.jar?
  • file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/sunec.jar?
  • file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/sunjce_provider.jar?
  • file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/sunmscapi.jar?
  • file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/sunpkcs11.jar?
  • file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/zipfs.jar?
  • file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/access-bridge-64.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/cldrdata.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/dnsns.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/jaccess.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/jfxrt.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/localedata.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/nashorn.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/sunec.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/sunjce_provider.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/sunmscapi.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/sunpkcs11.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/zipfs.jar


    5 開發(fā)自己的類加載器


      在前面介紹類加載器的代理委派模式的時候,提到過類加載器會首先代理給其它類加載器來嘗試加載某個類。這就意味著真正完成類的加載工作的類加載器和啟動這個加載過程的類加載器,有可能不是同一個。真正完成類的加載工作是通過調(diào)用defineClass來實(shí)現(xiàn)的;而啟動類的加載過程是通過調(diào)用loadClass來實(shí)現(xiàn)的。前者稱為一個類的定義加載器(defining loader),后者稱為初始加載器(initiating loader)。在Java虛擬機(jī)判斷兩個類是否相同的時候,使用的是類的定義加載器。也就是說,哪個類加載器啟動類的加載過程并不重要,重要的是最終定義這個類的加載器。兩種類加載器的關(guān)聯(lián)之處在于:一個類的定義加載器是它引用的其它類的初始加載器。如類 com.example.Outer引用了類 com.example.Inner,則由類 com.example.Outer的定義加載器負(fù)責(zé)啟動類 com.example.Inner的加載過程。
      方法 loadClass()拋出的是 java.lang.ClassNotFoundException異常;方法 defineClass()拋出的是 java.lang.NoClassDefFoundError異常。
      類加載器在成功加載某個類之后,會把得到的 java.lang.Class類的實(shí)例緩存起來。下次再請求加載該類的時候,類加載器會直接使用緩存的類的實(shí)例,而不會嘗試再次加載。也就是說,對于一個類加載器實(shí)例來說,相同全名的類只加載一次,即 loadClass方法不會被重復(fù)調(diào)用。

      在絕大多數(shù)情況下,系統(tǒng)默認(rèn)提供的類加載器實(shí)現(xiàn)已經(jīng)可以滿足需求。但是在某些情況下,您還是需要為應(yīng)用開發(fā)出自己的類加載器。比如您的應(yīng)用通過網(wǎng)絡(luò)來傳輸Java類的字節(jié)代碼,為了保證安全性,這些字節(jié)代碼經(jīng)過了加密處理。這個時候您就需要自己的類加載器來從某個網(wǎng)絡(luò)地址上讀取加密后的字節(jié)代碼,接著進(jìn)行解密和驗(yàn)證,最后定義出要在Java虛擬機(jī)中運(yùn)行的類來。下面將通過兩個具體的實(shí)例來說明類加載器的開發(fā)。


    5.1 文件系統(tǒng)類加載器


      第一個類加載器用來加載存儲在文件系統(tǒng)上的Java字節(jié)代碼。完整的實(shí)現(xiàn)如下所示。

    [java] view plaincopyprint?
  • package classloader;?
  • ?
  • import java.io.ByteArrayOutputStream;?
  • import java.io.File;?
  • import java.io.FileInputStream;?
  • import java.io.IOException;?
  • import java.io.InputStream;?
  • ?
  • // 文件系統(tǒng)類加載器?
  • public class FileSystemClassLoader extends ClassLoader {?
  • ?
  • ??? private String rootDir;?
  • ?
  • ??? public FileSystemClassLoader(String rootDir) {?
  • ??????? this.rootDir = rootDir;?
  • ??? }?
  • ?
  • ??? // 獲取類的字節(jié)碼?
  • ??? @Override?
  • ??? protected Class<?> findClass(String name) throws ClassNotFoundException {?
  • ??????? byte[] classData = getClassData(name);? // 獲取類的字節(jié)數(shù)組?
  • ??????? if (classData == null) {?
  • ??????????? throw new ClassNotFoundException();?
  • ??????? } else {?
  • ??????????? return defineClass(name, classData, 0, classData.length);?
  • ??????? }?
  • ??? }?
  • ?
  • ??? private byte[] getClassData(String className) {?
  • ??????? // 讀取類文件的字節(jié)?
  • ??????? 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;?
  • ??????????? // 讀取類文件的字節(jié)碼?
  • ??????????? while ((bytesNumRead = ins.read(buffer)) != -1) {?
  • ??????????????? baos.write(buffer, 0, bytesNumRead);?
  • ??????????? }?
  • ??????????? return baos.toByteArray();?
  • ??????? } catch (IOException e) {?
  • ??????????? e.printStackTrace();?
  • ??????? }?
  • ??????? return null;?
  • ??? }?
  • ?
  • ??? private String classNameToPath(String className) {?
  • ??????? // 得到類文件的完全路徑?
  • ??????? return rootDir + File.separatorChar?
  • ??????????????? + className.replace('.', File.separatorChar) + ".class";?
  • ??? }?
  • }?
  • package classloader;import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream;// 文件系統(tǒng)類加載器 public class FileSystemClassLoader extends ClassLoader {private String rootDir;public FileSystemClassLoader(String rootDir) {this.rootDir = rootDir;}// 獲取類的字節(jié)碼@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] classData = getClassData(name); // 獲取類的字節(jié)數(shù)組if (classData == null) {throw new ClassNotFoundException();} else {return defineClass(name, classData, 0, classData.length);}}private byte[] getClassData(String className) {// 讀取類文件的字節(jié)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;// 讀取類文件的字節(jié)碼while ((bytesNumRead = ins.read(buffer)) != -1) {baos.write(buffer, 0, bytesNumRead);}return baos.toByteArray();} catch (IOException e) {e.printStackTrace();}return null;}private String classNameToPath(String className) {// 得到類文件的完全路徑return rootDir + File.separatorChar+ className.replace('.', File.separatorChar) + ".class";} }

      如上所示,類 FileSystemClassLoader繼承自類java.lang.ClassLoader。在java.lang.ClassLoader類的常用方法中,一般來說,自己開發(fā)的類加載器只需要覆寫 findClass(String name)方法即可。java.lang.ClassLoader類的方法loadClass()封裝了前面提到的代理模式的實(shí)現(xiàn)。該方法會首先調(diào)用findLoadedClass()方法來檢查該類是否已經(jīng)被加載過;如果沒有加載過的話,會調(diào)用父類加載器的loadClass()方法來嘗試加載該類;如果父類加載器無法加載該類的話,就調(diào)用findClass()方法來查找該類。因此,為了保證類加載器都正確實(shí)現(xiàn)代理模式,在開發(fā)自己的類加載器時,最好不要覆寫 loadClass()方法,而是覆寫 findClass()方法。
      類 FileSystemClassLoader的 findClass()方法首先根據(jù)類的全名在硬盤上查找類的字節(jié)代碼文件(.class 文件),然后讀取該文件內(nèi)容,最后通過defineClass()方法來把這些字節(jié)代碼轉(zhuǎn)換成 java.lang.Class類的實(shí)例。

      加載本地文件系統(tǒng)上的類,示例如下:

    [java] view plaincopyprint?
  • package com.example;?
  • ?
  • public class Sample {?
  • ?
  • ??? private Sample instance;?
  • ?
  • ??? public void setSample(Object instance) {?
  • ??????? System.out.println(instance.toString());?
  • ??????? this.instance = (Sample) instance;?
  • ??? }?
  • }?
  • package com.example;public class Sample {private Sample instance;public void setSample(Object instance) {System.out.println(instance.toString());this.instance = (Sample) instance;} } [java] view plaincopyprint?
  • package classloader;?
  • ?
  • import java.lang.reflect.Method;?
  • ?
  • public class ClassIdentity {?
  • ?
  • ??? public static void main(String[] args) {?
  • ??????? new ClassIdentity().testClassIdentity();?
  • ??? }?
  • ?
  • ??? public void testClassIdentity() {?
  • ??????? String classDataRootPath = "C:\\Users\\JackZhou\\Documents\\NetBeansProjects\\classloader\\build\\classes";?
  • ??????? FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);?
  • ??????? FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);?
  • ??????? String className = "com.example.Sample";?
  • ??????? try {?
  • ??????????? Class<?> class1 = fscl1.loadClass(className);? // 加載Sample類?
  • ??????????? Object obj1 = class1.newInstance();? // 創(chuàng)建對象?
  • ??????????? Class<?> class2 = fscl2.loadClass(className);?
  • ??????????? Object obj2 = class2.newInstance();?
  • ??????????? Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);?
  • ??????????? setSampleMethod.invoke(obj1, obj2);?
  • ??????? } catch (Exception e) {?
  • ??????????? e.printStackTrace();?
  • ??????? }?
  • ??? }?
  • }?
  • package classloader;import java.lang.reflect.Method;public class ClassIdentity {public static void main(String[] args) {new ClassIdentity().testClassIdentity();}public void testClassIdentity() {String classDataRootPath = "C:\\Users\\JackZhou\\Documents\\NetBeansProjects\\classloader\\build\\classes";FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);String className = "com.example.Sample";try {Class<?> class1 = fscl1.loadClass(className); // 加載Sample類Object obj1 = class1.newInstance(); // 創(chuàng)建對象Class<?> class2 = fscl2.loadClass(className);Object obj2 = class2.newInstance();Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);setSampleMethod.invoke(obj1, obj2);} catch (Exception e) {e.printStackTrace();}} }

      運(yùn)行輸出:com.example.Sample@7852e922


    5.2 網(wǎng)絡(luò)類加載器


      下面將通過一個網(wǎng)絡(luò)類加載器來說明如何通過類加載器來實(shí)現(xiàn)組件的動態(tài)更新。即基本的場景是:Java 字節(jié)代碼(.class)文件存放在服務(wù)器上,客戶端通過網(wǎng)絡(luò)的方式獲取字節(jié)代碼并執(zhí)行。當(dāng)有版本更新的時候,只需要替換掉服務(wù)器上保存的文件即可。通過類加載器可以比較簡單的實(shí)現(xiàn)這種需求。
      類 NetworkClassLoader負(fù)責(zé)通過網(wǎng)絡(luò)下載Java類字節(jié)代碼并定義出Java類。它的實(shí)現(xiàn)與FileSystemClassLoader類似。

    [java] view plaincopyprint?
  • package classloader;?
  • ?
  • import java.io.ByteArrayOutputStream;?
  • import java.io.InputStream;?
  • import java.net.URL;?
  • ?
  • public class NetworkClassLoader extends ClassLoader {?
  • ?
  • ??? private String rootUrl;?
  • ?
  • ??? public NetworkClassLoader(String rootUrl) {?
  • ??????? // 指定URL?
  • ??????? this.rootUrl = rootUrl;?
  • ??? }?
  • ?
  • ??? // 獲取類的字節(jié)碼?
  • ??? @Override?
  • ??? protected Class<?> findClass(String name) throws ClassNotFoundException {?
  • ??????? byte[] classData = getClassData(name);?
  • ??????? if (classData == null) {?
  • ??????????? throw new ClassNotFoundException();?
  • ??????? } else {?
  • ??????????? return defineClass(name, classData, 0, classData.length);?
  • ??????? }?
  • ??? }?
  • ?
  • ??? private byte[] getClassData(String className) {?
  • ??????? // 從網(wǎng)絡(luò)上讀取的類的字節(jié)?
  • ??????? String path = classNameToPath(className);?
  • ??????? try {?
  • ??????????? URL url = new URL(path);?
  • ??????????? InputStream ins = url.openStream();?
  • ??????????? ByteArrayOutputStream baos = new ByteArrayOutputStream();?
  • ??????????? int bufferSize = 4096;?
  • ??????????? byte[] buffer = new byte[bufferSize];?
  • ??????????? int bytesNumRead = 0;?
  • ??????????? // 讀取類文件的字節(jié)?
  • ??????????? while ((bytesNumRead = ins.read(buffer)) != -1) {?
  • ??????????????? baos.write(buffer, 0, bytesNumRead);?
  • ??????????? }?
  • ??????????? return baos.toByteArray();?
  • ??????? } catch (Exception e) {?
  • ??????????? e.printStackTrace();?
  • ??????? }?
  • ??????? return null;?
  • ??? }?
  • ?
  • ??? private String classNameToPath(String className) {?
  • ??????? // 得到類文件的URL?
  • ??????? return rootUrl + "/"?
  • ??????????????? + className.replace('.', '/') + ".class";?
  • ??? }?
  • }?
  • package classloader;import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.URL;public class NetworkClassLoader extends ClassLoader {private String rootUrl;public NetworkClassLoader(String rootUrl) {// 指定URLthis.rootUrl = rootUrl;}// 獲取類的字節(jié)碼@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] classData = getClassData(name);if (classData == null) {throw new ClassNotFoundException();} else {return defineClass(name, classData, 0, classData.length);}}private byte[] getClassData(String className) {// 從網(wǎng)絡(luò)上讀取的類的字節(jié)String path = classNameToPath(className);try {URL url = new URL(path);InputStream ins = url.openStream();ByteArrayOutputStream baos = new ByteArrayOutputStream();int bufferSize = 4096;byte[] buffer = new byte[bufferSize];int bytesNumRead = 0;// 讀取類文件的字節(jié)while ((bytesNumRead = ins.read(buffer)) != -1) {baos.write(buffer, 0, bytesNumRead);}return baos.toByteArray();} catch (Exception e) {e.printStackTrace();}return null;}private String classNameToPath(String className) {// 得到類文件的URLreturn rootUrl + "/"+ className.replace('.', '/') + ".class";} }

      在通過NetworkClassLoader加載了某個版本的類之后,一般有兩種做法來使用它。第一種做法是使用Java反射API。另外一種做法是使用接口。需要注意的是,并不能直接在客戶端代碼中引用從服務(wù)器上下載的類,因?yàn)榭蛻舳舜a的類加載器找不到這些類。使用Java反射API可以直接調(diào)用Java類的方法。而使用接口的做法則是把接口的類放在客戶端中,從服務(wù)器上加載實(shí)現(xiàn)此接口的不同版本的類。在客戶端通過相同的接口來使用這些實(shí)現(xiàn)類。我們使用接口的方式。示例如下:

      客戶端接口:

    [java] view plaincopyprint?
  • package classloader;?
  • ?
  • public interface Versioned {?
  • ?
  • ??? String getVersion();?
  • }?
  • package classloader;public interface Versioned {String getVersion(); }

    [java] view plaincopyprint?
  • package classloader;?
  • ?
  • public interface ICalculator extends Versioned {?
  • ?
  • ??? String calculate(String expression);?
  • }?
  • package classloader;public interface ICalculator extends Versioned {String calculate(String expression); }

      網(wǎng)絡(luò)上的不同版本的類:

    [java] view plaincopyprint?
  • package com.example;?
  • ?
  • import classloader.ICalculator;?
  • ?
  • public class CalculatorBasic implements ICalculator {?
  • ?
  • ??? @Override?
  • ??? public String calculate(String expression) {?
  • ??????? return expression;?
  • ??? }?
  • ?
  • ??? @Override?
  • ??? public String getVersion() {?
  • ??????? return "1.0";?
  • ??? }?
  • ?
  • }?
  • package com.example;import classloader.ICalculator;public class CalculatorBasic implements ICalculator {@Overridepublic String calculate(String expression) {return expression;}@Overridepublic String getVersion() {return "1.0";}} [java] view plaincopyprint?
  • package com.example;?
  • ?
  • import classloader.ICalculator;?
  • ?
  • public class CalculatorAdvanced implements ICalculator {?
  • ?
  • ??? @Override?
  • ??? public String calculate(String expression) {?
  • ??????? return "Result is " + expression;?
  • ??? }?
  • ?
  • ??? @Override?
  • ??? public String getVersion() {?
  • ??????? return "2.0";?
  • ??? }?
  • ?
  • }?
  • package com.example;import classloader.ICalculator;public class CalculatorAdvanced implements ICalculator {@Overridepublic String calculate(String expression) {return "Result is " + expression;}@Overridepublic String getVersion() {return "2.0";}}

      在客戶端加載網(wǎng)絡(luò)上的類的過程:

    [java] view plaincopyprint?
  • package classloader;?
  • ?
  • public class CalculatorTest {?
  • ?
  • ??? public static void main(String[] args) {?
  • ??????? String url = "http://localhost:8080/ClassloaderTest/classes";?
  • ??????? NetworkClassLoader ncl = new NetworkClassLoader(url);?
  • ??????? String basicClassName = "com.example.CalculatorBasic";?
  • ??????? String advancedClassName = "com.example.CalculatorAdvanced";?
  • ??????? try {?
  • ??????????? Class<?> clazz = ncl.loadClass(basicClassName);? // 加載一個版本的類?
  • ??????????? ICalculator calculator = (ICalculator) clazz.newInstance();? // 創(chuàng)建對象?
  • ??????????? System.out.println(calculator.getVersion());?
  • ??????????? clazz = ncl.loadClass(advancedClassName);? // 加載另一個版本的類?
  • ??????????? calculator = (ICalculator) clazz.newInstance();?
  • ??????????? System.out.println(calculator.getVersion());?
  • ??????? } catch (Exception e) {?
  • ??????????? e.printStackTrace();?
  • ??????? }?
  • ??? }?
  • ?
  • }?
  • package classloader;public class CalculatorTest {public static void main(String[] args) {String url = "http://localhost:8080/ClassloaderTest/classes";NetworkClassLoader ncl = new NetworkClassLoader(url);String basicClassName = "com.example.CalculatorBasic";String advancedClassName = "com.example.CalculatorAdvanced";try {Class<?> clazz = ncl.loadClass(basicClassName); // 加載一個版本的類ICalculator calculator = (ICalculator) clazz.newInstance(); // 創(chuàng)建對象System.out.println(calculator.getVersion());clazz = ncl.loadClass(advancedClassName); // 加載另一個版本的類calculator = (ICalculator) clazz.newInstance();System.out.println(calculator.getVersion());} catch (Exception e) {e.printStackTrace();}}}


    ?

    參考文獻(xiàn):

    http://www.blogjava.net/zhuxing/archive/2008/08/08/220841.html

    http://www.ibm.com/developerworks/cn/java/j-lo-classloader/

    1 線程上下文類加載器


      線程上下文類加載器(context class loader)是從 JDK 1.2 開始引入的。類 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用來獲取和設(shè)置線程的上下文類加載器。如果沒有通過 setContextClassLoader(ClassLoader cl)方法進(jìn)行設(shè)置的話,線程將繼承其父線程的上下文類加載器。Java 應(yīng)用運(yùn)行的初始線程的上下文類加載器是系統(tǒng)類加載器。在線程中運(yùn)行的代碼可以通過此類加載器來加載類和資源。

      前面提到的類加載器的代理模式并不能解決 Java 應(yīng)用開發(fā)中會遇到的類加載器的全部問題。Java 提供了很多服務(wù)提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實(shí)現(xiàn)。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。這些 SPI 的接口由 Java 核心庫來提供,如 JAXP 的 SPI 接口定義包含在 javax.xml.parsers包中。這些 SPI 的實(shí)現(xiàn)代碼很可能是作為 Java 應(yīng)用所依賴的 jar 包被包含進(jìn)來,可以通過類路徑(CLASSPATH)來找到,如實(shí)現(xiàn)了 JAXP SPI 的 Apache Xerces所包含的 jar 包。SPI 接口中的代碼經(jīng)常需要加載具體的實(shí)現(xiàn)類。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory類中的 newInstance()方法用來生成一個新的 DocumentBuilderFactory的實(shí)例。這里的實(shí)例的真正的類是繼承自 javax.xml.parsers.DocumentBuilderFactory,由 SPI 的實(shí)現(xiàn)所提供的。如在 Apache Xerces 中,實(shí)現(xiàn)的類是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。而問題在于,SPI 的接口是 Java 核心庫的一部分,是由引導(dǎo)類加載器來加載的;SPI 實(shí)現(xiàn)的 Java 類一般是由系統(tǒng)類加載器來加載的。引導(dǎo)類加載器是無法找到 SPI 的實(shí)現(xiàn)類的,因?yàn)樗患虞d Java 的核心庫。它也不能代理給系統(tǒng)類加載器,因?yàn)樗窍到y(tǒng)類加載器的祖先類加載器。也就是說,類加載器的代理模式無法解決這個問題。

      線程上下文類加載器正好解決了這個問題。如果不做任何的設(shè)置,Java 應(yīng)用的線程的上下文類加載器默認(rèn)就是系統(tǒng)上下文類加載器。在 SPI 接口的代碼中使用線程上下文類加載器,就可以成功的加載到 SPI 實(shí)現(xiàn)的類。線程上下文類加載器在很多 SPI 的實(shí)現(xiàn)中都會用到。

      Java默認(rèn)的線程上下文類加載器是系統(tǒng)類加載器(AppClassLoader)。以下代碼摘自sun.misc.Launch的無參構(gòu)造函數(shù)Launch()。

    [java] view plaincopyprint?
  • // Now create the class loader to use to launch the application?
  • try {?
  • ??? loader = AppClassLoader.getAppClassLoader(extcl);?
  • } catch (IOException e) {?
  • ??? throw new InternalError(?
  • "Could not create application class loader" );?
  • }?
  • ?
  • // Also set the context class loader for the primordial thread.?
  • Thread.currentThread().setContextClassLoader(loader);?
  • // Now create the class loader to use to launch the application try {loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) {throw new InternalError( "Could not create application class loader" ); }// Also set the context class loader for the primordial thread. Thread.currentThread().setContextClassLoader(loader);

      使用線程上下文類加載器,可以在執(zhí)行線程中拋棄雙親委派加載鏈模式,使用線程上下文里的類加載器加載類。典型的例子有:通過線程上下文來加載第三方庫jndi實(shí)現(xiàn),而不依賴于雙親委派。大部分java application服務(wù)器(jboss, tomcat..)也是采用contextClassLoader來處理web服務(wù)。還有一些采用hot swap特性的框架,也使用了線程上下文類加載器,比如 seasar (full stack framework in japenese)。
      線程上下文從根本解決了一般應(yīng)用不能違背雙親委派模式的問題。使java類加載體系顯得更靈活。隨著多核時代的來臨,相信多線程開發(fā)將會越來越多地進(jìn)入程序員的實(shí)際編碼過程中。因此,在編寫基礎(chǔ)設(shè)施時, 通過使用線程上下文來加載類,應(yīng)該是一個很好的選擇。
      當(dāng)然,好東西都有利弊。使用線程上下文加載類,也要注意保證多個需要通信的線程間的類加載器應(yīng)該是同一個,防止因?yàn)椴煌念惣虞d器導(dǎo)致類型轉(zhuǎn)換異常(ClassCastException)。

      defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain)是java.lang.Classloader提供給開發(fā)人員,用來自定義加載class的接口。使用該接口,可以動態(tài)的加載class文件。例如在jdk中,URLClassLoader是配合findClass方法來使用defineClass,可以從網(wǎng)絡(luò)或硬盤上加載class。而使用類加載接口,并加上自己的實(shí)現(xiàn)邏輯,還可以定制出更多的高級特性。

      下面是一個簡單的hot swap類加載器實(shí)現(xiàn)。hot swap即熱插拔的意思,這里表示一個類已經(jīng)被一個加載器加載了以后,在不卸載它的情況下重新再加載它一次。我們知道Java缺省的加載器對相同全名的類只會加載一次,以后直接從緩存中取這個Class object。因此要實(shí)現(xiàn)hot swap,必須在加載的那一刻進(jìn)行攔截,先判斷是否已經(jīng)加載,若是則重新加載一次,否則直接首次加載它。我們從URLClassLoader繼承,加載類的過程都代理給系統(tǒng)類加載器URLClassLoader中的相應(yīng)方法來完成。

    [java] view plaincopyprint?
  • package classloader;?
  • ?
  • import java.net.URL;?
  • import java.net.URLClassLoader;?
  • ?
  • /**
  • * 可以重新載入同名類的類加載器實(shí)現(xiàn)
  • * 放棄了雙親委派的加載鏈模式,需要外部維護(hù)重載后的類的成員變量狀態(tài)
  • */?
  • public class HotSwapClassLoader extends URLClassLoader {?
  • ?
  • ??? public HotSwapClassLoader(URL[] urls) {?
  • ??????? super(urls);?
  • ??? }?
  • ?
  • ??? public HotSwapClassLoader(URL[] urls, ClassLoader parent) {?
  • ??????? super(urls, parent);?
  • ??? }?
  • ?
  • ??? // 下面的兩個重載load方法實(shí)現(xiàn)類的加載,仿照ClassLoader中的兩個loadClass()?
  • ??? // 具體的加載過程代理給父類中的相應(yīng)方法來完成?
  • ??? public Class<?> load(String name) throws ClassNotFoundException {?
  • ??????? return load(name, false);?
  • ??? }?
  • ?
  • ??? public Class<?> load(String name, boolean resolve) throws ClassNotFoundException {?
  • ??????? // 若類已經(jīng)被加載,則重新再加載一次?
  • ??????? if (null != super.findLoadedClass(name)) {?
  • ??????????? return reload(name, resolve);?
  • ??????? }?
  • ??????? // 否則用findClass()首次加載它?
  • ??????? Class<?> clazz = super.findClass(name);?
  • ??????? if (resolve) {?
  • ??????????? super.resolveClass(clazz);?
  • ??????? }?
  • ??????? return clazz;?
  • ??? }?
  • ?
  • ??? public Class<?> reload(String name, boolean resolve) throws ClassNotFoundException {?
  • ??????? return new HotSwapClassLoader(super.getURLs(), super.getParent()).load(?
  • ??????????????? name, resolve);?
  • ??? }?
  • }?
  • package classloader;import java.net.URL; import java.net.URLClassLoader;/*** 可以重新載入同名類的類加載器實(shí)現(xiàn)* 放棄了雙親委派的加載鏈模式,需要外部維護(hù)重載后的類的成員變量狀態(tài)*/ public class HotSwapClassLoader extends URLClassLoader {public HotSwapClassLoader(URL[] urls) {super(urls);}public HotSwapClassLoader(URL[] urls, ClassLoader parent) {super(urls, parent);}// 下面的兩個重載load方法實(shí)現(xiàn)類的加載,仿照ClassLoader中的兩個loadClass()// 具體的加載過程代理給父類中的相應(yīng)方法來完成public Class<?> load(String name) throws ClassNotFoundException {return load(name, false);}public Class<?> load(String name, boolean resolve) throws ClassNotFoundException {// 若類已經(jīng)被加載,則重新再加載一次if (null != super.findLoadedClass(name)) {return reload(name, resolve);}// 否則用findClass()首次加載它Class<?> clazz = super.findClass(name);if (resolve) {super.resolveClass(clazz);}return clazz;}public Class<?> reload(String name, boolean resolve) throws ClassNotFoundException {return new HotSwapClassLoader(super.getURLs(), super.getParent()).load(name, resolve);} }

      兩個重載的load方法參數(shù)與ClassLoader類中的兩個loadClass()相似。在load的實(shí)現(xiàn)中,用findLoadedClass()查找指定的類是否已經(jīng)被祖先加載器加載了,若已加載則重新再加載一次,從而放棄了雙親委派的方式(這種方式只會加載一次)。若沒有加載則用自身的findClass()來首次加載它。

      下面是使用示例:

    [java] view plaincopyprint?
  • package classloader;?
  • ?
  • public class A {?
  • ?????
  • ??? private B b;?
  • ?
  • ??? public void setB(B b) {?
  • ??????? this.b = b;?
  • ??? }?
  • ?
  • ??? public B getB() {?
  • ??????? return b;?
  • ??? }?
  • }?
  • package classloader;public class A {private B b;public void setB(B b) {this.b = b;}public B getB() {return b;} } [java] view plaincopyprint?
  • package classloader;?
  • ?
  • public class B {?
  • ?????
  • }?
  • package classloader;public class B {} [java] view plaincopyprint?
  • package classloader;?
  • ?
  • import java.lang.reflect.InvocationTargetException;?
  • import java.lang.reflect.Method;?
  • import java.net.MalformedURLException;?
  • import java.net.URL;?
  • ?
  • public class TestHotSwap {?
  • ?
  • ??? public static void main(String args[]) throws MalformedURLException {?
  • ??????? A a = new A();? // 加載類A?
  • ??????? B b = new B();? // 加載類B?
  • ??????? a.setB(b);? // A引用了B,把b對象拷貝到A.b?
  • ??????? System.out.printf("A classLoader is %s\n", a.getClass().getClassLoader());?
  • ??????? System.out.printf("B classLoader is %s\n", b.getClass().getClassLoader());?
  • ??????? System.out.printf("A.b classLoader is %s\n", a.getB().getClass().getClassLoader());?
  • ?
  • ??????? try {?
  • ??????????? URL[] urls = new URL[]{ new URL("file:///C:/Users/JackZhou/Documents/NetBeansProjects/classloader/build/classes/") };?
  • ??????????? HotSwapClassLoader c1 = new HotSwapClassLoader(urls, a.getClass().getClassLoader());?
  • ??????????? Class clazz = c1.load("classloader.A");? // 用hot swap重新加載類A?
  • ??????????? Object aInstance = clazz.newInstance();? // 創(chuàng)建A類對象?
  • ??????????? Method method1 = clazz.getMethod("setB", B.class);? // 獲取setB(B b)方法?
  • ??????????? method1.invoke(aInstance, b);??? // 調(diào)用setB(b)方法,重新把b對象拷貝到A.b?
  • ??????????? Method method2 = clazz.getMethod("getB");? // 獲取getB()方法?
  • ??????????? Object bInstance = method2.invoke(aInstance);? // 調(diào)用getB()方法?
  • ??????????? System.out.printf("Reloaded A.b classLoader is %s\n", bInstance.getClass().getClassLoader());?
  • ??????? } catch (MalformedURLException | ClassNotFoundException |??
  • ??????????????? InstantiationException | IllegalAccessException |??
  • ??????????????? NoSuchMethodException | SecurityException |??
  • ??????????????? IllegalArgumentException | InvocationTargetException e) {?
  • ??????????? e.printStackTrace();?
  • ??????? }?
  • ??? }?
  • }?
  • package classloader;import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL;public class TestHotSwap {public static void main(String args[]) throws MalformedURLException {A a = new A(); // 加載類AB b = new B(); // 加載類Ba.setB(b); // A引用了B,把b對象拷貝到A.bSystem.out.printf("A classLoader is %s\n", a.getClass().getClassLoader());System.out.printf("B classLoader is %s\n", b.getClass().getClassLoader());System.out.printf("A.b classLoader is %s\n", a.getB().getClass().getClassLoader());try {URL[] urls = new URL[]{ new URL("file:///C:/Users/JackZhou/Documents/NetBeansProjects/classloader/build/classes/") };HotSwapClassLoader c1 = new HotSwapClassLoader(urls, a.getClass().getClassLoader());Class clazz = c1.load("classloader.A"); // 用hot swap重新加載類AObject aInstance = clazz.newInstance(); // 創(chuàng)建A類對象Method method1 = clazz.getMethod("setB", B.class); // 獲取setB(B b)方法method1.invoke(aInstance, b); // 調(diào)用setB(b)方法,重新把b對象拷貝到A.bMethod method2 = clazz.getMethod("getB"); // 獲取getB()方法Object bInstance = method2.invoke(aInstance); // 調(diào)用getB()方法System.out.printf("Reloaded A.b classLoader is %s\n", bInstance.getClass().getClassLoader());} catch (MalformedURLException | ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) {e.printStackTrace();}} }

      運(yùn)行輸出:

    [java] view plaincopyprint?
  • A classLoader is sun.misc.Launcher$AppClassLoader@73d16e93?
  • B classLoader is sun.misc.Launcher$AppClassLoader@73d16e93?
  • A.b classLoader is sun.misc.Launcher$AppClassLoader@73d16e93?
  • Reloaded A.b classLoader is sun.misc.Launcher$AppClassLoader@73d16e93?
  • A classLoader is sun.misc.Launcher$AppClassLoader@73d16e93 B classLoader is sun.misc.Launcher$AppClassLoader@73d16e93 A.b classLoader is sun.misc.Launcher$AppClassLoader@73d16e93 Reloaded A.b classLoader is sun.misc.Launcher$AppClassLoader@73d16e93

      HotSwapClassLoader加載器的作用是重新加載同名的類。為了實(shí)現(xiàn)hot swap,一個類在加載過后,若重新再加載一次,則新的Class object的狀態(tài)會改變,老的狀態(tài)數(shù)據(jù)需要通過其他方式拷貝到重新加載過的類生成的全新Class object實(shí)例中來。上面A類引用了B類,加載A時也會加載B(如果B已經(jīng)加載,則直接從緩存中取出)。在重新加載A后,其Class object中的成員b會重置,因此要重新調(diào)用setB(b)拷貝一次。你可以注釋掉這行代碼,再運(yùn)行會拋出java.lang.NullPointerException,指示A.b為null。
      注意新的A Class object實(shí)例所依賴的B類Class object,如果它與老的B Class object實(shí)例不是同一個類加載器加載的, 將會拋出類型轉(zhuǎn)換異常(ClassCastException),表示兩種不同的類。因此在重新加載A后,要特別注意給它的B類成員b傳入外部值時,它們是否由同一個類加載器加載。為了解決這種問題, HotSwapClassLoader自定義的l/oad方法中,當(dāng)前類(類A)是由自身classLoader加載的, 而內(nèi)部依賴的類(類B)還是老對象的classLoader加載的。


    2 何時使用Thread.getContextClassLoader()?


      這是一個很常見的問題,但答案卻很難回答。這個問題通常在需要動態(tài)加載類和資源的系統(tǒng)編程時會遇到。總的說來動態(tài)加載資源時,往往需要從三種類加載器里選擇:系統(tǒng)或程序的類加載器、當(dāng)前類加載器、以及當(dāng)前線程的上下文類加載器。在程序中應(yīng)該使用何種類加載器呢?
      系統(tǒng)類加載器通常不會使用。此類加載器處理啟動應(yīng)用程序時classpath指定的類,可以通過ClassLoader.getSystemClassLoader()來獲得。所有的ClassLoader.getSystemXXX()接口也是通過這個類加載器加載的。一般不要顯式調(diào)用這些方法,應(yīng)該讓其他類加載器代理到系統(tǒng)類加載器上。由于系統(tǒng)類加載器是JVM最后創(chuàng)建的類加載器,這樣代碼只會適應(yīng)于簡單命令行啟動的程序。一旦代碼移植到EJB、Web應(yīng)用或者Java Web Start應(yīng)用程序中,程序肯定不能正確執(zhí)行。
      因此一般只有兩種選擇,當(dāng)前類加載器和線程上下文類加載器。當(dāng)前類加載器是指當(dāng)前方法所在類的加載器。這個類加載器是運(yùn)行時類解析使用的加載器,Class.forName(String)和Class.getResource(String)也使用該類加載器。代碼中X.class的寫法使用的類加載器也是這個類加載器。
      線程上下文類加載器在Java 2(J2SE)時引入。每個線程都有一個關(guān)聯(lián)的上下文類加載器。如果你使用new Thread()方式生成新的線程,新線程將繼承其父線程的上下文類加載器。如果程序?qū)€程上下文類加載器沒有任何改動的話,程序中所有的線程將都使用系統(tǒng)類加載器作為上下文類加載器。Web應(yīng)用和Java企業(yè)級應(yīng)用中,應(yīng)用服務(wù)器經(jīng)常要使用復(fù)雜的類加載器結(jié)構(gòu)來實(shí)現(xiàn)JNDI(Java命名和目錄接口)、線程池、組件熱部署等功能,因此理解這一點(diǎn)尤其重要。
      為什么要引入線程的上下文類加載器?將它引入J2SE并不是純粹的噱頭,由于Sun沒有提供充分的文檔解釋說明這一點(diǎn),這使許多開發(fā)者很糊涂。實(shí)際上,上下文類加載器為同樣在J2SE中引入的類加載代理機(jī)制提供了后門。通常JVM中的類加載器是按照層次結(jié)構(gòu)組織的,目的是每個類加載器(除了啟動整個JVM的原初類加載器)都有一個父類加載器。當(dāng)類加載請求到來時,類加載器通常首先將請求代理給父類加載器。只有當(dāng)父類加載器失敗后,它才試圖按照自己的算法查找并定義當(dāng)前類。
      有時這種模式并不能總是奏效。這通常發(fā)生在JVM核心代碼必須動態(tài)加載由應(yīng)用程序動態(tài)提供的資源時。拿JNDI為例,它的核心是由JRE核心類(rt.jar)實(shí)現(xiàn)的。但這些核心JNDI類必須能加載由第三方廠商提供的JNDI實(shí)現(xiàn)。這種情況下調(diào)用父類加載器(原初類加載器)來加載只有其子類加載器可見的類,這種代理機(jī)制就會失效。解決辦法就是讓核心JNDI類使用線程上下文類加載器,從而有效的打通類加載器層次結(jié)構(gòu),逆著代理機(jī)制的方向使用類加載器。
      順便提一下,XML解析API(JAXP)也是使用此種機(jī)制。當(dāng)JAXP還是J2SE擴(kuò)展時,XML解析器使用當(dāng)前類加載器方法來加載解析器實(shí)現(xiàn)。但當(dāng)JAXP成為J2SE核心代碼后,類加載機(jī)制就換成了使用線程上下文加載器,這和JNDI的原因相似。
      好了,現(xiàn)在我們明白了問題的關(guān)鍵:這兩種選擇不可能適應(yīng)所有情況。一些人認(rèn)為線程上下文類加載器應(yīng)成為新的標(biāo)準(zhǔn)。但這在不同JVM線程共享數(shù)據(jù)來溝通時,就會使類加載器的結(jié)構(gòu)亂七八糟。除非所有線程都使用同一個上下文類加載器。而且,使用當(dāng)前類加載器已成為缺省規(guī)則,它們廣泛應(yīng)用在類聲明、Class.forName等情景中。即使你想盡可能只使用上下文類加載器,總是有這樣那樣的代碼不是你所能控制的。這些代碼都使用代理到當(dāng)前類加載器的模式。混雜使用代理模式是很危險的。
      更為糟糕的是,某些應(yīng)用服務(wù)器將當(dāng)前類加載器和上下文類加器分別設(shè)置成不同的ClassLoader實(shí)例。雖然它們擁有相同的類路徑,但是它們之間并不存在父子代理關(guān)系。想想這為什么可怕:記住加載并定義某個類的類加載器是虛擬機(jī)內(nèi)部標(biāo)識該類的組成部分,如果當(dāng)前類加載器加載類X并接著執(zhí)行它,如JNDI查找類型為Y的數(shù)據(jù),上下文類加載器能夠加載并定義Y,這個Y的定義和當(dāng)前類加載器加載的相同名稱的類就不是同一個,使用隱式類型轉(zhuǎn)換就會造成異常。
      這種混亂的狀況還將在Java中存在很長時間。在J2SE中還包括以下的功能使用不同的類加載器:
      (1)JNDI使用線程上下文類加載器。
      (2)Class.getResource()和Class.forName()使用當(dāng)前類加載器。
      (3)JAXP使用上下文類加載器。
      (4)java.util.ResourceBundle使用調(diào)用者的當(dāng)前類加載器。
      (5)URL協(xié)議處理器使用java.protocol.handler.pkgs系統(tǒng)屬性并只使用系統(tǒng)類加載器。
      (6)Java序列化API缺省使用調(diào)用者當(dāng)前的類加載器。
      這些類加載器非常混亂,沒有在J2SE文檔中給以清晰明確的說明。
      該如何選擇類加載器?
      如若代碼是限于某些特定框架,這些框架有著特定加載規(guī)則,則不要做任何改動,讓框架開發(fā)者來保證其工作(比如應(yīng)用服務(wù)器提供商,盡管他們并不能總是做對)。如在Web應(yīng)用和EJB中,要使用Class.gerResource來加載資源。

      在其他情況下,我們可以自己來選擇最合適的類加載器。可以使用策略模式來設(shè)計選擇機(jī)制。其思想是將總是使用上下文類加載器或者總是使用當(dāng)前類加載器的決策同具體實(shí)現(xiàn)邏輯分離開。往往設(shè)計之初是很難預(yù)測何種類加載策略是合適的,該設(shè)計能夠讓你可以后來修改類加載策略。

      考慮使用下面的代碼,這是作者本人在工作中發(fā)現(xiàn)的經(jīng)驗(yàn)。這兒有一個缺省實(shí)現(xiàn),應(yīng)該可以適應(yīng)大部分工作場景:

    [java] view plaincopyprint?
  • package classloader.context;?
  • ?
  • /**
  • * 類加載上下文,持有要加載的類
  • */?
  • public class ClassLoadContext {?
  • ?
  • ??? private final Class m_caller;?
  • ?
  • ??? public final Class getCallerClass() {?
  • ??????? return m_caller;?
  • ??? }?
  • ?
  • ??? ClassLoadContext(final Class caller) {?
  • ??????? m_caller = caller;?
  • ??? }?
  • }?
  • package classloader.context;/*** 類加載上下文,持有要加載的類*/ public class ClassLoadContext {private final Class m_caller;public final Class getCallerClass() {return m_caller;}ClassLoadContext(final Class caller) {m_caller = caller;} }


    ?

    [java] view plaincopyprint?
  • package classloader.context;?
  • ?
  • /**
  • * 類加載策略接口
  • */?
  • public interface IClassLoadStrategy {?
  • ?
  • ??? ClassLoader getClassLoader(ClassLoadContext ctx);?
  • }?
  • package classloader.context;/*** 類加載策略接口*/ public interface IClassLoadStrategy {ClassLoader getClassLoader(ClassLoadContext ctx); } [java] view plaincopyprint?
  • /**
  • * 缺省的類加載策略,可以適應(yīng)大部分工作場景
  • */?
  • public class DefaultClassLoadStrategy implements IClassLoadStrategy {?
  • ?
  • ??? /**
  • ???? * 為ctx返回最合適的類加載器,從系統(tǒng)類加載器、當(dāng)前類加載器
  • ???? * 和當(dāng)前線程上下文類加載中選擇一個最底層的加載器
  • ???? * @param ctx
  • ???? * @return
  • ???? */?
  • ??? @Override?
  • ??? public ClassLoader getClassLoader(final ClassLoadContext ctx) {?
  • ??????? final ClassLoader callerLoader = ctx.getCallerClass().getClassLoader();?
  • ??????? final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();?
  • ??????? ClassLoader result;?
  • ?
  • ??????? // If 'callerLoader' and 'contextLoader' are in a parent-child?
  • ??????? // relationship, always choose the child:?
  • ??????? if (isChild(contextLoader, callerLoader)) {?
  • ??????????? result = callerLoader;?
  • ??????? } else if (isChild(callerLoader, contextLoader)) {?
  • ??????????? result = contextLoader;?
  • ??????? } else {?
  • ??????????? // This else branch could be merged into the previous one,?
  • ??????????? // but I show it here to emphasize the ambiguous case:?
  • ??????????? result = contextLoader;?
  • ??????? }?
  • ??????? final ClassLoader systemLoader = ClassLoader.getSystemClassLoader();?
  • ??????? // Precaution for when deployed as a bootstrap or extension class:?
  • ??????? if (isChild(result, systemLoader)) {?
  • ??????????? result = systemLoader;?
  • ??????? }?
  • ?????????
  • ??????? return result;?
  • ??? }?
  • ?????
  • ??? // 判斷anotherLoader是否是oneLoader的child?
  • ??? private boolean isChild(ClassLoader oneLoader, ClassLoader anotherLoader){?
  • ??????? //...?
  • ??? }?
  • ?
  • ??? // ... more methods ?
  • }?
  • /*** 缺省的類加載策略,可以適應(yīng)大部分工作場景*/ public class DefaultClassLoadStrategy implements IClassLoadStrategy {/*** 為ctx返回最合適的類加載器,從系統(tǒng)類加載器、當(dāng)前類加載器* 和當(dāng)前線程上下文類加載中選擇一個最底層的加載器* @param ctx* @return */@Overridepublic ClassLoader getClassLoader(final ClassLoadContext ctx) {final ClassLoader callerLoader = ctx.getCallerClass().getClassLoader();final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();ClassLoader result;// If 'callerLoader' and 'contextLoader' are in a parent-child// relationship, always choose the child:if (isChild(contextLoader, callerLoader)) {result = callerLoader;} else if (isChild(callerLoader, contextLoader)) {result = contextLoader;} else {// This else branch could be merged into the previous one,// but I show it here to emphasize the ambiguous case:result = contextLoader;}final ClassLoader systemLoader = ClassLoader.getSystemClassLoader();// Precaution for when deployed as a bootstrap or extension class:if (isChild(result, systemLoader)) {result = systemLoader;}return result;}// 判斷anotherLoader是否是oneLoader的childprivate boolean isChild(ClassLoader oneLoader, ClassLoader anotherLoader){//...}// ... more methods }

      決定應(yīng)該使用何種類加載器的接口是IClassLoaderStrategy,為了幫助IClassLoadStrategy做決定,給它傳遞了個ClassLoadContext對象作為參數(shù)。ClassLoadContext持有要加載的類。

      上面代碼的邏輯很簡單:如調(diào)用類的當(dāng)前類加載器和上下文類加載器是父子關(guān)系,則總是選擇子類加載器。對子類加載器可見的資源通常是對父類可見資源的超集,因此如果每個開發(fā)者都遵循J2SE的代理規(guī)則,這樣做大多數(shù)情況下是合適的。
      當(dāng)前類加載器和上下文類加載器是兄弟關(guān)系時,決定使用哪一個是比較困難的。理想情況下,Java運(yùn)行時不應(yīng)產(chǎn)生這種模糊。但一旦發(fā)生,上面代碼選擇上下文類加載器。這是作者本人的實(shí)際經(jīng)驗(yàn),絕大多數(shù)情況下應(yīng)該能正常工作。你可以修改這部分代碼來適應(yīng)具體需要。一般來說,上下文類加載器要比當(dāng)前類加載器更適合于框架編程,而當(dāng)前類加載器則更適合于業(yè)務(wù)邏輯編程。
      最后需要檢查一下,以便保證所選類加載器不是系統(tǒng)類加載器的父親,在開發(fā)標(biāo)準(zhǔn)擴(kuò)展類庫時這通常是個好習(xí)慣。
      注意作者故意沒有檢查要加載資源或類的名稱。Java XML API成為J2SE核心的歷程應(yīng)該能讓我們清楚過濾類名并不是好想法。作者也沒有試圖檢查哪個類加載器加載首先成功,而是檢查類加載器的父子關(guān)系,這是更好更有保證的方法。
      下面是類加載器的選擇器:

    [java] view plaincopyprint?
  • package classloader.context;?
  • ?
  • /**
  • * 類加載解析器,獲取最合適的類加載器
  • */?
  • public abstract class ClassLoaderResolver {?
  • ?????????
  • ??? private static IClassLoadStrategy s_strategy;? // initialized in <clinit>?
  • ??? private static final int CALL_CONTEXT_OFFSET = 3;? // may need to change if this class is redesigned?
  • ??? private static final CallerResolver CALLER_RESOLVER;? // set in <clinit>?
  • ?????
  • ??? static {?
  • ??????? try {?
  • ??????????? // This can fail if the current SecurityManager does not allow?
  • ??????????? // RuntimePermission ("createSecurityManager"):?
  • ??????????? CALLER_RESOLVER = new CallerResolver();?
  • ??????? } catch (SecurityException se) {?
  • ??????????? throw new RuntimeException("ClassLoaderResolver: could not create CallerResolver: " + se);?
  • ??????? }?
  • ??????? s_strategy = new DefaultClassLoadStrategy();? //默認(rèn)使用缺省加載策略?
  • ??? }?
  • ?
  • ??? /**
  • ???? * This method selects the best classloader instance to be used for
  • ???? * class/resource loading by whoever calls this method. The decision
  • ???? * typically involves choosing between the caller's current, thread context,
  • ???? * system, and other classloaders in the JVM and is made by the {@link IClassLoadStrategy}
  • ???? * instance established by the last call to {@link #setStrategy}.
  • ???? *
  • ???? * @return classloader to be used by the caller ['null' indicates the
  • ???? * primordial loader]
  • ???? */?
  • ??? public static synchronized ClassLoader getClassLoader() {?
  • ??????? final Class caller = getCallerClass(0); // 獲取執(zhí)行當(dāng)前方法的類?
  • ??????? final ClassLoadContext ctx = new ClassLoadContext(caller);? // 創(chuàng)建類加載上下文?
  • ??????? return s_strategy.getClassLoader(ctx);? // 獲取最合適的類加載器?
  • ??? }?
  • ?
  • ??? public static synchronized IClassLoadStrategy getStrategy() {?
  • ??????? return s_strategy;?
  • ??? }?
  • ?
  • ??? public static synchronized IClassLoadStrategy setStrategy(final IClassLoadStrategy strategy) {?
  • ??????? final IClassLoadStrategy old = s_strategy;? // 設(shè)置類加載策略?
  • ??????? s_strategy = strategy;?
  • ??????? return old;?
  • ??? }?
  • ?
  • ??? /**
  • ???? * A helper class to get the call context. It subclasses SecurityManager
  • ???? * to make getClassContext() accessible. An instance of CallerResolver
  • ???? * only needs to be created, not installed as an actual security manager.
  • ???? */?
  • ??? private static final class CallerResolver extends SecurityManager {?
  • ??????? @Override?
  • ??????? protected Class[] getClassContext() {?
  • ??????????? return super.getClassContext();? // 獲取當(dāng)執(zhí)行棧的所有類,native方法?
  • ??????? }?
  • ?
  • ??? }?
  • ?
  • ??? /*
  • ???? * Indexes into the current method call context with a given
  • ???? * offset.
  • ???? */?
  • ??? private static Class getCallerClass(final int callerOffset) {?
  • ??????? return CALLER_RESOLVER.getClassContext()[CALL_CONTEXT_OFFSET?
  • ??????????????? + callerOffset];? // 獲取執(zhí)行棧上某個方法所屬的類?
  • ??? }?
  • }?
  • package classloader.context;/*** 類加載解析器,獲取最合適的類加載器*/ public abstract class ClassLoaderResolver {private static IClassLoadStrategy s_strategy; // initialized in <clinit>private static final int CALL_CONTEXT_OFFSET = 3; // may need to change if this class is redesignedprivate static final CallerResolver CALLER_RESOLVER; // set in <clinit>static {try {// This can fail if the current SecurityManager does not allow// RuntimePermission ("createSecurityManager"):CALLER_RESOLVER = new CallerResolver();} catch (SecurityException se) {throw new RuntimeException("ClassLoaderResolver: could not create CallerResolver: " + se);}s_strategy = new DefaultClassLoadStrategy(); //默認(rèn)使用缺省加載策略}/*** This method selects the best classloader instance to be used for* class/resource loading by whoever calls this method. The decision* typically involves choosing between the caller's current, thread context,* system, and other classloaders in the JVM and is made by the {@link IClassLoadStrategy}* instance established by the last call to {@link #setStrategy}.* * @return classloader to be used by the caller ['null' indicates the* primordial loader]*/public static synchronized ClassLoader getClassLoader() {final Class caller = getCallerClass(0); // 獲取執(zhí)行當(dāng)前方法的類final ClassLoadContext ctx = new ClassLoadContext(caller); // 創(chuàng)建類加載上下文return s_strategy.getClassLoader(ctx); // 獲取最合適的類加載器}public static synchronized IClassLoadStrategy getStrategy() {return s_strategy;}public static synchronized IClassLoadStrategy setStrategy(final IClassLoadStrategy strategy) {final IClassLoadStrategy old = s_strategy; // 設(shè)置類加載策略s_strategy = strategy;return old;}/*** A helper class to get the call context. It subclasses SecurityManager* to make getClassContext() accessible. An instance of CallerResolver* only needs to be created, not installed as an actual security manager.*/private static final class CallerResolver extends SecurityManager {@Overrideprotected Class[] getClassContext() {return super.getClassContext(); // 獲取當(dāng)執(zhí)行棧的所有類,native方法}}/** Indexes into the current method call context with a given* offset.*/private static Class getCallerClass(final int callerOffset) {return CALLER_RESOLVER.getClassContext()[CALL_CONTEXT_OFFSET+ callerOffset]; // 獲取執(zhí)行棧上某個方法所屬的類} }

      可通過調(diào)用ClassLoaderResolver.getClassLoader()方法來獲取類加載器對象,并使用其ClassLoader的接口如loadClass()等來加載類和資源。此外還可使用下面的ResourceLoader接口來取代ClassLoader接口:

    [java] view plaincopyprint?
  • package classloader.context;?
  • ?
  • import java.net.URL;?
  • ?
  • public class ResourceLoader {?
  • ?
  • ??? /**
  • ???? * 加載一個類
  • ???? *
  • ???? * @param name
  • ???? * @return
  • ???? * @throws java.lang.ClassNotFoundException
  • ???? * @see java.lang.ClassLoader#loadClass(java.lang.String)
  • ???? */?
  • ??? public static Class<?> loadClass(final String name) throws ClassNotFoundException {?
  • ??????? //獲取最合適的類加載器?
  • ??????? final ClassLoader loader = ClassLoaderResolver.getClassLoader();?
  • ??????? //用指定加載器加載類?
  • ??????? return Class.forName(name, false, loader);?
  • ??? }?
  • ?
  • ??? /**
  • ???? * 加載一個資源
  • ???? *
  • ???? * @param name
  • ???? * @return
  • ???? * @see java.lang.ClassLoader#getResource(java.lang.String)
  • ???? */?
  • ??? public static URL getResource(final String name) {?
  • ??????? //獲取最合適的類加載器?
  • ??????? final ClassLoader loader = ClassLoaderResolver.getClassLoader();?
  • ??????? //查找指定的資源?
  • ??????? if (loader != null) {?
  • ??????????? return loader.getResource(name);?
  • ??????? } else {?
  • ??????????? return ClassLoader.getSystemResource(name);?
  • ??????? }?
  • ??? }?
  • ?
  • ??? // ... more methods ...?
  • }?
  • package classloader.context;import java.net.URL;public class ResourceLoader {/*** 加載一個類* * @param name* @return * @throws java.lang.ClassNotFoundException * @see java.lang.ClassLoader#loadClass(java.lang.String)*/public static Class<?> loadClass(final String name) throws ClassNotFoundException {//獲取最合適的類加載器final ClassLoader loader = ClassLoaderResolver.getClassLoader();//用指定加載器加載類return Class.forName(name, false, loader);}/*** 加載一個資源* * @param name* @return * @see java.lang.ClassLoader#getResource(java.lang.String)*/public static URL getResource(final String name) {//獲取最合適的類加載器final ClassLoader loader = ClassLoaderResolver.getClassLoader();//查找指定的資源if (loader != null) {return loader.getResource(name);} else {return ClassLoader.getSystemResource(name);}}// ... more methods ... }

      ClassLoadContext.getCallerClass()返回的類在ClassLoaderResolver或ResourceLoader使用,這樣做的目的是讓其能找到調(diào)用類的類加載器(上下文加載器總是能通過Thread.currentThread().getContextClassLoader()來獲得)。注意調(diào)用類是靜態(tài)獲得的,因此這個接口不需現(xiàn)有業(yè)務(wù)方法增加額外的Class參數(shù),而且也適合于靜態(tài)方法和類初始化代碼。具體使用時,可以往這個上下文對象中添加具體部署環(huán)境中所需的其他屬性。


    3 類加載器與Web容器


      對于運(yùn)行在 Java EE容器中的 Web 應(yīng)用來說,類加載器的實(shí)現(xiàn)方式與一般的 Java 應(yīng)用有所不同。不同的 Web 容器的實(shí)現(xiàn)方式也會有所不同。以 Apache Tomcat 來說,每個 Web 應(yīng)用都有一個對應(yīng)的類加載器實(shí)例。該類加載器也使用代理模式,所不同的是它是首先嘗試去加載某個類,如果找不到再代理給父類加載器。這與一般類加載器的順序是相反的。這是 Java Servlet 規(guī)范中的推薦做法,其目的是使得 Web 應(yīng)用自己的類的優(yōu)先級高于 Web 容器提供的類。這種代理模式的一個例外是:Java 核心庫的類是不在查找范圍之內(nèi)的。這也是為了保證 Java 核心庫的類型安全。
      絕大多數(shù)情況下,Web 應(yīng)用的開發(fā)人員不需要考慮與類加載器相關(guān)的細(xì)節(jié)。下面給出幾條簡單的原則:
      (1)每個 Web 應(yīng)用自己的 Java 類文件和使用的庫的 jar 包,分別放在 WEB-INF/classes和 WEB-INF/lib目錄下面。
      (2)多個應(yīng)用共享的 Java 類文件和 jar 包,分別放在 Web 容器指定的由所有 Web 應(yīng)用共享的目錄下面。
      (3)當(dāng)出現(xiàn)找不到類的錯誤時,檢查當(dāng)前類的類加載器和當(dāng)前線程的上下文類加載器是否正確。


    4 類加載器與OSGi


      OSGi是 Java 上的動態(tài)模塊系統(tǒng)。它為開發(fā)人員提供了面向服務(wù)和基于組件的運(yùn)行環(huán)境,并提供標(biāo)準(zhǔn)的方式用來管理軟件的生命周期。OSGi 已經(jīng)被實(shí)現(xiàn)和部署在很多產(chǎn)品上,在開源社區(qū)也得到了廣泛的支持。Eclipse就是基于OSGi 技術(shù)來構(gòu)建的。
      OSGi 中的每個模塊(bundle)都包含 Java 包和類。模塊可以聲明它所依賴的需要導(dǎo)入(import)的其它模塊的 Java 包和類(通過 Import-Package),也可以聲明導(dǎo)出(export)自己的包和類,供其它模塊使用(通過 Export-Package)。也就是說需要能夠隱藏和共享一個模塊中的某些 Java 包和類。這是通過 OSGi 特有的類加載器機(jī)制來實(shí)現(xiàn)的。OSGi 中的每個模塊都有對應(yīng)的一個類加載器。它負(fù)責(zé)加載模塊自己包含的 Java 包和類。當(dāng)它需要加載 Java 核心庫的類時(以 java開頭的包和類),它會代理給父類加載器(通常是啟動類加載器)來完成。當(dāng)它需要加載所導(dǎo)入的 Java 類時,它會代理給導(dǎo)出此 Java 類的模塊來完成加載。模塊也可以顯式的聲明某些 Java 包和類,必須由父類加載器來加載。只需要設(shè)置系統(tǒng)屬性 org.osgi.framework.bootdelegation的值即可。
      假設(shè)有兩個模塊 bundleA 和 bundleB,它們都有自己對應(yīng)的類加載器 classLoaderA 和 classLoaderB。在 bundleA 中包含類 com.bundleA.Sample,并且該類被聲明為導(dǎo)出的,也就是說可以被其它模塊所使用的。bundleB 聲明了導(dǎo)入 bundleA 提供的類 com.bundleA.Sample,并包含一個類 com.bundleB.NewSample繼承自 com.bundleA.Sample。在 bundleB 啟動的時候,其類加載器 classLoaderB 需要加載類 com.bundleB.NewSample,進(jìn)而需要加載類 com.bundleA.Sample。由于 bundleB 聲明了類 com.bundleA.Sample是導(dǎo)入的,classLoaderB 把加載類 com.bundleA.Sample的工作代理給導(dǎo)出該類的 bundleA 的類加載器 classLoaderA。classLoaderA 在其模塊內(nèi)部查找類 com.bundleA.Sample并定義它,所得到的類 com.bundleA.Sample實(shí)例就可以被所有聲明導(dǎo)入了此類的模塊使用。對于以 java開頭的類,都是由父類加載器來加載的。如果聲明了系統(tǒng)屬性 org.osgi.framework.bootdelegation=com.example.core.*,那么對于包 com.example.core中的類,都是由父類加載器來完成的。
      OSGi 模塊的這種類加載器結(jié)構(gòu),使得一個類的不同版本可以共存在 Java 虛擬機(jī)中,帶來了很大的靈活性。不過它的這種不同,也會給開發(fā)人員帶來一些麻煩,尤其當(dāng)模塊需要使用第三方提供的庫的時候。下面提供幾條比較好的建議:
      (1)如果一個類庫只有一個模塊使用,把該類庫的 jar 包放在模塊中,在 Bundle-ClassPath中指明即可。
      (2)如果一個類庫被多個模塊共用,可以為這個類庫單獨(dú)的創(chuàng)建一個模塊,把其它模塊需要用到的 Java 包聲明為導(dǎo)出的。其它模塊聲明導(dǎo)入這些類。
      (3)如果類庫提供了 SPI 接口,并且利用線程上下文類加載器來加載 SPI 實(shí)現(xiàn)的 Java 類,有可能會找不到 Java 類。如果出現(xiàn)了 NoClassDefFoundError異常,首先檢查當(dāng)前線程的上下文類加載器是否正確。通過 Thread.currentThread().getContextClassLoader()就可以得到該類加載器。該類加載器應(yīng)該是該模塊對應(yīng)的類加載器。如果不是的話,可以首先通過 class.getClassLoader()來得到模塊對應(yīng)的類加載器,再通過 Thread.currentThread().setContextClassLoader()來設(shè)置當(dāng)前線程的上下文類加載器。


    總結(jié)


      類加載器是 Java 語言的一個創(chuàng)新。它使得動態(tài)安裝和更新軟件組件成為可能。本文詳細(xì)介紹了類加載器的相關(guān)話題,包括基本概念、代理模式、線程上下文類加載器、與 Web 容器和 OSGi 的關(guān)系等。開發(fā)人員在遇到 ClassNotFoundException和 NoClassDefFoundError等異常的時候,應(yīng)該檢查拋出異常的類的類加載器和當(dāng)前線程的上下文類加載器,從中可以發(fā)現(xiàn)問題的所在。在開發(fā)自己的類加載器的時候,需要注意與已有的類加載器組織結(jié)構(gòu)的協(xié)調(diào)。


    參考文獻(xiàn):

    https://www.ibm.com/developerworks/cn/java/j-lo-classloader/

    http://www.blogjava.net/lihao336/archive/2009/09/17/295489.html

    http://kenwublog.com/structure-of-java-class-loader

    ?

    ?

    總結(jié)

    以上是生活随笔為你收集整理的深入理解Java类加载器:Java类加载原理解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

    国产精品va在线观看无码 | 成人精品天堂一区二区三区 | 国产精品欧美成人 | 欧美亚洲日韩国产人成在线播放 | 呦交小u女精品视频 | 久久人妻内射无码一区三区 | 国产精品无码永久免费888 | 久久www免费人成人片 | 国产sm调教视频在线观看 | 高中生自慰www网站 | 亚洲国产欧美日韩精品一区二区三区 | 成年美女黄网站色大免费全看 | 国产成人精品三级麻豆 | 中文字幕亚洲情99在线 | 亚洲 a v无 码免 费 成 人 a v | 国产午夜精品一区二区三区嫩草 | 在线观看欧美一区二区三区 | 麻花豆传媒剧国产免费mv在线 | 精品熟女少妇av免费观看 | 精品夜夜澡人妻无码av蜜桃 | 国产亚洲欧美日韩亚洲中文色 | 亚洲日韩av一区二区三区中文 | 日本肉体xxxx裸交 | 中文无码精品a∨在线观看不卡 | 天堂а√在线中文在线 | 人妻体内射精一区二区三四 | 成在人线av无码免观看麻豆 | 亚洲中文字幕在线无码一区二区 | av在线亚洲欧洲日产一区二区 | 西西人体www44rt大胆高清 | 特大黑人娇小亚洲女 | 日本在线高清不卡免费播放 | 亚洲色在线无码国产精品不卡 | 日本精品人妻无码77777 天堂一区人妻无码 | 高清国产亚洲精品自在久久 | 国产日产欧产精品精品app | 少妇愉情理伦片bd | 在线a亚洲视频播放在线观看 | 无码国产激情在线观看 | 色综合久久久久综合一本到桃花网 | 少妇太爽了在线观看 | 国产人妻大战黑人第1集 | 性欧美牲交在线视频 | 在线精品亚洲一区二区 | 一本大道久久东京热无码av | 久久精品一区二区三区四区 | 日日噜噜噜噜夜夜爽亚洲精品 | 男人扒开女人内裤强吻桶进去 | 久久综合狠狠综合久久综合88 | 免费乱码人妻系列无码专区 | 欧美性生交xxxxx久久久 | 曰韩无码二三区中文字幕 | 亚洲欧洲日本综合aⅴ在线 | 亚洲国产精品毛片av不卡在线 | 国产av无码专区亚洲awww | 欧美自拍另类欧美综合图片区 | 亚洲色无码一区二区三区 | 国产精品久久精品三级 | 麻豆国产人妻欲求不满 | 高清无码午夜福利视频 | 无码吃奶揉捏奶头高潮视频 | 国产精品无码mv在线观看 | 国产精品无套呻吟在线 | 亚洲娇小与黑人巨大交 | 久久国语露脸国产精品电影 | 97久久国产亚洲精品超碰热 | 99久久久国产精品无码免费 | 欧美国产日韩久久mv | 国产亚洲精品久久久闺蜜 | 国产精品鲁鲁鲁 | 国内丰满熟女出轨videos | 日韩少妇内射免费播放 | 欧美freesex黑人又粗又大 | 荡女精品导航 | 粗大的内捧猛烈进出视频 | 亚洲日韩中文字幕在线播放 | 精品一区二区不卡无码av | 特级做a爰片毛片免费69 | 日韩精品无码一本二本三本色 | 丰满少妇人妻久久久久久 | 久久伊人色av天堂九九小黄鸭 | 欧美丰满老熟妇xxxxx性 | 日韩成人一区二区三区在线观看 | 国产精品香蕉在线观看 | 在线视频网站www色 | 爆乳一区二区三区无码 | 日韩精品久久久肉伦网站 | 99精品无人区乱码1区2区3区 | 人人妻人人澡人人爽精品欧美 | 亚洲精品国产a久久久久久 | 在线成人www免费观看视频 | 亚洲a无码综合a国产av中文 | 午夜免费福利小电影 | 日日摸夜夜摸狠狠摸婷婷 | 亚洲人成网站色7799 | 男人和女人高潮免费网站 | 亚洲综合伊人久久大杳蕉 | 国语自产偷拍精品视频偷 | 无码人中文字幕 | 久久精品国产日本波多野结衣 | 无码av岛国片在线播放 | 国产人妻人伦精品1国产丝袜 | 日韩精品无码一本二本三本色 | 日本精品少妇一区二区三区 | 人妻少妇精品无码专区动漫 | 亚洲乱码日产精品bd | 疯狂三人交性欧美 | 人人妻在人人 | 99久久无码一区人妻 | 荫蒂被男人添的好舒服爽免费视频 | 一个人看的视频www在线 | 国产精品久久久午夜夜伦鲁鲁 | 国产真人无遮挡作爱免费视频 | 色综合久久久久综合一本到桃花网 | 天下第一社区视频www日本 | 牛和人交xxxx欧美 | 亚洲成av人片在线观看无码不卡 | 亚洲熟女一区二区三区 | 久久久精品人妻久久影视 | 成人免费视频在线观看 | 六十路熟妇乱子伦 | 欧美第一黄网免费网站 | 青青青手机频在线观看 | 久久久久亚洲精品中文字幕 | 天天躁日日躁狠狠躁免费麻豆 | 九一九色国产 | 激情人妻另类人妻伦 | 国产亚洲精品精品国产亚洲综合 | 亚洲欧美精品伊人久久 | 理论片87福利理论电影 | 亚洲日韩av一区二区三区四区 | 少妇被黑人到高潮喷出白浆 | 国产成人无码av片在线观看不卡 | 图片小说视频一区二区 | 日本护士xxxxhd少妇 | 久久精品中文字幕大胸 | 人妻中文无码久热丝袜 | 2020久久香蕉国产线看观看 | 黑人玩弄人妻中文在线 | 东京热男人av天堂 | 久久久久成人片免费观看蜜芽 | 精品亚洲韩国一区二区三区 | 99精品无人区乱码1区2区3区 | 天天躁夜夜躁狠狠是什么心态 | 丰满妇女强制高潮18xxxx | 无遮无挡爽爽免费视频 | 美女黄网站人色视频免费国产 | 伊人久久大香线蕉午夜 | 中文字幕无码免费久久9一区9 | 超碰97人人做人人爱少妇 | 亚洲色www成人永久网址 | 99久久人妻精品免费二区 | 亚洲欧美国产精品专区久久 | 国产成人人人97超碰超爽8 | 久久99精品国产麻豆蜜芽 | 久久人妻内射无码一区三区 | 西西人体www44rt大胆高清 | 国产农村乱对白刺激视频 | 亚洲中文字幕在线无码一区二区 | 精品欧美一区二区三区久久久 | 欧洲极品少妇 | 久久精品人人做人人综合试看 | 捆绑白丝粉色jk震动捧喷白浆 | 亚洲精品久久久久avwww潮水 | 亚洲欧洲无卡二区视頻 | 97精品国产97久久久久久免费 | 国产精品无码永久免费888 | 成人性做爰aaa片免费看不忠 | 欧美老熟妇乱xxxxx | 中文字幕无码免费久久9一区9 | 亚洲日韩乱码中文无码蜜桃臀网站 | 精品国产av色一区二区深夜久久 | 强辱丰满人妻hd中文字幕 | 精品人妻av区 | 黄网在线观看免费网站 | 特黄特色大片免费播放器图片 | 国产精品无码一区二区桃花视频 | 久久精品一区二区三区四区 | 久久久久免费看成人影片 | 俺去俺来也www色官网 | 最新国产麻豆aⅴ精品无码 | 国产精品亚洲五月天高清 | 成人动漫在线观看 | 久久综合九色综合欧美狠狠 | 国产精品毛片一区二区 | 亚洲一区二区三区播放 | 欧美乱妇无乱码大黄a片 | 亚洲中文字幕成人无码 | 午夜丰满少妇性开放视频 | 免费网站看v片在线18禁无码 | 无码一区二区三区在线观看 | 国产精品办公室沙发 | 久久综合给久久狠狠97色 | 色一情一乱一伦一视频免费看 | 久久精品人妻少妇一区二区三区 | 国产成人亚洲综合无码 | 欧美成人高清在线播放 | 亚洲中文无码av永久不收费 | 国产精品自产拍在线观看 | 中文字幕无码免费久久99 | 人妻无码αv中文字幕久久琪琪布 | 久久久久se色偷偷亚洲精品av | 亚洲国产成人a精品不卡在线 | 久久久av男人的天堂 | 欧美成人高清在线播放 | 九九在线中文字幕无码 | 少妇太爽了在线观看 | 蜜桃视频插满18在线观看 | 好男人社区资源 | 国内少妇偷人精品视频 | 99精品久久毛片a片 | 无码人妻丰满熟妇区毛片18 | 伊人久久大香线蕉av一区二区 | 国产乱人伦偷精品视频 | 久久精品中文闷骚内射 | 丁香啪啪综合成人亚洲 | 国产人妻精品一区二区三区不卡 | 精品无码成人片一区二区98 | 欧美日韩一区二区综合 | 牛和人交xxxx欧美 | 中文字幕无码av波多野吉衣 | 激情内射亚州一区二区三区爱妻 | 人人妻人人澡人人爽欧美一区九九 | 日本精品高清一区二区 | 熟女少妇人妻中文字幕 | 国产疯狂伦交大片 | 午夜丰满少妇性开放视频 | 亚洲色在线无码国产精品不卡 | 国产成人精品优优av | 全黄性性激高免费视频 | 国产又粗又硬又大爽黄老大爷视 | 无码国产色欲xxxxx视频 | 小鲜肉自慰网站xnxx | 中国女人内谢69xxxx | 免费播放一区二区三区 | 老熟妇仑乱视频一区二区 | 水蜜桃亚洲一二三四在线 | 2020久久香蕉国产线看观看 | 久久综合色之久久综合 | 亚洲色www成人永久网址 | 国产偷抇久久精品a片69 | 久久午夜夜伦鲁鲁片无码免费 | 人妻少妇精品久久 | 中文字幕乱码人妻二区三区 | 亚洲国产精品无码一区二区三区 | 国产av一区二区三区最新精品 | √天堂资源地址中文在线 | 水蜜桃亚洲一二三四在线 | 丁香啪啪综合成人亚洲 | 色一情一乱一伦一区二区三欧美 | 久久伊人色av天堂九九小黄鸭 | 免费无码肉片在线观看 | 国产精品人人妻人人爽 | 无码人妻出轨黑人中文字幕 | 国产精品久久久久9999小说 | 欧美国产亚洲日韩在线二区 | 丁香花在线影院观看在线播放 | 色综合久久久久综合一本到桃花网 | 人人澡人人妻人人爽人人蜜桃 | 三级4级全黄60分钟 | 日本精品高清一区二区 | 国内综合精品午夜久久资源 | 黑森林福利视频导航 | 亚洲色偷偷男人的天堂 | 久久久精品欧美一区二区免费 | 狂野欧美性猛交免费视频 | 国产午夜亚洲精品不卡下载 | 国精产品一品二品国精品69xx | 玩弄人妻少妇500系列视频 | 内射巨臀欧美在线视频 | 久久久久久a亚洲欧洲av冫 | 国产手机在线αⅴ片无码观看 | 97精品国产97久久久久久免费 | 亚洲色大成网站www | 国产偷抇久久精品a片69 | 国产明星裸体无码xxxx视频 | 国产综合在线观看 | 国产亚洲欧美日韩亚洲中文色 | 亚洲一区二区三区香蕉 | 暴力强奷在线播放无码 | 美女毛片一区二区三区四区 | 无码午夜成人1000部免费视频 | 久久久成人毛片无码 | 丝袜美腿亚洲一区二区 | 精品人妻中文字幕有码在线 | 久久久久久久人妻无码中文字幕爆 | 亚洲国产精品毛片av不卡在线 | 天天做天天爱天天爽综合网 | 亚洲大尺度无码无码专区 | 免费看少妇作爱视频 | 精品 日韩 国产 欧美 视频 | 精品久久久无码人妻字幂 | 精品欧美一区二区三区久久久 | 无码av最新清无码专区吞精 | 在线播放亚洲第一字幕 | 亚洲男人av香蕉爽爽爽爽 | 双乳奶水饱满少妇呻吟 | 无码人妻黑人中文字幕 | 亚洲成色www久久网站 | 欧美老人巨大xxxx做受 | 强辱丰满人妻hd中文字幕 | 天堂一区人妻无码 | 玩弄人妻少妇500系列视频 | 丰满少妇弄高潮了www | 国产激情无码一区二区app | 福利一区二区三区视频在线观看 | 久久久精品人妻久久影视 | 国产口爆吞精在线视频 | 台湾无码一区二区 | 国产精品美女久久久久av爽李琼 | 色欲综合久久中文字幕网 | 日日摸夜夜摸狠狠摸婷婷 | 国产高清av在线播放 | 国产精品二区一区二区aⅴ污介绍 | 亚洲乱码日产精品bd | 亚洲欧美国产精品久久 | 欧美zoozzooz性欧美 | 久久综合网欧美色妞网 | 久久久婷婷五月亚洲97号色 | 俺去俺来也在线www色官网 | 丰满少妇弄高潮了www | 久久久久人妻一区精品色欧美 | 欧美人与禽zoz0性伦交 | 亚洲男女内射在线播放 | 少妇太爽了在线观看 | 天堂一区人妻无码 | 2019午夜福利不卡片在线 | 国产成人精品无码播放 | 亚洲狠狠婷婷综合久久 | 欧美xxxx黑人又粗又长 | 亚洲国产欧美国产综合一区 | 精品国产麻豆免费人成网站 | 日本一卡2卡3卡四卡精品网站 | 久久精品国产亚洲精品 | 人人爽人人爽人人片av亚洲 | 日韩精品a片一区二区三区妖精 | 国产亚洲人成a在线v网站 | 精品欧洲av无码一区二区三区 | 最新国产麻豆aⅴ精品无码 | 水蜜桃亚洲一二三四在线 | 人人爽人人澡人人高潮 | 日韩精品一区二区av在线 | 无套内射视频囯产 | 两性色午夜免费视频 | 精品午夜福利在线观看 | 丝袜足控一区二区三区 | 精品aⅴ一区二区三区 | 亚洲精品一区三区三区在线观看 | 亚洲熟女一区二区三区 | 在线a亚洲视频播放在线观看 | 日本免费一区二区三区最新 | 亚洲男人av香蕉爽爽爽爽 | 日日摸日日碰夜夜爽av | 亚洲色欲色欲天天天www | 欧美人与牲动交xxxx | 无码av最新清无码专区吞精 | 精品一二三区久久aaa片 | 国产激情艳情在线看视频 | √8天堂资源地址中文在线 | 在线天堂新版最新版在线8 | 97精品国产97久久久久久免费 | 丰满人妻被黑人猛烈进入 | 天堂а√在线中文在线 | 日日碰狠狠丁香久燥 | 国产亚洲精品久久久久久国模美 | 亚洲人交乣女bbw | 欧美老熟妇乱xxxxx | 水蜜桃亚洲一二三四在线 | 精品久久综合1区2区3区激情 | 日日碰狠狠躁久久躁蜜桃 | 日本一区二区更新不卡 | 又粗又大又硬毛片免费看 | 中文无码精品a∨在线观看不卡 | 99久久精品日本一区二区免费 | 亚洲 a v无 码免 费 成 人 a v | 在线 国产 欧美 亚洲 天堂 | 国模大胆一区二区三区 | 性欧美牲交xxxxx视频 | 又大又硬又爽免费视频 | 国产乡下妇女做爰 | www国产精品内射老师 | 久久亚洲中文字幕精品一区 | 亚洲色大成网站www国产 | 樱花草在线播放免费中文 | 精品熟女少妇av免费观看 | 欧美丰满熟妇xxxx性ppx人交 | 日本精品人妻无码77777 天堂一区人妻无码 | 丁香啪啪综合成人亚洲 | 国产sm调教视频在线观看 | 精品无码国产一区二区三区av | 性啪啪chinese东北女人 | 无码一区二区三区在线观看 | 少妇人妻偷人精品无码视频 | 激情国产av做激情国产爱 | 精品久久8x国产免费观看 | 亚洲精品www久久久 | 丰满人妻被黑人猛烈进入 | 国产69精品久久久久app下载 | 欧美大屁股xxxxhd黑色 | 一个人免费观看的www视频 | 亚洲精品中文字幕乱码 | 国产精品手机免费 | 久久无码中文字幕免费影院蜜桃 | 国产在线aaa片一区二区99 | 乱人伦人妻中文字幕无码 | 无码国模国产在线观看 | 亚洲色偷偷男人的天堂 | 亚洲国产日韩a在线播放 | 亚洲精品中文字幕久久久久 | 2020久久超碰国产精品最新 | 青春草在线视频免费观看 | 欧美zoozzooz性欧美 | 精品国偷自产在线视频 | 免费观看黄网站 | 亚洲国产成人av在线观看 | 亚洲午夜福利在线观看 | 精品国精品国产自在久国产87 | 99久久精品日本一区二区免费 | 亚洲大尺度无码无码专区 | 又粗又大又硬又长又爽 | 无码av岛国片在线播放 | 久久无码专区国产精品s | 亚洲成av人片天堂网无码】 | 国产精品亚洲五月天高清 | 国产小呦泬泬99精品 | 亚洲国产成人a精品不卡在线 | √8天堂资源地址中文在线 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 亚洲啪av永久无码精品放毛片 | 日产精品高潮呻吟av久久 | 国产深夜福利视频在线 | 色欲久久久天天天综合网精品 | 亚洲七七久久桃花影院 | 国产明星裸体无码xxxx视频 | 野外少妇愉情中文字幕 | 亚洲一区二区三区香蕉 | 免费视频欧美无人区码 | av小次郎收藏 | www国产精品内射老师 | 亚洲精品欧美二区三区中文字幕 | 国产精品a成v人在线播放 | 久久久成人毛片无码 | 精品国产av色一区二区深夜久久 | 熟妇女人妻丰满少妇中文字幕 | 国产高清不卡无码视频 | 亚洲成av人影院在线观看 | 色婷婷久久一区二区三区麻豆 | 国产午夜无码视频在线观看 | 亚洲自偷精品视频自拍 | 亚洲高清偷拍一区二区三区 | 成人精品天堂一区二区三区 | 欧美激情综合亚洲一二区 | 亚洲综合无码久久精品综合 | 精品一区二区三区波多野结衣 | 99riav国产精品视频 | 亚洲自偷精品视频自拍 | 亚洲国产综合无码一区 | 日韩精品无码免费一区二区三区 | 精品国产一区二区三区四区 | 国产成人综合色在线观看网站 | 亚洲精品一区二区三区四区五区 | 国产人妻精品午夜福利免费 | 亚洲春色在线视频 | 99精品国产综合久久久久五月天 | ass日本丰满熟妇pics | 未满成年国产在线观看 | 欧美怡红院免费全部视频 | 无码人妻av免费一区二区三区 | 色综合久久久久综合一本到桃花网 | 亚洲国产精品无码一区二区三区 | 蜜桃视频插满18在线观看 | 成人无码视频免费播放 | 国产片av国语在线观看 | 欧美第一黄网免费网站 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 国产在线一区二区三区四区五区 | 未满小14洗澡无码视频网站 | 中文字幕亚洲情99在线 | 国产精品毛片一区二区 | 在线成人www免费观看视频 | 色情久久久av熟女人妻网站 | 久久久婷婷五月亚洲97号色 | 永久免费观看国产裸体美女 | 亚洲精品www久久久 | 丰满诱人的人妻3 | 大胆欧美熟妇xx | 97久久精品无码一区二区 | 免费国产成人高清在线观看网站 | 亚洲经典千人经典日产 | 性生交片免费无码看人 | 在教室伦流澡到高潮hnp视频 | 国产一区二区不卡老阿姨 | 欧洲美熟女乱又伦 | 波多野结衣av一区二区全免费观看 | 国産精品久久久久久久 | 中文字幕人成乱码熟女app | 亚洲国产精品美女久久久久 | 成人片黄网站色大片免费观看 | 欧美国产日产一区二区 | 国内综合精品午夜久久资源 | 日韩人妻系列无码专区 | av香港经典三级级 在线 | 白嫩日本少妇做爰 | 久久zyz资源站无码中文动漫 | 亚洲综合在线一区二区三区 | 国产成人久久精品流白浆 | 欧洲精品码一区二区三区免费看 | 国产精品igao视频网 | 久久久久人妻一区精品色欧美 | 人人爽人人澡人人高潮 | 三上悠亚人妻中文字幕在线 | 无码精品人妻一区二区三区av | 伊人久久大香线焦av综合影院 | 精品欧洲av无码一区二区三区 | 国产av无码专区亚洲awww | 精品无码一区二区三区的天堂 | 精品乱子伦一区二区三区 | 亚洲 欧美 激情 小说 另类 | 纯爱无遮挡h肉动漫在线播放 | 夜夜夜高潮夜夜爽夜夜爰爰 | 性色欲网站人妻丰满中文久久不卡 | 131美女爱做视频 | 亚洲一区二区三区香蕉 | 久久人人爽人人人人片 | 男女作爱免费网站 | 久久国内精品自在自线 | 日本大乳高潮视频在线观看 | 亚洲精品午夜无码电影网 | 又湿又紧又大又爽a视频国产 | 中文字幕人妻无码一区二区三区 | 亚洲国产综合无码一区 | 九月婷婷人人澡人人添人人爽 | 亚洲国产精品久久久久久 | 亚洲中文字幕乱码av波多ji | 色婷婷香蕉在线一区二区 | 99精品视频在线观看免费 | 国产亚洲精品久久久久久国模美 | 色狠狠av一区二区三区 | 久久国产精品_国产精品 | 国产乱子伦视频在线播放 | 午夜不卡av免费 一本久久a久久精品vr综合 | 久久久久久a亚洲欧洲av冫 | 中文字幕中文有码在线 | 国产精品igao视频网 | 又粗又大又硬又长又爽 | 水蜜桃色314在线观看 | 国产欧美精品一区二区三区 | 宝宝好涨水快流出来免费视频 | 娇妻被黑人粗大高潮白浆 | 国产绳艺sm调教室论坛 | 色五月丁香五月综合五月 | 久久久久久av无码免费看大片 | 99久久人妻精品免费二区 | 一本久久伊人热热精品中文字幕 | 免费中文字幕日韩欧美 | 日韩精品a片一区二区三区妖精 | 色综合久久中文娱乐网 | 无码纯肉视频在线观看 | 男女超爽视频免费播放 | 在线看片无码永久免费视频 | 亚洲精品中文字幕乱码 | 色婷婷欧美在线播放内射 | 久久精品人妻少妇一区二区三区 | 对白脏话肉麻粗话av | 国产日产欧产精品精品app | 日韩欧美群交p片內射中文 | 久久久久亚洲精品中文字幕 | 国内精品久久久久久中文字幕 | 欧美兽交xxxx×视频 | 欧美性猛交内射兽交老熟妇 | 男女爱爱好爽视频免费看 | 欧美人妻一区二区三区 | 国产真实伦对白全集 | 无码免费一区二区三区 | 亚洲国产一区二区三区在线观看 | 精品国产一区av天美传媒 | 亚洲成av人影院在线观看 | 国产亚洲日韩欧美另类第八页 | 无码精品人妻一区二区三区av | 性开放的女人aaa片 | 我要看www免费看插插视频 | 丁香啪啪综合成人亚洲 | 久久亚洲a片com人成 | 婷婷色婷婷开心五月四房播播 | 天堂无码人妻精品一区二区三区 | 成人免费视频视频在线观看 免费 | 免费观看激色视频网站 | 丰满护士巨好爽好大乳 | 精品国偷自产在线 | 日本一区二区三区免费播放 | 国产午夜福利亚洲第一 | 沈阳熟女露脸对白视频 | 性生交片免费无码看人 | 亚洲国产精品无码久久久久高潮 | 国产在线一区二区三区四区五区 | 国产精品久久国产三级国 | 国产精品高潮呻吟av久久 | 久久久中文久久久无码 | 国产成人av免费观看 | 亚洲理论电影在线观看 | 99久久婷婷国产综合精品青草免费 | 亚洲日韩中文字幕在线播放 | 亚洲中文无码av永久不收费 | 老熟女重囗味hdxx69 | 黑人大群体交免费视频 | 波多野结衣一区二区三区av免费 | 强辱丰满人妻hd中文字幕 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 欧美日韩亚洲国产精品 | 国产又爽又猛又粗的视频a片 | 男女猛烈xx00免费视频试看 | 一区二区传媒有限公司 | 精品成人av一区二区三区 | 亚洲综合无码久久精品综合 | 爽爽影院免费观看 | 丰满岳乱妇在线观看中字无码 | 久久久久av无码免费网 | 女人和拘做爰正片视频 | 亚洲 日韩 欧美 成人 在线观看 | 一个人看的www免费视频在线观看 | 国产精品久久久一区二区三区 | 在线天堂新版最新版在线8 | 亚洲一区二区三区四区 | 日本精品久久久久中文字幕 | 国产在线无码精品电影网 | 动漫av一区二区在线观看 | 蜜桃视频插满18在线观看 | aa片在线观看视频在线播放 | 亚洲色欲久久久综合网东京热 | 无码人妻av免费一区二区三区 | 国产精品亚洲综合色区韩国 | 久久综合久久自在自线精品自 | 久久久久se色偷偷亚洲精品av | 成人免费视频视频在线观看 免费 | 久久久精品成人免费观看 | 久久aⅴ免费观看 | 午夜时刻免费入口 | 最近中文2019字幕第二页 | 日韩av激情在线观看 | 国产绳艺sm调教室论坛 | av在线亚洲欧洲日产一区二区 | 久久无码人妻影院 | 天堂久久天堂av色综合 | 丰满人妻精品国产99aⅴ | 在线播放免费人成毛片乱码 | 学生妹亚洲一区二区 | 日韩欧美群交p片內射中文 | 国产精品美女久久久久av爽李琼 | 久久久久久国产精品无码下载 | 亚洲欧美日韩成人高清在线一区 | 伊人久久大香线焦av综合影院 | 日日鲁鲁鲁夜夜爽爽狠狠 | 精品人人妻人人澡人人爽人人 | 欧美 日韩 人妻 高清 中文 | 精品无码一区二区三区爱欲 | 初尝人妻少妇中文字幕 | 久久精品女人天堂av免费观看 | 在线a亚洲视频播放在线观看 | 色婷婷久久一区二区三区麻豆 | 亚洲熟悉妇女xxx妇女av | 性欧美牲交xxxxx视频 | 日本爽爽爽爽爽爽在线观看免 | 亚洲人亚洲人成电影网站色 | 中文字幕人妻无码一夲道 | 国产精品沙发午睡系列 | 色综合视频一区二区三区 | 色老头在线一区二区三区 | av在线亚洲欧洲日产一区二区 | 特级做a爰片毛片免费69 | 无码国产乱人伦偷精品视频 | 黑人粗大猛烈进出高潮视频 | 伊在人天堂亚洲香蕉精品区 | 国产亚洲美女精品久久久2020 | 久久久成人毛片无码 | 无码人妻精品一区二区三区下载 | 国产suv精品一区二区五 | 亚洲 高清 成人 动漫 | 97久久精品无码一区二区 | 在线a亚洲视频播放在线观看 | 亚洲精品美女久久久久久久 | 人人妻人人澡人人爽人人精品 | 久久久久久久久888 | 露脸叫床粗话东北少妇 | 日韩av无码中文无码电影 | 99久久婷婷国产综合精品青草免费 | 日韩av无码一区二区三区不卡 | 国产国产精品人在线视 | 少妇无码吹潮 | 国产亚洲欧美日韩亚洲中文色 | 天堂亚洲免费视频 | 久精品国产欧美亚洲色aⅴ大片 | 精品国精品国产自在久国产87 | 国精产品一区二区三区 | 欧美阿v高清资源不卡在线播放 | 老熟女重囗味hdxx69 | 99riav国产精品视频 | 欧美刺激性大交 | 久久亚洲中文字幕精品一区 | 男女猛烈xx00免费视频试看 | 亚洲日本在线电影 | 国产高清av在线播放 | 国产精品嫩草久久久久 | 国产成人精品久久亚洲高清不卡 | 粗大的内捧猛烈进出视频 | 无码毛片视频一区二区本码 | 国产香蕉尹人视频在线 | 成人免费无码大片a毛片 | 欧美丰满少妇xxxx性 | 精品无码一区二区三区的天堂 | 欧美猛少妇色xxxxx | 国产av无码专区亚洲awww | a在线观看免费网站大全 | 国产精品久久久久无码av色戒 | 精品人妻中文字幕有码在线 | 美女张开腿让人桶 | 国产精品高潮呻吟av久久4虎 | 欧洲美熟女乱又伦 | 久久精品国产大片免费观看 | 欧美丰满熟妇xxxx | 国产精品久久久久久亚洲影视内衣 | 国产熟女一区二区三区四区五区 | 久久国语露脸国产精品电影 | 成人无码视频在线观看网站 | 无码帝国www无码专区色综合 | 久久久精品欧美一区二区免费 | 日产国产精品亚洲系列 | 高潮喷水的毛片 | 国产两女互慰高潮视频在线观看 | 国产区女主播在线观看 | 久久久国产精品无码免费专区 | 无码毛片视频一区二区本码 | 黑森林福利视频导航 | 丰满少妇人妻久久久久久 | 无码帝国www无码专区色综合 | 黑人粗大猛烈进出高潮视频 | 在线欧美精品一区二区三区 | 成人女人看片免费视频放人 | 少妇厨房愉情理9仑片视频 | 亚洲 日韩 欧美 成人 在线观看 | 少妇性l交大片 | 亚洲精品国产精品乱码不卡 | v一区无码内射国产 | 风流少妇按摩来高潮 | 国产97人人超碰caoprom | 亚洲熟悉妇女xxx妇女av | 国产亚洲精品久久久久久久久动漫 | 日韩亚洲欧美中文高清在线 | 久久综合久久自在自线精品自 | 国产精品无码久久av | 国产高清av在线播放 | 乱码午夜-极国产极内射 | 午夜福利一区二区三区在线观看 | 色综合久久88色综合天天 | 久久人妻内射无码一区三区 | 国产高清av在线播放 | 成人免费视频视频在线观看 免费 | 国产亚洲视频中文字幕97精品 | 婷婷综合久久中文字幕蜜桃三电影 | 欧美激情综合亚洲一二区 | 国产乱人伦av在线无码 | 暴力强奷在线播放无码 | 国语精品一区二区三区 | 狠狠色噜噜狠狠狠狠7777米奇 | 中文字幕无码免费久久9一区9 | 精品人人妻人人澡人人爽人人 | 蜜桃视频插满18在线观看 | 野外少妇愉情中文字幕 | 美女黄网站人色视频免费国产 | 亚洲无人区午夜福利码高清完整版 | 国产特级毛片aaaaaaa高清 | 国内精品人妻无码久久久影院蜜桃 | 久久99热只有频精品8 | 人人爽人人澡人人人妻 | 免费网站看v片在线18禁无码 | 男人和女人高潮免费网站 | 国产肉丝袜在线观看 | 久久综合香蕉国产蜜臀av | 一本久久a久久精品亚洲 | 久久99精品国产麻豆蜜芽 | 精品国产一区二区三区四区 | 中国大陆精品视频xxxx | 最新国产麻豆aⅴ精品无码 | 中文字幕无码免费久久99 | 亚洲日韩av片在线观看 | 国产猛烈高潮尖叫视频免费 | 久久人人爽人人爽人人片av高清 | 亚洲中文字幕在线观看 | 乱人伦人妻中文字幕无码 | 亚洲欧美国产精品久久 | 在线观看免费人成视频 | 亚洲の无码国产の无码影院 | 亚洲国产精品一区二区第一页 | 久久综合久久自在自线精品自 | 老司机亚洲精品影院 | 乱码午夜-极国产极内射 | 99久久亚洲精品无码毛片 | 久久人人爽人人爽人人片av高清 | 国产熟女一区二区三区四区五区 | 无套内谢的新婚少妇国语播放 | 色欲综合久久中文字幕网 | 国产午夜视频在线观看 | 国产一区二区三区四区五区加勒比 | 久久国语露脸国产精品电影 | 国产精品毛多多水多 | 一本久道久久综合狠狠爱 | 精品成在人线av无码免费看 | 午夜丰满少妇性开放视频 | 玩弄少妇高潮ⅹxxxyw | 俺去俺来也www色官网 | 内射老妇bbwx0c0ck | 2020最新国产自产精品 | 男女下面进入的视频免费午夜 | 一本大道久久东京热无码av | 麻豆精品国产精华精华液好用吗 | 精品久久久中文字幕人妻 | 久久综合给久久狠狠97色 | 妺妺窝人体色www在线小说 | 激情综合激情五月俺也去 | 国产综合久久久久鬼色 | 亚洲日韩av一区二区三区四区 | 乌克兰少妇性做爰 | 99久久精品日本一区二区免费 | 国产精品亚洲lv粉色 | 国产精品久久久久久久影院 | 又粗又大又硬毛片免费看 | 中文字幕av日韩精品一区二区 | 日韩av无码一区二区三区不卡 | 天海翼激烈高潮到腰振不止 | 伊人久久大香线蕉午夜 | 亚洲精品国偷拍自产在线观看蜜桃 | 少妇高潮一区二区三区99 | 国产三级精品三级男人的天堂 | 国产无遮挡又黄又爽免费视频 | 欧美日韩在线亚洲综合国产人 | 日本一卡二卡不卡视频查询 | 任你躁国产自任一区二区三区 | 曰本女人与公拘交酡免费视频 | 国产精品久久久一区二区三区 | 精品国产乱码久久久久乱码 | 无码av免费一区二区三区试看 | 久久久久久a亚洲欧洲av冫 | 亚洲中文字幕无码中文字在线 | 九月婷婷人人澡人人添人人爽 | 好爽又高潮了毛片免费下载 | 又湿又紧又大又爽a视频国产 | 久久亚洲中文字幕无码 | 精品无码一区二区三区的天堂 | 亚洲娇小与黑人巨大交 | 色欲av亚洲一区无码少妇 | 亚洲精品国产a久久久久久 | 欧美人与禽zoz0性伦交 | 国产精品久久久久久亚洲影视内衣 | 中国大陆精品视频xxxx | 亚无码乱人伦一区二区 | 任你躁在线精品免费 | 欧美亚洲国产一区二区三区 | 2020最新国产自产精品 | 亚洲综合另类小说色区 | ass日本丰满熟妇pics | 99er热精品视频 | www一区二区www免费 | 99国产欧美久久久精品 | 精品乱子伦一区二区三区 | 日日干夜夜干 | 色情久久久av熟女人妻网站 | 亚洲中文字幕在线观看 | 国产超碰人人爽人人做人人添 | 99视频精品全部免费免费观看 | 久久亚洲中文字幕无码 | 在线精品亚洲一区二区 | 国产成人综合在线女婷五月99播放 | 国产精品久久久久影院嫩草 | 四虎影视成人永久免费观看视频 | 欧美人与物videos另类 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | www成人国产高清内射 | 一二三四社区在线中文视频 | 人妻少妇被猛烈进入中文字幕 | 中文字幕人妻无码一区二区三区 | 日韩 欧美 动漫 国产 制服 | 日本又色又爽又黄的a片18禁 | 荫蒂被男人添的好舒服爽免费视频 | 无码人妻av免费一区二区三区 | 天堂在线观看www | 国产国语老龄妇女a片 | 国产精品内射视频免费 | 老司机亚洲精品影院 | 亚洲成av人片在线观看无码不卡 | 国产内射老熟女aaaa | 久久久久久久人妻无码中文字幕爆 | 久久精品一区二区三区四区 | 99久久久无码国产精品免费 | 秋霞特色aa大片 | 99久久久国产精品无码免费 | 国产精品鲁鲁鲁 | 欧美 丝袜 自拍 制服 另类 | 国产无遮挡吃胸膜奶免费看 | 免费无码的av片在线观看 | 精品国产一区av天美传媒 | www国产精品内射老师 | 亚洲精品久久久久中文第一幕 | 国产高清不卡无码视频 | 亚洲自偷精品视频自拍 | 国产成人午夜福利在线播放 | 波多野结衣av一区二区全免费观看 | 精品国产一区av天美传媒 | 国产精品亚洲综合色区韩国 | 牛和人交xxxx欧美 | 娇妻被黑人粗大高潮白浆 | 久久99精品国产.久久久久 | 久久国产精品二国产精品 | 无遮无挡爽爽免费视频 | 国产区女主播在线观看 | 中文字幕无码视频专区 | 精品久久久久久亚洲精品 | 亚洲一区二区三区无码久久 | 中文字幕人妻丝袜二区 | 沈阳熟女露脸对白视频 | 亚洲国产精品一区二区第一页 | 精品日本一区二区三区在线观看 | 131美女爱做视频 | 久久亚洲国产成人精品性色 | 西西人体www44rt大胆高清 | 天天躁日日躁狠狠躁免费麻豆 | 国产明星裸体无码xxxx视频 | 久久久婷婷五月亚洲97号色 | 玩弄中年熟妇正在播放 | 一个人看的视频www在线 | 精品一区二区三区波多野结衣 | 婷婷五月综合缴情在线视频 | 色情久久久av熟女人妻网站 | 天天av天天av天天透 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 国产精品久久久久无码av色戒 | 日韩视频 中文字幕 视频一区 | 初尝人妻少妇中文字幕 | 久久婷婷五月综合色国产香蕉 | 无码乱肉视频免费大全合集 | 99国产精品白浆在线观看免费 | 免费无码av一区二区 | 狠狠色丁香久久婷婷综合五月 | 国产高清不卡无码视频 | 奇米影视7777久久精品人人爽 | 中文字幕无码视频专区 | 国产av一区二区精品久久凹凸 | 日本va欧美va欧美va精品 | 夜精品a片一区二区三区无码白浆 | 国产婷婷色一区二区三区在线 | 欧美成人家庭影院 | 成人欧美一区二区三区黑人 | 日韩亚洲欧美精品综合 | 免费人成在线观看网站 | 女人被爽到呻吟gif动态图视看 | 人妻尝试又大又粗久久 | 国产一区二区三区影院 | 亚洲s色大片在线观看 | 国产成人无码a区在线观看视频app | 在线观看国产一区二区三区 | 黑森林福利视频导航 | 99久久人妻精品免费一区 | 狠狠色噜噜狠狠狠7777奇米 | 欧美zoozzooz性欧美 | 国产激情一区二区三区 | 国产一区二区三区日韩精品 | 玩弄人妻少妇500系列视频 | 亚洲国产精品成人久久蜜臀 | 色婷婷综合中文久久一本 | 亚洲春色在线视频 | 人人爽人人爽人人片av亚洲 | 特级做a爰片毛片免费69 | 国产欧美精品一区二区三区 | 扒开双腿疯狂进出爽爽爽视频 | 嫩b人妻精品一区二区三区 | 日本一卡2卡3卡四卡精品网站 | 国产麻豆精品精东影业av网站 | 国产激情精品一区二区三区 | 特级做a爰片毛片免费69 | 人人爽人人澡人人高潮 | 亚洲人成无码网www | 亚洲精品午夜国产va久久成人 | 久久精品国产一区二区三区肥胖 | 色欲av亚洲一区无码少妇 | 亚洲色偷偷偷综合网 | 成人av无码一区二区三区 | 日日干夜夜干 | 国产成人av免费观看 | 牲欲强的熟妇农村老妇女视频 | 青草视频在线播放 | 国产熟女一区二区三区四区五区 | 亚洲s码欧洲m码国产av | 好男人社区资源 | 在线观看国产午夜福利片 | 国产深夜福利视频在线 | 女人被爽到呻吟gif动态图视看 | 国产香蕉尹人视频在线 | 精品久久久久香蕉网 | 亚洲精品国偷拍自产在线观看蜜桃 | 亚洲精品一区二区三区四区五区 | 成人欧美一区二区三区黑人免费 | 人人妻人人澡人人爽欧美一区九九 | 一二三四社区在线中文视频 | 亚洲人亚洲人成电影网站色 | 全球成人中文在线 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 国产综合久久久久鬼色 | 亚洲人成影院在线观看 | 黄网在线观看免费网站 | 装睡被陌生人摸出水好爽 | 国产亚洲美女精品久久久2020 | 高潮毛片无遮挡高清免费视频 | 国产精品怡红院永久免费 | 免费观看激色视频网站 | 欧美日韩久久久精品a片 | 久久无码人妻影院 | 日本一区二区更新不卡 | 性色av无码免费一区二区三区 | 欧美日本日韩 | 欧美精品在线观看 | 欧美放荡的少妇 | 思思久久99热只有频精品66 | 国产亚洲精品久久久闺蜜 | 欧美zoozzooz性欧美 | 久久久无码中文字幕久... | 久久久av男人的天堂 | 色综合久久久无码网中文 | 亚洲中文字幕在线无码一区二区 | 欧美高清在线精品一区 | 国产av一区二区精品久久凹凸 | 国産精品久久久久久久 | 女高中生第一次破苞av | 久久国产自偷自偷免费一区调 | 国产色视频一区二区三区 | 欧美人与牲动交xxxx | 色诱久久久久综合网ywww | 中文字幕av无码一区二区三区电影 | 在线播放亚洲第一字幕 | 乱人伦中文视频在线观看 | 欧美日韩在线亚洲综合国产人 | 亚洲经典千人经典日产 | 亚洲色在线无码国产精品不卡 | 亚洲国产av美女网站 | 妺妺窝人体色www婷婷 | 在线观看免费人成视频 | 欧美老熟妇乱xxxxx | 精品人人妻人人澡人人爽人人 | 思思久久99热只有频精品66 | 亚洲日本va中文字幕 | 国产成人精品一区二区在线小狼 | 国产色xx群视频射精 | 欧美人与牲动交xxxx | 图片小说视频一区二区 | 亚洲伊人久久精品影院 | 老太婆性杂交欧美肥老太 | 任你躁在线精品免费 | 欧美人与善在线com | 日日麻批免费40分钟无码 | 欧洲极品少妇 | 色五月五月丁香亚洲综合网 | 在线观看国产一区二区三区 | 久久亚洲精品中文字幕无男同 | 黑人玩弄人妻中文在线 | 奇米影视7777久久精品 | 亚洲国产日韩a在线播放 | 成人aaa片一区国产精品 | 国产无套内射久久久国产 | 成人影院yy111111在线观看 | 波多野结衣 黑人 | 网友自拍区视频精品 | 福利一区二区三区视频在线观看 | 成人精品一区二区三区中文字幕 | 最近免费中文字幕中文高清百度 | 久久综合久久自在自线精品自 | 久久无码专区国产精品s | 亚洲自偷自偷在线制服 | 中文字幕日韩精品一区二区三区 | 两性色午夜免费视频 | 国产精品.xx视频.xxtv | 亚洲午夜无码久久 | 人妻少妇被猛烈进入中文字幕 | 国产超级va在线观看视频 | 免费无码的av片在线观看 | 色狠狠av一区二区三区 | 狠狠色丁香久久婷婷综合五月 | 国产精品手机免费 | 无码福利日韩神码福利片 | 亚洲狠狠婷婷综合久久 | 欧美日韩一区二区免费视频 | 天天摸天天透天天添 | 内射老妇bbwx0c0ck | 熟妇女人妻丰满少妇中文字幕 | 国产精品香蕉在线观看 | 亚洲中文字幕在线无码一区二区 | 日本丰满护士爆乳xxxx | 久久精品国产精品国产精品污 | 帮老师解开蕾丝奶罩吸乳网站 | 老太婆性杂交欧美肥老太 | 国产女主播喷水视频在线观看 | 国产无遮挡吃胸膜奶免费看 | 久久伊人色av天堂九九小黄鸭 | 99久久久国产精品无码免费 | 婷婷丁香五月天综合东京热 | 亚洲色偷偷男人的天堂 | 精品国产麻豆免费人成网站 | 欧美黑人乱大交 | 精品国精品国产自在久国产87 | 麻豆蜜桃av蜜臀av色欲av | 亚洲色www成人永久网址 | 欧美激情综合亚洲一二区 | 捆绑白丝粉色jk震动捧喷白浆 | 亚洲 a v无 码免 费 成 人 a v | 2019nv天堂香蕉在线观看 | 国产偷国产偷精品高清尤物 | 成人欧美一区二区三区 | 国产成人无码a区在线观看视频app | 秋霞成人午夜鲁丝一区二区三区 | 老司机亚洲精品影院无码 | 又色又爽又黄的美女裸体网站 | 国产精品久久久久影院嫩草 | 中国大陆精品视频xxxx | 天堂久久天堂av色综合 | 粉嫩少妇内射浓精videos | 荫蒂被男人添的好舒服爽免费视频 | 18禁黄网站男男禁片免费观看 | 老子影院午夜精品无码 | www国产亚洲精品久久网站 | 日日鲁鲁鲁夜夜爽爽狠狠 | 丝袜人妻一区二区三区 | 99久久久无码国产aaa精品 | 成人毛片一区二区 | 亚洲伊人久久精品影院 | a片在线免费观看 | 波多野42部无码喷潮在线 | 蜜桃无码一区二区三区 | 天海翼激烈高潮到腰振不止 | 东京热男人av天堂 | 亚洲中文字幕成人无码 | av在线亚洲欧洲日产一区二区 | 色欲av亚洲一区无码少妇 | 成人性做爰aaa片免费看 | 好爽又高潮了毛片免费下载 | 夜夜躁日日躁狠狠久久av | 伊人久久大香线蕉av一区二区 | 精品无码国产一区二区三区av | 青青青爽视频在线观看 | 久久人人爽人人爽人人片ⅴ | 无码人妻丰满熟妇区五十路百度 | 国产亚洲精品久久久久久 | 国产精品亚洲专区无码不卡 | 亚拍精品一区二区三区探花 | 欧美人与牲动交xxxx | 无码免费一区二区三区 | 亚洲欧美日韩国产精品一区二区 | 在线播放无码字幕亚洲 | 日日摸日日碰夜夜爽av | 小泽玛莉亚一区二区视频在线 | 国内精品久久毛片一区二区 | 麻豆md0077饥渴少妇 | 国产又粗又硬又大爽黄老大爷视 | 久久zyz资源站无码中文动漫 | 性开放的女人aaa片 | 久久人人爽人人爽人人片av高清 | 国产精品美女久久久网av | 国产精品嫩草久久久久 | 亚洲成a人片在线观看无码 | 十八禁视频网站在线观看 | 欧美日韩色另类综合 | 中文字幕无线码免费人妻 | 在线天堂新版最新版在线8 | 亚洲乱码中文字幕在线 | 欧美日韩色另类综合 | 中文精品无码中文字幕无码专区 | 亚洲精品一区二区三区在线 | 亚洲午夜无码久久 | 娇妻被黑人粗大高潮白浆 | 夜精品a片一区二区三区无码白浆 | 在线 国产 欧美 亚洲 天堂 | 亚洲精品国产a久久久久久 | 女人被男人躁得好爽免费视频 | 日产精品99久久久久久 | 国产精品va在线播放 | 久久久久成人精品免费播放动漫 | 成人性做爰aaa片免费看不忠 | 日韩欧美成人免费观看 | 亚洲欧美国产精品久久 | 欧美丰满熟妇xxxx性ppx人交 | 免费人成在线观看网站 | 伊人久久婷婷五月综合97色 | 蜜桃av抽搐高潮一区二区 | 18精品久久久无码午夜福利 | 国产精品无码mv在线观看 | 国产成人av免费观看 | 国产成人无码av片在线观看不卡 | 无码人妻出轨黑人中文字幕 | 露脸叫床粗话东北少妇 | 国产精品美女久久久久av爽李琼 | 无码吃奶揉捏奶头高潮视频 | 久久zyz资源站无码中文动漫 | 波多野结衣 黑人 | 免费观看的无遮挡av | 成人精品一区二区三区中文字幕 | 少妇性l交大片欧洲热妇乱xxx | a片免费视频在线观看 | 国产黄在线观看免费观看不卡 | 99re在线播放 | 日日摸日日碰夜夜爽av | 国产人妻久久精品二区三区老狼 | 免费观看黄网站 | 日本www一道久久久免费榴莲 | 国产成人综合在线女婷五月99播放 | 18精品久久久无码午夜福利 | 亚洲最大成人网站 | 日本丰满护士爆乳xxxx | 波多野42部无码喷潮在线 | 国产精品爱久久久久久久 | 欧美 亚洲 国产 另类 | 婷婷丁香六月激情综合啪 | 成人欧美一区二区三区 | 欧美亚洲国产一区二区三区 | 国产成人综合色在线观看网站 | 日本免费一区二区三区最新 | 国产麻豆精品精东影业av网站 | 亚洲欧洲中文日韩av乱码 | 国产精品鲁鲁鲁 | 装睡被陌生人摸出水好爽 | 国产偷抇久久精品a片69 | 久久国产36精品色熟妇 | 国产无遮挡吃胸膜奶免费看 | 中文字幕人妻无码一夲道 | 欧美熟妇另类久久久久久多毛 | 欧美黑人性暴力猛交喷水 | 日本又色又爽又黄的a片18禁 | 久久久久久亚洲精品a片成人 | 久久精品视频在线看15 | 日本va欧美va欧美va精品 | 免费男性肉肉影院 | 亚洲中文字幕在线无码一区二区 | 亚洲自偷自偷在线制服 | 国产香蕉尹人综合在线观看 | 99久久婷婷国产综合精品青草免费 | 久久人妻内射无码一区三区 | 国产国语老龄妇女a片 | 麻豆成人精品国产免费 | 98国产精品综合一区二区三区 | 嫩b人妻精品一区二区三区 | 色一情一乱一伦 | 无码精品国产va在线观看dvd | 亚洲中文字幕无码一久久区 | 国产亚洲视频中文字幕97精品 | 国产综合久久久久鬼色 | 人妻天天爽夜夜爽一区二区 | 亚洲日本va午夜在线电影 | 亚洲国产精品毛片av不卡在线 | 欧美人与牲动交xxxx | 国产成人精品必看 | 成人欧美一区二区三区黑人免费 | 久9re热视频这里只有精品 | 亚洲熟妇色xxxxx欧美老妇y | 精品国偷自产在线视频 | 日日天日日夜日日摸 | аⅴ资源天堂资源库在线 | 国产成人久久精品流白浆 | 97夜夜澡人人双人人人喊 | 日本大乳高潮视频在线观看 | 性生交片免费无码看人 | 国产精品高潮呻吟av久久4虎 | 亚洲精品国偷拍自产在线观看蜜桃 | 亚洲一区二区三区无码久久 | 蜜臀av无码人妻精品 | 狠狠噜狠狠狠狠丁香五月 | 精品一区二区三区无码免费视频 | 大地资源网第二页免费观看 | 色欲久久久天天天综合网精品 | 波多野结衣乳巨码无在线观看 | 国产偷国产偷精品高清尤物 | 国产亚洲欧美在线专区 | 性欧美大战久久久久久久 | 国产 浪潮av性色四虎 | 国产人妻久久精品二区三区老狼 | 夜精品a片一区二区三区无码白浆 | 国产欧美亚洲精品a | 一本久道高清无码视频 | 午夜男女很黄的视频 | 天堂无码人妻精品一区二区三区 | 97色伦图片97综合影院 | 亚洲天堂2017无码 | 日韩精品成人一区二区三区 | аⅴ资源天堂资源库在线 | 中国大陆精品视频xxxx | 国产成人无码区免费内射一片色欲 | 国产亚洲精品久久久久久久久动漫 | 人人妻人人澡人人爽欧美一区 | 国产suv精品一区二区五 | 东京热无码av男人的天堂 | 人人妻人人澡人人爽精品欧美 | 无码精品国产va在线观看dvd | 天堂久久天堂av色综合 | 精品国产麻豆免费人成网站 | 中国大陆精品视频xxxx | 精品国产一区av天美传媒 | 黄网在线观看免费网站 | 四虎国产精品一区二区 | 婷婷丁香五月天综合东京热 | 亚洲色偷偷偷综合网 | 日本一区二区三区免费高清 | 无码帝国www无码专区色综合 | 亚洲高清偷拍一区二区三区 | 西西人体www44rt大胆高清 | 伊人久久大香线蕉亚洲 | 成 人 网 站国产免费观看 | 成人欧美一区二区三区 | 久久99精品国产麻豆 | 狂野欧美激情性xxxx | 综合激情五月综合激情五月激情1 | 国产肉丝袜在线观看 | 免费观看的无遮挡av | 国产成人精品三级麻豆 | 蜜桃av抽搐高潮一区二区 | 六月丁香婷婷色狠狠久久 | 国产精品成人av在线观看 | 久久精品国产99精品亚洲 | 色欲久久久天天天综合网精品 | 国产后入清纯学生妹 | 人妻插b视频一区二区三区 | 久久综合网欧美色妞网 | 四虎影视成人永久免费观看视频 | 亚洲国产高清在线观看视频 | 狠狠色噜噜狠狠狠狠7777米奇 | 久久国语露脸国产精品电影 | 亚洲成色www久久网站 | 又粗又大又硬又长又爽 | 理论片87福利理论电影 | 国产精品久免费的黄网站 | 色五月五月丁香亚洲综合网 | 日本欧美一区二区三区乱码 | 久久久国产精品无码免费专区 | 伊人久久大香线焦av综合影院 | 日韩av无码中文无码电影 | 国产办公室秘书无码精品99 | 国产美女极度色诱视频www | 国产免费无码一区二区视频 | 国产免费观看黄av片 | 亚洲国产精品久久久久久 | 亚洲日韩av片在线观看 | 天天躁夜夜躁狠狠是什么心态 | 丰腴饱满的极品熟妇 | 久久久久久久久888 | 日韩欧美群交p片內射中文 | 国产精品亚洲一区二区三区喷水 | 久久亚洲a片com人成 | 欧美亚洲日韩国产人成在线播放 | 99riav国产精品视频 | aⅴ亚洲 日韩 色 图网站 播放 | 精品熟女少妇av免费观看 | 色婷婷香蕉在线一区二区 | 亚洲欧美日韩国产精品一区二区 | 国产午夜视频在线观看 | 午夜男女很黄的视频 | 亚洲一区二区三区无码久久 | 国内丰满熟女出轨videos | 天堂亚洲2017在线观看 | 国产莉萝无码av在线播放 | 成人片黄网站色大片免费观看 | 99久久久无码国产精品免费 | 三上悠亚人妻中文字幕在线 | 精品无码国产一区二区三区av | 99精品久久毛片a片 | 国产做国产爱免费视频 | 久久精品女人天堂av免费观看 | 午夜不卡av免费 一本久久a久久精品vr综合 | 黑人巨大精品欧美一区二区 | 日韩在线不卡免费视频一区 | 高清国产亚洲精品自在久久 | 人人妻人人澡人人爽欧美一区九九 | 国产成人精品三级麻豆 | 性欧美大战久久久久久久 | 中文久久乱码一区二区 | 久久精品人人做人人综合试看 | 伊人色综合久久天天小片 | 成人一区二区免费视频 | 国产精品第一区揄拍无码 | 亚洲精品久久久久久久久久久 | 最新版天堂资源中文官网 | 精品国精品国产自在久国产87 | 在线观看欧美一区二区三区 | 骚片av蜜桃精品一区 | aⅴ亚洲 日韩 色 图网站 播放 | 色窝窝无码一区二区三区色欲 | 久久精品国产日本波多野结衣 | 香港三级日本三级妇三级 | 无码播放一区二区三区 | 97资源共享在线视频 | 377p欧洲日本亚洲大胆 | 国产成人无码区免费内射一片色欲 | 亚洲一区二区三区无码久久 | 天天燥日日燥 | 精品偷拍一区二区三区在线看 | 亚洲另类伦春色综合小说 | 日韩亚洲欧美中文高清在线 | 性啪啪chinese东北女人 | 国产亚洲精品久久久久久久久动漫 | 国产欧美熟妇另类久久久 | 男女爱爱好爽视频免费看 | 激情内射亚州一区二区三区爱妻 | 精品夜夜澡人妻无码av蜜桃 | 成人av无码一区二区三区 | 日日夜夜撸啊撸 | 久久精品视频在线看15 | 国产精品无码一区二区桃花视频 | 乌克兰少妇性做爰 | 国产特级毛片aaaaaaa高清 | 亚洲aⅴ无码成人网站国产app | 久久久亚洲欧洲日产国码αv | 水蜜桃亚洲一二三四在线 | 国产在线精品一区二区三区直播 | 97夜夜澡人人爽人人喊中国片 | 67194成是人免费无码 | 自拍偷自拍亚洲精品被多人伦好爽 | 精品国偷自产在线视频 | 日韩人妻少妇一区二区三区 | 国产乱人伦av在线无码 | 久久综合色之久久综合 | 亚洲无人区午夜福利码高清完整版 | 欧美野外疯狂做受xxxx高潮 | 扒开双腿疯狂进出爽爽爽视频 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 亚洲国产欧美在线成人 | 国产97色在线 | 免 | 性欧美牲交在线视频 | 亚洲一区二区三区播放 | 亚洲成a人片在线观看日本 | 免费国产成人高清在线观看网站 | 精品人妻人人做人人爽 | 大地资源中文第3页 | 97精品国产97久久久久久免费 | 午夜精品久久久久久久久 | 日日碰狠狠躁久久躁蜜桃 | 日韩人妻少妇一区二区三区 | 日韩精品无码一本二本三本色 | 妺妺窝人体色www婷婷 | 色婷婷久久一区二区三区麻豆 | 亚洲精品美女久久久久久久 | 欧美精品国产综合久久 | 精品成人av一区二区三区 | 国产精品国产自线拍免费软件 | 欧洲欧美人成视频在线 | 久久久中文字幕日本无吗 | 国产精品va在线播放 | 人妻熟女一区 | 久久久久人妻一区精品色欧美 | 少妇人妻大乳在线视频 | 夜精品a片一区二区三区无码白浆 | 久久久久久九九精品久 | 97久久精品无码一区二区 | 人人妻人人澡人人爽欧美一区 | 日本熟妇大屁股人妻 | 日本精品人妻无码77777 天堂一区人妻无码 | 99久久精品日本一区二区免费 | 亚洲精品综合一区二区三区在线 | 狠狠色丁香久久婷婷综合五月 | 天天综合网天天综合色 | 我要看www免费看插插视频 | 久久久中文久久久无码 | 少妇的肉体aa片免费 | 国产偷国产偷精品高清尤物 | 亚洲伊人久久精品影院 | 国产艳妇av在线观看果冻传媒 | 亚洲一区二区三区在线观看网站 | 亚洲自偷精品视频自拍 | 人人爽人人澡人人高潮 | 国产精品va在线观看无码 | 国产成人无码区免费内射一片色欲 | 伊人久久大香线蕉av一区二区 | 天天av天天av天天透 | 在线观看免费人成视频 | 啦啦啦www在线观看免费视频 | 无码av最新清无码专区吞精 | 人妻尝试又大又粗久久 | 麻豆果冻传媒2021精品传媒一区下载 | 97夜夜澡人人爽人人喊中国片 | 日韩精品a片一区二区三区妖精 | 亚洲精品一区二区三区大桥未久 | 久久人人爽人人人人片 | 玩弄人妻少妇500系列视频 | 精品久久久无码人妻字幂 | 久久久精品欧美一区二区免费 | av无码电影一区二区三区 | 精品一二三区久久aaa片 | 国产精品久久久久影院嫩草 | 久久亚洲精品成人无码 | 欧美刺激性大交 | 亚拍精品一区二区三区探花 | 秋霞成人午夜鲁丝一区二区三区 | 人人澡人摸人人添 | 日本xxxx色视频在线观看免费 | 又湿又紧又大又爽a视频国产 | 国产真实乱对白精彩久久 | 日本护士毛茸茸高潮 | 国产美女极度色诱视频www | 久久99精品久久久久婷婷 | 99久久精品午夜一区二区 | 欧美三级a做爰在线观看 | 日韩精品乱码av一区二区 | 呦交小u女精品视频 | 欧美成人家庭影院 | 中文字幕av伊人av无码av | 国产尤物精品视频 | 小泽玛莉亚一区二区视频在线 | 99久久婷婷国产综合精品青草免费 | 久久久久成人精品免费播放动漫 | 日本精品人妻无码77777 天堂一区人妻无码 | 人人妻人人澡人人爽精品欧美 | 国产97人人超碰caoprom | 国产精品高潮呻吟av久久 | 亚洲精品一区三区三区在线观看 | 国产激情艳情在线看视频 | 老太婆性杂交欧美肥老太 | 午夜福利试看120秒体验区 | 日本一卡2卡3卡四卡精品网站 | 一本久久a久久精品vr综合 | 欧美人与动性行为视频 | 日韩成人一区二区三区在线观看 | 国产欧美亚洲精品a | 人人澡人人透人人爽 | 久久97精品久久久久久久不卡 | 久久无码人妻影院 | 麻豆国产97在线 | 欧洲 | 欧美熟妇另类久久久久久不卡 | 国产农村乱对白刺激视频 | 红桃av一区二区三区在线无码av | 丰满少妇女裸体bbw | 无码午夜成人1000部免费视频 | 国产精品美女久久久久av爽李琼 | 国産精品久久久久久久 | 熟女体下毛毛黑森林 | 国产av一区二区三区最新精品 | 亚洲 a v无 码免 费 成 人 a v | 国产午夜福利100集发布 | 久久久精品国产sm最大网站 | 亚洲精品成人av在线 | 国产亚洲日韩欧美另类第八页 | 免费无码的av片在线观看 | 久久人人爽人人爽人人片ⅴ | 午夜福利电影 | 国产两女互慰高潮视频在线观看 | 国产亲子乱弄免费视频 | 亚洲综合色区中文字幕 | 中文字幕无码av波多野吉衣 | 蜜桃无码一区二区三区 | 国产艳妇av在线观看果冻传媒 | 少妇人妻大乳在线视频 | 国产精品亚洲专区无码不卡 | 性色欲情网站iwww九文堂 | 无码av岛国片在线播放 | 欧美亚洲日韩国产人成在线播放 | 亚洲国产精品无码久久久久高潮 | 免费无码午夜福利片69 | 日本丰满熟妇videos | 亚洲日韩av片在线观看 | 精品一区二区三区无码免费视频 | 国产成人精品久久亚洲高清不卡 | 人妻人人添人妻人人爱 | 婷婷五月综合缴情在线视频 | 亚洲国产av精品一区二区蜜芽 | 无码毛片视频一区二区本码 | 久久99精品久久久久久动态图 | 永久免费观看美女裸体的网站 | 乱码午夜-极国产极内射 | 99久久人妻精品免费二区 | 国产精品美女久久久 | www国产亚洲精品久久久日本 | 波多野结衣乳巨码无在线观看 | 国产精品美女久久久 | 亚洲中文字幕在线观看 | 久久久婷婷五月亚洲97号色 | 大地资源网第二页免费观看 | 精品久久8x国产免费观看 | 免费人成网站视频在线观看 | 久久综合久久自在自线精品自 | 久久国语露脸国产精品电影 | 欧美性黑人极品hd | 国产精品国产自线拍免费软件 | 综合网日日天干夜夜久久 | 一个人看的www免费视频在线观看 | 久久久久免费精品国产 | 三级4级全黄60分钟 | 六十路熟妇乱子伦 | 精品一区二区三区波多野结衣 | 又大又紧又粉嫩18p少妇 | 亚洲一区二区三区在线观看网站 | 久久午夜无码鲁丝片 | 亚洲中文字幕在线观看 | 亚洲中文字幕在线观看 | 99久久婷婷国产综合精品青草免费 | 熟妇人妻中文av无码 | 中文字幕av伊人av无码av | 麻花豆传媒剧国产免费mv在线 | 人妻少妇精品无码专区动漫 | 国产精品va在线播放 | 色综合天天综合狠狠爱 | 玩弄中年熟妇正在播放 | 亚洲の无码国产の无码步美 | 少妇高潮喷潮久久久影院 | 精品乱码久久久久久久 | 久久精品国产大片免费观看 | 青草青草久热国产精品 | 婷婷丁香五月天综合东京热 | 成人性做爰aaa片免费看 | 日日夜夜撸啊撸 | 少妇激情av一区二区 | 3d动漫精品啪啪一区二区中 | 亚洲中文字幕va福利 | 国产偷自视频区视频 | 欧美老妇交乱视频在线观看 | 欧美亚洲国产一区二区三区 | 精品亚洲韩国一区二区三区 | 国产亚洲tv在线观看 | 天堂亚洲免费视频 | 亚洲色偷偷偷综合网 | 日本高清一区免费中文视频 | 无遮无挡爽爽免费视频 | 性啪啪chinese东北女人 | 国产精品无码永久免费888 | 亚洲成av人综合在线观看 | 男女猛烈xx00免费视频试看 | 国产亚洲欧美在线专区 | 国产一区二区不卡老阿姨 | 中文字幕人妻无码一夲道 | 亚洲男人av天堂午夜在 | 久久综合久久自在自线精品自 | 久久天天躁狠狠躁夜夜免费观看 | 狂野欧美性猛xxxx乱大交 | 亚欧洲精品在线视频免费观看 | 又粗又大又硬又长又爽 | 捆绑白丝粉色jk震动捧喷白浆 | 午夜丰满少妇性开放视频 | 性史性农村dvd毛片 | 人人超人人超碰超国产 | 国产av剧情md精品麻豆 | 人妻体内射精一区二区三四 | 伊人色综合久久天天小片 | 亚洲乱码中文字幕在线 | 2019午夜福利不卡片在线 | 国产精品无码永久免费888 | 好男人社区资源 | 久久精品成人欧美大片 | 乱人伦中文视频在线观看 | 久久精品无码一区二区三区 | 亚洲精品国偷拍自产在线麻豆 | 天天做天天爱天天爽综合网 | 九九在线中文字幕无码 | 2020久久超碰国产精品最新 | 无码国产乱人伦偷精品视频 | 亚洲精品综合一区二区三区在线 | 国内精品人妻无码久久久影院 | 一区二区三区乱码在线 | 欧洲 | 国产无遮挡又黄又爽免费视频 | 特级做a爰片毛片免费69 | 国产亚洲视频中文字幕97精品 | 亲嘴扒胸摸屁股激烈网站 | 国产精品福利视频导航 | 久久综合狠狠综合久久综合88 | 给我免费的视频在线观看 | 内射白嫩少妇超碰 | 波多野结衣一区二区三区av免费 | 成人女人看片免费视频放人 | 色欲人妻aaaaaaa无码 | 草草网站影院白丝内射 | 狠狠躁日日躁夜夜躁2020 | 狠狠色噜噜狠狠狠狠7777米奇 | 双乳奶水饱满少妇呻吟 | av无码电影一区二区三区 | 精品一区二区不卡无码av | 国产人妻人伦精品1国产丝袜 | 荫蒂被男人添的好舒服爽免费视频 | 色欲人妻aaaaaaa无码 | 天天躁夜夜躁狠狠是什么心态 |