日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

SpringBoot java-jar命令行啟動原理解析

瀏覽:14日期:2022-08-30 08:24:37

在spring boot里,很吸引人的一個特性是可以直接把應用打包成為一個jar/war,然后這個jar/war是可以直接啟動的,而不需要另外配置一個Web Server。那么spring boot如何啟動的呢?今天我們就來一起探究一下它的原理。首先我們來創建一個基本的spring boot工程來幫助我們分析,本次spring boot版本為 2.2.5.RELEASE。

// SpringBootDemo.java@SpringBootApplicationpublic class SpringBootDemo { public static void main(String[] args) { SpringApplication.run(SpringBootDemo.class); }}

下面是pom依賴:

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency></dependencies><build> <finalName>springboot-demo</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build>

創建完工程后,執行maven的打包命令,會生成兩個jar文件:

springboot-demo.jarspringboot-demo.jar.original

其中springboot-demo.jar.original是默認的maven-jar-plugin生成的包。springboot-demo.jar是spring boot maven插件生成的jar包,里面包含了應用的依賴,以及spring boot相關的類。下面稱之為executable jar或者fat jar。后者僅包含應用編譯后的本地資源,而前者引入了相關的第三方依賴,這點從文件大小也能看出。

SpringBoot java-jar命令行啟動原理解析

圖1

關于executable jar,spring boot官方文檔中是這樣解釋的。

Executable jars (sometimes called “fat jars”) are archives containing your compiled classes along with all of the jar dependencies that your code needs to run.

Executable jar(有時稱為“fat jars”)是包含您的已編譯類以及代碼需要運行的所有jar依賴項的歸檔文件。

Java does not provide any standard way to load nested jar files (that is, jar files that are themselves contained within a jar). This can be problematic if you need to distribute a self-contained application that can be run from the command line without unpacking.

Java沒有提供任何標準的方式來加載嵌套的jar文件(即,它們本身包含在jar中的jar文件)。如果您需要分發一個自包含的應用程序,而該應用程序可以從命令行運行而無需解壓縮,則可能會出現問題。

To solve this problem, many developers use “shaded” jars. A shaded jar packages all classes, from all jars, into a single “uber jar”. The problem with shaded jars is that it becomes hard to see which libraries are actually in your application. It can also be problematic if the same filename is used (but with different content) in multiple jars.

為了解決這個問題,許多開發人員使用 shaded jars。 一個 shaded jar 將來自所有jar的所有類打包到一個 uber(超級)jar 中。 shaded jars的問題在于,很難查看應用程序中實際包含哪些庫。 如果在多個jar中使用相同的文件名(但具有不同的內容),也可能會產生問題。

Spring Boot takes a different approach and lets you actually nest jars directly.

Spring Boot采用了另一種方法,實際上允許您直接嵌套jar。

簡單來說,Java標準中是沒有來加載嵌套的jar文件,就是jar中的jar的方式的,為了解決這一問題,很多開發人員采用shaded jars,但是這種方式會有一些問題,而spring boot采用了不同于shaded jars的另一種方式。

Executable Jar 文件結構

那么spring boot具體是如何實現的呢?帶著這個疑問,先來查看spring boot打好的包的目錄結構(不重要的省略掉):

SpringBoot java-jar命令行啟動原理解析

圖6

可以發現,文件目錄遵循了下面的規范:

Application classes should be placed in a nestedBOOT-INF/classesdirectory. Dependencies should be placed in a nested BOOT-INF/libdirectory.

應用程序類應該放在嵌套的BOOT-INF/classes目錄中。依賴項應該放在嵌套的BOOT-INF/lib目錄中。

我們通常在服務器中使用java -jar命令啟動我們的應用程序,在Java官方文檔是這樣描述的:

Executes a program encapsulated in a JAR file. The filename argument is the name of a JAR file with a manifest that contains a line in the form Main-Class:classname that defines the class with the public static void main(String[] args) method that serves as your application’s starting point.

執行封裝在JAR文件中的程序。filename參數是具有清單的JAR文件的名稱,該清單包含Main-Class:classname形式的行,該行使用公共靜態void main(String [] args)方法定義該類,該方法充當應用程序的起點。

When you use the -jar option, the specified JAR file is the source of all user classes, and other class path settings are ignored.

使用-jar選項時,指定的JAR文件是所有用戶類的源,而其他類路徑設置將被忽略。

