<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java中的屠龍之術(shù)(二):如何方便快捷地生成.class文件

          共 6604字,需瀏覽 14分鐘

           ·

          2021-12-18 16:06


          點(diǎn)擊藍(lán)色“程序員黃小斜”關(guān)注我喲

          加個(gè)“星標(biāo)”,每天和你一起多進(jìn)步一點(diǎn)點(diǎn)

          在之前的“Java中的屠龍之術(shù):如何修改語法樹”中,我們?cè)敿?xì)介紹了如何使用Javac源碼提供的工具類來修改語法樹。

          而在此基礎(chǔ)上,有一款開源工具javapoet可以更加快捷地生成字節(jié)碼,實(shí)現(xiàn)原理其實(shí)也就是對(duì)JavaAPT的封裝,然而Javapoet有一個(gè)局限性,就是只能生成新的.class文件,卻無法修改原有的類,這也是它的一大局限性所在。接下來就讓我們看看它的使用方法把。

          0x00 概述

          注解系列

          • 注解基礎(chǔ)

          • JavaPoet

          • 編譯期注解處理之APT

          上一篇限于篇幅只介紹了APT,這篇來繼續(xù)介紹javapoet,是square公司的開源庫。正如其名,java詩人,通過注解來生成java源文件,通常要使用javapoet這個(gè)庫與Filer配合使用。主要和注解配合用來干掉那些重復(fù)的模板代碼(如butterknife 和databinding所做的事情),當(dāng)然你也可以使用這個(gè)技術(shù)讓你的代碼更加的炫酷。

          0x01 簡單使用

          使用之前要先引入這個(gè)庫

          compile 'com.squareup:javapoet:1.7.0'
          復(fù)制代碼


          javapoet是用來生成代碼的,需要借助

          常用類

          使用javapoet前需要了解8個(gè)常用類

          類名作用
          MethodSpec代表一個(gè)構(gòu)造函數(shù)或方法聲明
          TypeSpec代表一個(gè)類,接口,或者枚舉聲明
          FieldSpec代表一個(gè)成員變量,一個(gè)字段聲明
          JavaFile包含一個(gè)頂級(jí)類的Java文件
          ParameterSpec用來創(chuàng)建參數(shù)
          AnnotationSpec用來創(chuàng)建注解
          ClassName用來包裝一個(gè)類
          TypeName類型,如在添加返回值類型是使用 TypeName.VOID

          除此之外 JavaPoet提供了一套自定義的字符串格式化規(guī)則,常用的有

          格式化規(guī)則表示含義
          $L字面量
          $S字符串
          $T類、接口
          $N變量

          0x02 使用進(jìn)階

          下面由淺入深,循序漸進(jìn)的說明用法

          方法&控制流:

          • 添加方法 addcodeaddstatement 對(duì)與無需類引入的極簡代碼可以直接使用addCode

          MethodSpec main = MethodSpec.methodBuilder("main")
          .addCode(""
          + "int total = 0;\n"
          + "for (int i = 0; i < 10; i++) {\n"
          + " total += i;\n"
          + "}\n")
          .build();
          復(fù)制代碼


          生成的是

          void main() {
          int total = 0;
          for (int i = 0; i < 10; i++) {
          total += i;
          }
          }
          復(fù)制代碼


          要是需要import的方法,如上面的.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") 就需要使用.addStatement來聲明

          • 更優(yōu)雅的流控制

          beginControlFlow 流開啟 addStatement 處理語句 endControlFlow()流結(jié)束

          如上面的用流改寫就是

          MethodSpec main = MethodSpec.methodBuilder("main")
          .addStatement("int total = 0")
          .beginControlFlow("for (int i = 0; i < 10; i++)")
          .addStatement("total += i")
          .endControlFlow()
          .build();
          復(fù)制代碼


          占位符

          javapoet里面提供了占位符來幫助我們更好地生成代碼

          • $L 字面常量(Literals)

          private MethodSpec computeRange(String name, int from, int to, String op) {
          return MethodSpec.methodBuilder(name)
          .returns(int.class)
          .addStatement("int result = 0")
          .beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
          .addStatement("result = result $L i", op)
          .endControlFlow()
          .addStatement("return result")
          .build();
          }
          復(fù)制代碼


          這個(gè)就是一個(gè)for循環(huán),op負(fù)責(zé)加減乘除等符號(hào)

          • $S 字符串常量(String)

          • $T 類型(Types)

          最大的特點(diǎn)是自動(dòng)導(dǎo)入包

          MethodSpec today = MethodSpec.methodBuilder("today")
          .returns(Date.class)
          .addStatement("return new $T()", Date.class)
          .build();

          TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
          .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
          .addMethod(today)
          .build();

          JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
          .build();

          javaFile.writeTo(System.out);

          復(fù)制代碼


          生成的代碼如下,而且會(huì)自動(dòng)導(dǎo)包

          package com.example.helloworld;

          import java.util.Date;

          public final class HelloWorld {
          Date today() {
          return new Date();
          }
          }
          復(fù)制代碼


          • $N 命名(Names),通常指我們自己生成的方法名或者變量名等等

          比如這樣的代碼塊

          public String byteToHex(int b) {
          char[] result = new char[2];
          result[0] = hexDigit((b >>> 4) & 0xf);
          result[1] = hexDigit(b & 0xf);
          return new String(result);
          }

          public char hexDigit(int i) {
          return (char) (i < 10 ? i + '0' : i - 10 + 'a');
          }
          復(fù)制代碼


          我們可以傳遞hexDigit()來代替。

          MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
          .addParameter(int.class, "i")
          .returns(char.class)
          .addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
          .build();

          MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
          .addParameter(int.class, "b")
          .returns(String.class)
          .addStatement("char[] result = new char[2]")
          .addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
          .addStatement("result[1] = $N(b & 0xf)", hexDigit)
          .addStatement("return new String(result)")
          .build();
          復(fù)制代碼


          獲取對(duì)應(yīng)類

          有兩種方式:

          • ClassName.bestGuess(“類全名稱”) 返回ClassName對(duì)象,這里的類全名稱表示的類必須要存在,會(huì)自動(dòng)導(dǎo)入相應(yīng)的包

          • ClassName.get(“包名”,”類名”) 返回ClassName對(duì)象,不檢查該類是否存在

            因此如果使用JavaPoet的話后續(xù)代碼重構(gòu)改變類名往往需要格外注意一點(diǎn)

          ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
          ClassName list = ClassName.get("java.util", "List");
          ClassName arrayList = ClassName.get("java.util", "ArrayList");
          TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);

          MethodSpec beyond = MethodSpec.methodBuilder("beyond")
          .returns(listOfHoverboards)
          .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
          .addStatement("result.add(new $T())", hoverboard)
          .addStatement("result.add(new $T())", hoverboard)
          .addStatement("result.add(new $T())", hoverboard)
          .addStatement("return result")
          .build();
          復(fù)制代碼


          然后生成

          package com.example.helloworld;

          import com.mattel.Hoverboard;
          import java.util.ArrayList;
          import java.util.List;

          public final class HelloWorld {
          List beyond() {
          List result = new ArrayList<>();
          result.add(new Hoverboard());
          result.add(new Hoverboard());
          result.add(new Hoverboard());
          return result;
          }
          }
          復(fù)制代碼


          構(gòu)建類的元素

          • 方法

          方法的修飾,如Modifiers.ABSTRACT

          MethodSpec flux = MethodSpec.methodBuilder("flux")
          .addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED)
          .build();

          TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
          .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
          .addMethod(flux)
          .build();
          復(fù)制代碼


          這將會(huì)生成如下代碼

          public abstract class HelloWorld {
          protected abstract void flux();
          }

          復(fù)制代碼


          當(dāng)然Methods需要和MethodSpec.Builder配置來增加方法參數(shù)、異常、javadoc、注解等。

          • 構(gòu)造器

          這個(gè)其實(shí)也是個(gè)函數(shù)方法而已,因此可以使用MethodSpec來生成構(gòu)造器方法。比如:

          MethodSpec flux = MethodSpec.constructorBuilder()
          .addModifiers(Modifier.PUBLIC)
          .addParameter(String.class, "greeting")
          .addStatement("this.$N = $N", "greeting", "greeting")
          .build();

          TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
          .addModifiers(Modifier.PUBLIC)
          .addField(String.class, "greeting", Modifier.PRIVATE, Modifier.FINAL)
          .addMethod(flux)
          .build();
          復(fù)制代碼


          將會(huì)生成

          public class HelloWorld {
          private final String greeting;

          public HelloWorld(String greeting) {
          this.greeting = greeting;
          }
          }
          復(fù)制代碼


          • 參數(shù)(重要)

          之前我們是通過addstatement直接設(shè)置參數(shù),其實(shí)參數(shù)也有自己的一個(gè)專用類ParameterSpec,我們可以使用ParameterSpec.builder()來生成參數(shù),然后MethodSpec的addParameter去使用,這樣更加優(yōu)雅。

          ParameterSpec android = ParameterSpec.builder(String.class, "android")
          .addModifiers(Modifier.FINAL)
          .build();

          MethodSpec welcomeOverlords = MethodSpec.methodBuilder("welcomeOverlords")
          .addParameter(android)
          .addParameter(String.class, "robot", Modifier.FINAL)
          .build();
          復(fù)制代碼


          生成的代碼

          void welcomeOverlords(final String android, final String robot) {
          }
          復(fù)制代碼


          稍微復(fù)雜點(diǎn)的類型 比如泛型 、Map之類的,需要了解下JavaPoet定義的幾種專門描述類型的類


          常見的有

          分類生成的類型JavaPoet 寫法也可以這么寫 (等效的 Java 寫法)
          內(nèi)置類型intTypeName.INTint.class
          數(shù)組類型int[]ArrayTypeName.of(int.class)int[].class
          需要引入包名的類型java.io.FileClassName.get(“java.io”, “File”)java.io.File.class
          參數(shù)化類型 (ParameterizedTypeListParameterizedTypeName.get(List.class, String.class)-
          類型變量 (WildcardType) 用于聲明泛型TTypeVariableName.get(“T”)-
          通配符類型? extends StringWildcardTypeName.subtypeOf(String.class)-
          /*
          *Build input type, format as :
          *Map>
          */
          ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(
          ClassName.get(Map.class),
          ClassName.get(String.class),
          ParameterizedTypeName.get(
          ClassName.get(Class.class),
          WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))
          )
          );

          /*
          *Map
          */
          ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
          ClassName.get(Map.class),
          ClassName.get(String.class),
          ClassName.get(RouteMeta.class)
          );

          /*
          *Build input param name.
          */
          ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
          ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
          復(fù)制代碼


          生成參數(shù)類型

          public class ARouter$Root$app implements IRouteRoot {
          @Override
          public void loadInto(Map> routes) {
          routes.put("service", ARouter$Group$service.class);
          routes.put("test", ARouter$Group$test.class);
          }
          }

          public class ARouter$Group$service implements IRouteGroup {
          @Override
          public void loadInto(Map atlas) {
          atlas.put("/service/hello", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
          atlas.put("/service/json", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, -1, -2147483648));
          atlas.put("/service/single", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/service/single", "service", null, -1, -2147483648));
          }
          }

          復(fù)制代碼


          • 字段

          可以使用FieldSpec去聲明字段,然后加到Method中處理

          FieldSpec android = FieldSpec.builder(String.class, "android")
          .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
          .build();

          TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
          .addModifiers(Modifier.PUBLIC)
          .addField(android)
          .addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL)
          .build();
          復(fù)制代碼


          然后生成代碼

          public class HelloWorld {
          private final String android;

          private final String robot;
          }
          復(fù)制代碼


          通常Builder可以更加詳細(xì)的創(chuàng)建字段的內(nèi)容,比如javadoc、annotations或者初始化字段參數(shù)等,如:

          FieldSpec android = FieldSpec.builder(String.class, "android")
          .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
          .initializer("$S + $L", "Lollipop v.", 5.0d)
          .build();
          復(fù)制代碼


          對(duì)應(yīng)生成的代碼

          private final String android = "Lollipop v." + 5.0;
          復(fù)制代碼


          • 接口

          接口方法必須是PUBLIC ABSTRACT并且接口字段必須是PUBLIC STATIC FINAL ,使用TypeSpec.interfaceBuilder

          如下

          TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
          .addModifiers(Modifier.PUBLIC)
          .addField(FieldSpec.builder(String.class, "ONLY_THING_THAT_IS_CONSTANT")
          .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
          .initializer("$S", "change")
          .build())
          .addMethod(MethodSpec.methodBuilder("beep")
          .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
          .build())
          .build();
          復(fù)制代碼


          生成的代碼如下

          public interface HelloWorld {
          String ONLY_THING_THAT_IS_CONSTANT = "change";

          void beep();
          }
          復(fù)制代碼


          • 繼承父類 實(shí)現(xiàn)接口

          接口代碼

          package com.test.javapoet;
          public interface TestInterface {
          void test(T testPara);
          }
          復(fù)制代碼


          父類代碼

          public class TestExtendesClass {

          }
          復(fù)制代碼


          使用javapoet實(shí)現(xiàn)接口并且繼承父類

          final ClassName  InterfaceName = ClassName.get("com.test.javapoet","TestInterface");

          ClassName superinterface = ClassName.bestGuess("com.test.javapoet.TestClass");
          //ClassName superinterface = ClassName.get("com.test.javapoet","aa");

          TypeSpec.Builder spec = TypeSpec.classBuilder("TestImpl")
          .addModifiers(Modifier.PUBLIC)
          // 添加接口,ParameterizedTypeName的參數(shù)1是接口,參數(shù)2是接口的泛型
          .addSuperinterface(ParameterizedTypeName.get(InterfaceName, superinterface))
          //使用ClassName.bestGuess會(huì)自動(dòng)導(dǎo)入包
          .superclass(ClassName.bestGuess("com.zs.javapoet.test.TestExtendesClass"));

          MethodSpec.Builder methodSpec = MethodSpec.methodBuilder("test")
          .addAnnotation(Override.class)
          .returns(TypeName.VOID)
          .addParameter(superinterface, "testPara")
          .addStatement("System.out.println(hello)" );

          TypeSpec typeSpec = spec.addMethod(methodSpec.build()).build();

          JavaFile file = JavaFile.builder("com.zs.javapoet", typeSpec).build();
          file.writeTo(System.out);

          復(fù)制代碼


          生成代碼

          package com.test.javapoet;

          import com.zs.javapoet.test.TestExtendesClass;
          import java.lang.Override;

          public class TestImpl extends TestExtendesClass implements TestInterface {
          @Override
          void test(TestClass testPara) {
          System.out.println(hello);
          }
          }
          復(fù)制代碼


          • 枚舉類型

          使用TypeSpec.enumBuilder來創(chuàng)建,使用addEnumConstant來添加

          TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
          .addModifiers(Modifier.PUBLIC)
          .addEnumConstant("ROCK")
          .addEnumConstant("SCISSORS")
          .addEnumConstant("PAPER")
          .build();
          復(fù)制代碼


          生成的代碼

          public enum Roshambo {
          ROCK,

          SCISSORS,

          PAPER
          }
          復(fù)制代碼


          更復(fù)雜的類型也可以支持,如重寫、注解等

          TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
          .addModifiers(Modifier.PUBLIC)
          .addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("$S", "fist")
          .addMethod(MethodSpec.methodBuilder("toString")
          .addAnnotation(Override.class)
          .addModifiers(Modifier.PUBLIC)
          .addStatement("return $S", "avalanche!")
          .build())
          .build())
          .addEnumConstant("SCISSORS", TypeSpec.anonymousClassBuilder("$S", "peace")
          .build())
          .addEnumConstant("PAPER", TypeSpec.anonymousClassBuilder("$S", "flat")
          .build())
          .addField(String.class, "handsign", Modifier.PRIVATE, Modifier.FINAL)
          .addMethod(MethodSpec.constructorBuilder()
          .addParameter(String.class, "handsign")
          .addStatement("this.$N = $N", "handsign", "handsign")
          .build())
          .build();
          復(fù)制代碼


          生成代碼

          public enum Roshambo {
          ROCK("fist") {
          @Override
          public void toString() {
          return "avalanche!";
          }
          },

          SCISSORS("peace"),

          PAPER("flat");

          private final String handsign;

          Roshambo(String handsign) {
          this.handsign = handsign;
          }
          }
          復(fù)制代碼


          • 匿名內(nèi)部類

          需要使用Type.anonymousInnerClass(""),通常可以使用$L占位符來指代

          TypeSpec comparator = TypeSpec.anonymousClassBuilder("")
          .addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class))
          .addMethod(MethodSpec.methodBuilder("compare")
          .addAnnotation(Override.class)
          .addModifiers(Modifier.PUBLIC)
          .addParameter(String.class, "a")
          .addParameter(String.class, "b")
          .returns(int.class)
          .addStatement("return $N.length() - $N.length()", "a", "b")
          .build())
          .build();

          TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
          .addMethod(MethodSpec.methodBuilder("sortByLength")
          .addParameter(ParameterizedTypeName.get(List.class, String.class), "strings")
          .addStatement("$T.sort($N, $L)", Collections.class, "strings", comparator)
          .build())
          .build();
          復(fù)制代碼


          生成代碼

          void sortByLength(List strings) {
          Collections.sort(strings, new Comparator() {
          @Override
          public int compare(String a, String b) {
          return a.length() - b.length();
          }
          });
          }
          復(fù)制代碼


          定義匿名內(nèi)部類的一個(gè)特別棘手的問題是參數(shù)的構(gòu)造。在上面的代碼中我們傳遞了不帶參數(shù)的空字符串。TypeSpec.anonymousClassBuilder("")。

          • 注解

          注解使用起來比較簡單

          MethodSpec toString = MethodSpec.methodBuilder("toString")
          .addAnnotation(Override.class)
          .returns(String.class)
          .addModifiers(Modifier.PUBLIC)
          .addStatement("return $S", "Hoverboard")
          .build();
          復(fù)制代碼


          生成代碼

            @Override
          public String toString() {
          return "Hoverboard";
          }
          復(fù)制代碼


          通過AnnotationSpec.builder() 可以對(duì)注解設(shè)置屬性:

          MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
          .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
          .addAnnotation(AnnotationSpec.builder(Headers.class)
          .addMember("accept", "$S", "application/json; charset=utf-8")
          .addMember("userAgent", "$S", "Square Cash")
          .build())
          .addParameter(LogRecord.class, "logRecord")
          .returns(LogReceipt.class)
          .build();
          復(fù)制代碼


          代碼生成如下

          @Headers(
          accept = "application/json; charset=utf-8",
          userAgent = "Square Cash"
          )
          LogReceipt recordEvent(LogRecord logRecord);
          復(fù)制代碼


          注解同樣可以注解其他注解,通過$L引用如

          MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
          .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
          .addAnnotation(AnnotationSpec.builder(HeaderList.class)
          .addMember("value", "$L", AnnotationSpec.builder(Header.class)
          .addMember("name", "$S", "Accept")
          .addMember("value", "$S", "application/json; charset=utf-8")
          .build())
          .addMember("value", "$L", AnnotationSpec.builder(Header.class)
          .addMember("name", "$S", "User-Agent")
          .addMember("value", "$S", "Square Cash")
          .build())
          .build())
          .addParameter(LogRecord.class, "logRecord")
          .returns(LogReceipt.class)
          .build();
          復(fù)制代碼


          生成代碼

          @HeaderList({
          @Header(name = "Accept", value = "application/json; charset=utf-8"),
          @Header(name = "User-Agent", value = "Square Cash")
          })
          LogReceipt recordEvent(LogRecord logRecord);
          復(fù)制代碼


          注釋

          • javadoc

          0x03 后續(xù)

          在javapoet之前有javawriter,但javapoet有著更強(qiáng)大的代碼模型,并且對(duì)類的理解更加到位,因此推薦使用javapoet

          參考文章

          • juejin.cn/post/684490…

          • cloud.tencent.com/developer/a…

          • blog.csdn.net/qq_26376637…

          作者:渡口一艘船 鏈接:https://juejin.cn/post/6844903456629587976 來源:稀土掘金 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

          —?【 THE END 】—
          公眾號(hào)[程序員黃小斜]全部博文已整理成一個(gè)目錄,請(qǐng)?jiān)诠娞?hào)里回復(fù)「m」獲取!

          最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊(cè),覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù) PDF?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。

          謝謝支持喲 (*^__^*)

          瀏覽 54
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草原成人网站 | 人人人人人人摸 | 天天射天天操天天透人妻 | 欧美狠狠狠 | 曰曰摸日日碰 |