簡單說就是,java -jar 命令引導的具體啟動類必須配置在清單文件 MANIFEST.MF 的 Main-Class 屬性中,該命令用來引導標準可執行的jar文件,讀取的是 MANIFEST.MF文件的Main-Class 屬性值,Main-Class 也就是定義包含了main方法的類代表了應用程序執行入口類。

那么回過頭再去看下之前打包好、解壓之后的文件目錄,找到 /META-INF/MANIFEST.MF 文件,看下元數據:

Manifest-Version: 1.0 Implementation-Title: spring-boot-demo Implementation-Version: 1.0-SNAPSHOT Start-Class: com.example.spring.boot.demo.SpringBootDemo Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Build-Jdk-Spec: 1.8 Spring-Boot-Version: 2.2.5.RELEASE Created-By: Maven Archiver 3.4.0 Main-Class: org.springframework.boot.loader.JarLauncher

可以看到Main-Class是org.springframework.boot.loader.JarLauncher,說明項目的啟動入口并不是我們自己定義的啟動類,而是JarLauncher。而我們自己的項目引導類com.example.spring.boot.demo.SpringBootDemo,定義在了Start-Class屬性中,這個屬性并不是Java標準的MANIFEST.MF文件屬性。

spring-boot-maven-plugin 打包過程

我們并沒有添加org.springframework.boot.loader下的這些類的依賴,那么它們是如何被打包在 FatJar 里面的呢?這就必須要提到spring-boot-maven-plugin插件的工作機制了 。對于每個新建的 spring boot工程,可以在其 pom.xml 文件中看到如下插件:

<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build>

這個是 SpringBoot 官方提供的用于打包 FatJar 的插件,org.springframework.boot.loader 下的類其實就是通過這個插件打進去的;

當我們執行package命令的時候會看到下面這樣的日志:

[INFO] --- spring-boot-maven-plugin:2.2.5.RELEASE:repackage (repackage) @ spring-boot-demo ---[INFO] Replacing main artifact with repackaged archive

repackage目標對應的將執行到org.springframework.boot.maven.RepackageMojo#execute,該方法的主要邏輯是調用了org.springframework.boot.maven.RepackageMojo#repackage

// RepackageMojo.javaprivate void repackage() throws MojoExecutionException { // 獲取使用maven-jar-plugin生成的jar,最終的命名將加上.orignal后綴 Artifact source = getSourceArtifact(); // 最終文件,即Fat jar File target = getTargetFile(); // 獲取重新打包器,將重新打包成可執行jar文件 Repackager repackager = getRepackager(source.getFile()); // 查找并過濾項目運行時依賴的jar Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(), getFilters(getAdditionalFilters())); // 將artifacts轉換成libraries Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack, getLog()); try { // 提供Spring Boot啟動腳本 LaunchScript launchScript = getLaunchScript(); // 執行重新打包邏輯,生成最后fat jar repackager.repackage(target, libraries, launchScript); } catch (IOException ex) { throw new MojoExecutionException(ex.getMessage(), ex); } // 將source更新成 xxx.jar.orignal文件 updateArtifact(source, target, repackager.getBackupFile());}// 繼續跟蹤getRepackager這個方法,知道Repackager是如何生成的,也就大致能夠推測出內在的打包邏輯。private Repackager getRepackager(File source) { Repackager repackager = new Repackager(source, this.layoutFactory); repackager.addMainClassTimeoutWarningListener(new LoggingMainClassTimeoutWarningListener()); // 設置main class的名稱,如果不指定的話則會查找第一個包含main方法的類, // repacke最后將會設置org.springframework.boot.loader.JarLauncher repackager.setMainClass(this.mainClass); if (this.layout != null) { getLog().info('Layout: ' + this.layout); repackager.setLayout(this.layout.layout()); } return repackager;}

repackager設置了 layout方法的返回對象,也就是org.springframework.boot.loader.tools.Layouts.Jar

/** * Executable JAR layout. */public static class Jar implements RepackagingLayout { @Override public String getLauncherClassName() { return 'org.springframework.boot.loader.JarLauncher'; } @Override public String getLibraryDestination(String libraryName, LibraryScope scope) { return 'BOOT-INF/lib/'; } @Override public String getClassesLocation() { return ''; } @Override public String getRepackagedClassesLocation() { return 'BOOT-INF/classes/'; } @Override public boolean isExecutable() { return true; }}

layout我們可以將之翻譯為文件布局,或者目錄布局,代碼一看清晰明了,同時我們又發現了定義在MANIFEST.MF 文件的Main-Class屬性org.springframework.boot.loader.JarLauncher了,看來我們的下面的重點就是研究一下這個JarLauncher了。

JarLauncher構造過程

因為org.springframework.boot.loader.JarLauncher的類是在spring-boot-loader中的,關于spring-boot-loader,spring boot的github上是這樣介紹的:

Spring Boot Loader provides the secret sauce that allows you to build a single jar file that can be launched usingjava -jar. Generally you will not need to use spring-boot-loaderdirectly, but instead work with the Gradle or Maven plugin.

Spring Boot Loader提供了秘密工具,可讓您構建可以使用java -jar啟動的單個jar文件。通常,您不需要直接使用spring-boot-loader,而可以使用Gradle或Maven插件。

但是若想在IDEA中來看源碼,需要在pom文件中引入如下配置:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-loader</artifactId> <scope>provided</scope></dependency>

找到org.springframework.boot.loader.JarLauncher類

// JarLauncher.javapublic class JarLauncher extends ExecutableArchiveLauncher { // BOOT-INF/classes/ static final String BOOT_INF_CLASSES = 'BOOT-INF/classes/'; // BOOT-INF/lib/ static final String BOOT_INF_LIB = 'BOOT-INF/lib/'; public JarLauncher() { } protected JarLauncher(Archive archive) { super(archive); } @Override protected boolean isNestedArchive(Archive.Entry entry) { if (entry.isDirectory()) { return entry.getName().equals(BOOT_INF_CLASSES); } return entry.getName().startsWith(BOOT_INF_LIB); } // main方法 public static void main(String[] args) throws Exception { new JarLauncher().launch(args); }}

可以發現,JarLauncher定義了BOOT_INF_CLASSES和BOOT_INF_LIB兩個常量,正好就是前面我們解壓之后的兩個文件目錄。JarLauncher包含了一個main方法,作為應用的啟動入口。

從 main 來看,只是構造了一個 JarLauncher對象,然后執行其 launch 方法 。再來看一下JarLauncher的繼承結構:

SpringBoot java-jar命令行啟動原理解析

圖2

構造JarLauncherd對象時會調用父類ExecutableArchiveLauncher的構造方法:

// ExecutableArchiveLauncher.javapublic ExecutableArchiveLauncher() { try { // 構造 archive 對象 this.archive = createArchive(); } catch (Exception ex) { throw new IllegalStateException(ex); }}// 構造 archive 對象protected final Archive createArchive() throws Exception { ProtectionDomain protectionDomain = getClass().getProtectionDomain(); CodeSource codeSource = protectionDomain.getCodeSource(); URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null; // 這里就是拿到當前的 classpath 的絕對路徑 String path = (location != null) ? location.getSchemeSpecificPart() : null; if (path == null) { throw new IllegalStateException('Unable to determine code source archive'); } File root = new File(path); if (!root.exists()) { throw new IllegalStateException('Unable to determine code source archive from ' + root); } // 將構造的archive 對象返回 return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));}

Archive

這里又需要我們先來了解一下Archive相關的概念。

archive即歸檔文件,這個概念在linux下比較常見 通常就是一個tar/zip格式的壓縮包 jar是zip格式

public abstract class Archive { public abstract URL getUrl(); public String getMainClass(); public abstract Collection<Entry> getEntries(); public abstract List<Archive> getNestedArchives(EntryFilter filter);}

Archive是在spring boot里抽象出來的用來統一訪問資源的接口。該接口有兩個實現,分別是ExplodedArchive和JarFileArchive。前者是一個文件目錄,后者是一個jar,都是用來在文件目錄和jar中尋找資源的,這里看到JarLauncher既支持jar啟動,也支持文件系統啟動,實際上我們在解壓后的文件目錄里執行 java org.springframework.boot.loader.JarLauncher 命令也是可以正常啟動的。

SpringBoot java-jar命令行啟動原理解析

圖3

在FatJar中,使用的是后者。Archive都有一個自己的URL,比如

jar:file:/D:/java/workspace/spring-boot-bootstarp-demo/spring-boot-demo/target/springboot-demo.jar!

Archive類還有一個getNestedArchives方法,下面還會用到這個方法,這個方法實際返回的是springboot-demo.jar/lib下面的jar的Archive列表。它們的URL是:

jar:file:/D:/java/workspace/spring-boot-bootstarp-demo/spring-boot-demo/target/springboot-demo.jar!/BOOT-INF/lib/spring-boot-starter-web-2.2.5.RELEASE.jar!

jar:file:/D:/java/workspace/spring-boot-bootstarp-demo/spring-boot-demo/target/springboot-demo.jar!/BOOT-INF/lib/spring-boot-starter-2.2.5.RELEASE.jar!

jar:file:/D:/java/workspace/spring-boot-bootstarp-demo/spring-boot-demo/target/springboot-demo.jar!/BOOT-INF/lib/spring-boot-2.2.5.RELEASE.jar!

jar:file:/D:/java/workspace/spring-boot-bootstarp-demo/spring-boot-demo/target/springboot-demo.jar!/BOOT-INF/lib/spring-boot-autoconfigure-2.2.5.RELEASE.jar!/

省略......

launch()執行流程

archive構造完成后就該執行JarLauncher的launch方法了,這個方法定義在了父類的Launcher里:

// Launcher.javaprotected void launch(String[] args) throws Exception { /* * 利用 java.net.URLStreamHandler 的擴展機制注冊了SpringBoot的自定義的可以解析嵌套jar的協議。 * 因為SpringBoot FatJar除包含傳統Java Jar中的資源外還包含依賴的第三方Jar文件 * 當SpringBoot FatJar被java -jar命令引導時,其內部的Jar文件是無法被JDK的默認實現 * sun.net.www.protocol.jar.Handler當做classpath的,這就是SpringBoot的自定義協議的原因。 */ JarFile.registerUrlProtocolHandler(); // 通過 classpath 來構建一個 ClassLoader ClassLoader classLoader = createClassLoader(getClassPathArchives()); // 1 launch(args, getMainClass(), classLoader); // 2}

重點關注下createClassLoader(getClassPathArchives()) 構建ClassLoader的邏輯,首先調用getClassPathArchives()方法返回值作為參數,該方法為抽象方法,具體實現在子類ExecutableArchiveLauncher中:

// ExecutableArchiveLauncher.java@Overrideprotected List<Archive> getClassPathArchives() throws Exception { List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive)); postProcessClassPathArchives(archives); return archives;}

該方法會執行Archive接口定義的getNestedArchives方法返回的與指定過濾器匹配的條目的嵌套存檔列表。從上文可以發現,這里的archive其實就是JarFileArchive ,傳入的過濾器是JarLauncher#isNestedArchive方法引用

// JarLauncher.java@Overrideprotected boolean isNestedArchive(Archive.Entry entry) { // entry是文件目錄時,必須是我們自己的業務類所在的目錄 BOOT-INF/classes/ if (entry.isDirectory()) { return entry.getName().equals(BOOT_INF_CLASSES); } // entry是Jar文件時,需要在依賴的文件目錄 BOOT-INF/lib/下面 return entry.getName().startsWith(BOOT_INF_LIB);}

getClassPathArchives方法通過過濾器將BOOT-INF/classes/和BOOT-INF/lib/下的嵌套存檔作為List<Archive>返回參數傳入createClassLoader方法中。

// Launcher.javaprotected ClassLoader createClassLoader(List<Archive> archives) throws Exception { List<URL> urls = new ArrayList<>(archives.size()); for (Archive archive : archives) { // 前面說到,archive有一個自己的URL的,獲得archive的URL放到list中 urls.add(archive.getUrl()); } // 調用下面的重載方法 return createClassLoader(urls.toArray(new URL[0]));}// Launcher.javaprotected ClassLoader createClassLoader(URL[] urls) throws Exception { return new LaunchedURLClassLoader(urls, getClass().getClassLoader());}

createClassLoader()方法目的是為得到的URL們創建一個類加載器 LaunchedURLClassLoader,構造時傳入了當前Launcher的類加載器作為其父加載器,通常是系統類加載器。下面重點看一下LaunchedURLClassLoader的構造過程:

// LaunchedURLClassLoader.javapublic LaunchedURLClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent);}

LaunchedURLClassLoader是spring boot自己定義的類加載器,繼承了JDK的URLClassLoader并重寫了loadClass方法,也就是說它修改了默認的類加載方式,定義了自己的類加載規則,可以從前面得到的 List<Archive>中加載依賴包的class文件了 。

LaunchedURLClassLoader創建完成后,我們回到Launcher中,下一步就是執行launch的重載方法了。

// Launcher.javalaunch(args, getMainClass(), classLoader);

在此之前,會調用getMainClass方法并將其返回值作為參數。

getMainClass的實現在Launcher的子類ExecutableArchiveLauncher中:

// ExecutableArchiveLauncher.java@Overrideprotected String getMainClass() throws Exception { // 從 archive 中拿到 Manifest文件 Manifest manifest = this.archive.getManifest(); String mainClass = null; if (manifest != null) { // 就是MANIFEST.MF 文件中定義的Start-Class屬性,也就是我們自己寫的com.example.spring.boot.demo.SpringBootDemo這個類 mainClass = manifest.getMainAttributes().getValue('Start-Class'); } if (mainClass == null) { throw new IllegalStateException('No ’Start-Class’ manifest entry specified in ' + this); } // 返回mainClass return mainClass;}

得到mainClass后,執行launch的重載方法:

// Launcher.javaprotected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception { // 將自定義的LaunchedURLClassLoader設置為當前線程上下文類加載器 Thread.currentThread().setContextClassLoader(classLoader); // 構建一個 MainMethodRunner 實例對象來啟動應用 createMainMethodRunner(mainClass, args, classLoader).run();}// Launcher.javaprotected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) { return new MainMethodRunner(mainClass, args);}

MainMethodRunner對象構建完成后,調用它的run方法:

// MainMethodRunner.javapublic void run() throws Exception { // 使用當前線程上下文類加載器也就是自定義的LaunchedURLClassLoader來加載我們自己寫的com.example.spring.boot.demo.SpringBootDemo這個類 Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName); // 找到SpringBootDemo的main方法 Method mainMethod = mainClass.getDeclaredMethod('main', String[].class); // 最后,通過反射的方式調用main方法 mainMethod.invoke(null, new Object[] { this.args });}

至此,我們自己的main方法開始被調用,所有我們自己的應用程序類文件均可通過/BOOT-INF/classes加載,所有依賴的第三方jar均可通過/BOOT-INF/lib加載,然后就開始了spring boot的啟動流程了。

debug技巧

以上就是spring boot通過java -jar命令啟動的原理了,了解了原理以后我們可不可以通過debug來進一步加深一下理解呢?通常我們在IDEA里啟動時是直接運行main方法,因為依賴的Jar都讓IDEA放到classpath里了,所以spring boot直接啟動就完事了,并不會通過上面的方式來啟動。不過我們可以通過配置IDEA的 run/debug configurations 配置 JAR Application 來實現通過Jar方式啟動。

SpringBoot java-jar命令行啟動原理解析

圖4

當我們做了以上設置后,就可以來方便的在IDEA里來dubug源碼了。

SpringBoot java-jar命令行啟動原理解析

圖5小結

本文通過JarLauncher為切入點,介紹了spring boot的java -jar的啟動方式,闡述了JarLauncher啟動的基本工作原理,同時簡單介紹了相關的spring-boot-maven-plugin插件和Archive、LaunchedURLClassLoader等相關概念,希望能夠對大家的理解有所幫助。

到此這篇關于SpringBoot java-jar命令行啟動原理解析的文章就介紹到這了,更多相關SpringBoot java-jar命令行啟動內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Java
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
久久国产精品美女| 成人国产精品久久| 国产调教一区二区三区| 亚洲精品高潮| 婷婷六月综合| 日韩高清不卡| 91精品91| 亚洲国产不卡| 久久久久国产精品一区三寸| 欧美1区2区3| 精品视频一区二区三区四区五区| 日韩一区二区三区在线看| 香蕉精品久久| 国产91精品对白在线播放| 1024精品久久久久久久久| 91tv亚洲精品香蕉国产一区| 在线人成日本视频| 日韩精品看片| 欧美国产91| 欧美日韩国产高清| 一区二区视频欧美| 亚洲欧美在线综合| 亚洲免费专区| 亚洲人成高清| 日韩午夜一区| 亚洲精品国模| 日韩成人午夜精品| 日韩一区二区三区在线看| 国产亚洲一区| 国产精品亚洲片在线播放| 亚洲乱亚洲高清| 日韩影院免费视频| 四虎国产精品免费久久| 婷婷综合福利| 国产日产高清欧美一区二区三区| 国产图片一区| 九九九精品视频| 国产一区二区三区日韩精品| 中文字幕人成乱码在线观看| 日韩亚洲在线| 亚洲精品一级| 日韩在线成人| 国产精品麻豆成人av电影艾秋| 国产精品白丝久久av网站| 老牛国内精品亚洲成av人片| 国产aⅴ精品一区二区三区久久| 捆绑调教美女网站视频一区| 美女毛片一区二区三区四区最新中文字幕亚洲 | 99久久www免费| 不卡中文字幕| 亚洲视频国产精品| 久久国产视频网| 久久国产直播| 99视频精品| 日韩一区二区三免费高清在线观看| 婷婷久久免费视频| 青青青免费在线视频| 欧美不卡在线| 亚久久调教视频| 国产精品久久久久毛片大屁完整版| 国产理论在线| 午夜久久福利| 日韩高清在线不卡| 国产精品白丝久久av网站| 黄色国产精品| 国产欧美一区二区色老头| 日韩影院二区| 亚洲精品美女91| 在线看片国产福利你懂的| 免费久久久久久久久| 久久国产精品亚洲77777| 日本久久一区| 欧美va亚洲va日韩∨a综合色| 亚洲精品极品| 激情综合婷婷| 日韩毛片在线| 国产精品一区二区三区四区在线观看| 97视频热人人精品免费| 中文欧美日韩| 久久av中文| 欧美日韩国产免费观看| 亚洲精品在线国产| 精品久久97| 日本视频一区二区| 欧美少妇精品| 亚洲精选91| 久久亚洲二区| 水蜜桃精品av一区二区| 免费在线观看不卡| 久久久久黄色| 视频精品一区二区| 国产成人精品一区二区免费看京| 国产91久久精品一区二区| 蜜芽一区二区三区| 国产一区二区三区四区五区传媒| 国产一区二区精品| 日韩av中文字幕一区| 欧美国产偷国产精品三区| 免费成人在线视频观看| 国产二区精品| 国产一区二区三区日韩精品| 精品中文一区| 欧美精品中文| 日本欧美在线看| 亚洲综合在线电影| 国产精品天天看天天狠| 日韩av中文字幕一区| 欧美福利在线| 精品久久福利| 中文一区一区三区免费在线观 | 欧美日韩午夜| 欧美理论视频| 中文字幕在线视频久| 国产精品视频首页| 免费在线视频一区| 久久久久久久久99精品大| 亚洲女同av| 国产精品一区二区免费福利视频 | 伊人久久亚洲美女图片| 麻豆视频在线观看免费网站黄 | 伊人久久成人| 欧美日韩国产免费观看| 欧美日韩国产一区二区三区不卡| 欧美高清一区| 亚洲免费黄色| 视频一区在线播放| 视频一区日韩| 国产美女精品视频免费播放软件| 国产精品免费精品自在线观看| 国产高清视频一区二区| 久久精品国产亚洲一区二区三区| 国产精品一区二区三区美女 | 国产精品99久久久久久董美香| 久久av综合| 精品九九久久| 日韩免费在线| 五月精品视频| 日韩精品一区第一页| 日本va欧美va瓶| 久久成人高清| 精品视频网站| 欧美日韩国产v| 中文一区在线| 日韩精品一区二区三区中文字幕| 国产日韩欧美一区在线| 久久精品五月| 999国产精品| 蜜臀久久99精品久久久久久9| 日韩精品a在线观看91| 欧美国产另类| 99精品小视频| 亚洲伊人影院| 麻豆一区二区三| 久久国产电影| 综合激情网站| 国产精品一线| 日韩久久视频| 9色精品在线| 国产亚洲人成a在线v网站| 欧美精品第一区| 久久在线视频免费观看| 丝袜脚交一区二区| 国产福利一区二区三区在线播放| 日韩欧美一区免费| 蜜臀av亚洲一区中文字幕| 国产情侣一区| 久久香蕉国产| 色婷婷成人网| 国产欧美一区二区三区精品酒店| 国产精品日韩| 欧美精品国产| 欧美成a人国产精品高清乱码在线观看片在线观看久 | 免费福利视频一区二区三区| 婷婷亚洲五月| 欧美日韩在线精品一区二区三区激情综合 | 91精品国产自产观看在线| 欧美国产日韩电影| 午夜精品影院| 美女免费视频一区| 宅男噜噜噜66国产日韩在线观看| 国产欧美一区二区三区国产幕精品| 日韩大片在线观看| 日韩中文字幕在线一区| 国产高潮在线| 亚洲欧洲国产精品一区| 狠狠久久伊人中文字幕| 亚洲综合精品| 精品一区二区三区中文字幕视频 | 久久国产精品99国产| 欧美在线精品一区| 激情综合激情| 美女免费视频一区| 黄色av一区| 国产成人精品福利| 日韩精品1区2区3区| 国产99精品| 久久精品 人人爱| 日韩精品久久久久久久电影99爱| 亚洲乱亚洲高清| 在线看片福利|