zoukankan      html  css  js  c++  java
  • 注解和反射学习笔记

    注解和反射学习笔记

    狂神注解和反射

    基本把注解和反射介绍明白了,就是虚拟机这部分需从其他地方学习

    注解

    注解定义中,里面的定义指的是参数,不是方法,如下面这个value,表示使用时可以传递一个数组参数值到注解中。

    //镇压警告的意思,当加上@SuppressWarnings("all")注解后则警告便不会再出现,被抑制了。
    @SuppressWarnings("all")
    
    public @interface SuppressWarnings {
        String[] value();
    }
    

    注解.png

    元注解

    元注解:是解释其他注解的注解。

    @Retention——生命周期范围

    表示该定义的注解在哪段生命周期内有效

    //保留,注解的生命周期范围
    @Retention(RetentionPolicy.RUNTIME)
    
    //生命周期 SOURCE < CLASS < RUNTIME
    public enum RetentionPolicy {
     	//源码java文件时有效,当编译成class文件时是不会存在于class文件中的
        SOURCE,
        //编译期class文件时还有效,包含在源码java文件时期,但运行时是不会被加载进虚拟机的,这是默认的生命周期
        CLASS,
        //运行时有效,注解的生命周期范围最大
        RUNTIME
    }
    
    

    @Target——注解目标作用域

    表示该定义的注解可以用在哪些地方。

    //注解的目标作用域
    @Target({ElementType.TYPE})
    
    public enum ElementType {
        //接口、类、枚举、注解
        TYPE,
    
       //域,属性,字段,枚举的常量
        FIELD,
    
       //方法
        METHOD,
    
       //参数
        PARAMETER,
    
       //构造函数
        CONSTRUCTOR,
    
       //局部变量
        LOCAL_VARIABLE,
    
       //注解
        ANNOTATION_TYPE,
    
         //包
        PACKAGE,
    
        /**
         *  //标注类型参数
         *
         * @since 1.8
         */
        TYPE_PARAMETER,
    
        /**
         *  //标注任何类型名称
         *
         * @since 1.8
         */
        TYPE_USE
    }
    

    @Document——是否生成在Javadoc中

    表示该定义的注解是否在生成Javadoc时被包含在其中

    @Inherited——子类是否可以继承父类中的该注解

    表示该定义的注解修饰后,则其修饰的类如果有子类,子类将继承父类的这个注解,也就是说子类也被这个注解修饰。

    元注解.png

    自定义注解

    注意:

    • 注解类的每一个方法实际上是一个配置参数。
    • 当只有一个参数时默认使用value,且命名为value时,在使用注解传递参数时不需要显示指定value="xxx"。

    自定义注解.png

    代码示例——自定义注解

    //目标作用域
    @Target({ElementType.TYPE,ElementType.METHOD})
    //生命周期
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface CustomAnnotation {
        //注解的参数定义:
        //参数类型 参数名();
        String value() default "";
        //default 设置参数默认值,没有设置默认值的参数在使用时必须传参
        int id() default 0;
        //默认值为-1,表示不存在
        long code() default -1;
        String[] foods();
    }
    

    反射

    概述

    日常运用中我们常使用反射和注解配合使用。

    反射使Java从静态语言变成”准动态语言“,可以在运行时决定加载的对象。

    动态语言.png

    反射方式.png

    反射注意的操作API类

    反射涉及的类.png

    类的主动引用和被动引用

    类的主动引用和被动引用.png

    Java类加载器

    类加载器.png

    反射类

    反射类.png

    安全检查方法与反射方法

    当方法、属性值、或者构造器是私有的时候,我们如果想通过反射操作这些操作就需要在操作前先调用这些操作的安全检查设置为不检查,即constructor2.setAccessible(true);

    反射方法调用.png

    安全检查.png

    反射创建对象

    有两种方法通过反射创建对象。

    注意调用Class对象创建对象时要满足访问权限是足够的(比如不能是private,同包下必须大于默认权限如public、protected修饰才行)且有无参构造器才行。

    反射创建对象.png

    反射操作泛型

    反射操作泛型.png

    代码示例

    获取Class类的3种方式+2种其他方式

    public class ReflectTest {
        public static void main(String[] args) throws ClassNotFoundException {
            //1通过类名.class获得
            Class<Tiger> c1 = Tiger.class;
            System.out.println(c1.hashCode());
    
            //2 通过对象获得
            Tiger tiger = new Tiger();
            Class<? extends Tiger> c2 = tiger.getClass();
            System.out.println(c2.hashCode());
            //3通过Class.forName("包名.类名")获得
            Class<?> c3 = Class.forName("com.lai.springbootdemo.reflect.Tiger");
            System.out.println(c3.hashCode());
    
            //B 通过子类class获取父类类型
            Class<?> c4 = c3.getSuperclass();
            System.out.println(c4.getName());
            //A 基本类型的包装类的TYPE属性
            Class<Integer> c5 = Integer.TYPE;
            System.out.println(c5.getName());
        }
        /**
         * 1160460865
         * 1160460865
         * 1160460865
         * com.lai.springbootdemo.reflect.Animal
         * int
         */
    }
    

    所有类型的Class对象

    public class ClassCategoryTest {
        public static void main(String[] args) {
            Class c1 = String.class;//类
            Class c2 = Serializable.class;//接口
            Class c3 = String[].class;//一维数组
            Class c4 = int[][].class;//二维数组
            Class c5 = Override.class;//注解
            Class c6 = ElementType.class;//枚举
            Class c7 = Long.class;//基本数据类型
            Class c8 = void.class;//void
            Class c9 = Class.class;//Class类
            System.out.println(c1);
            System.out.println(c2);
            System.out.println(c3);
            System.out.println(c4);
            System.out.println(c5);
            System.out.println(c6);
            System.out.println(c7);
            System.out.println(c8);
            System.out.println(c9);
            long[] a = new long[10];
            long[] b = new long[100];
            System.out.println(a.getClass().hashCode()==b.getClass().hashCode());//true
        }
        /**
         * class java.lang.String
         * interface java.io.Serializable
         * class [Ljava.lang.String;
         * class [[I
         * interface java.lang.Override
         * class java.lang.annotation.ElementType
         * class java.lang.Long
         * void
         * class java.lang.Class
         * true
         */
    }
    

    获得类加载器

    public class ClassloaderTest {
        public static void main(String[] args) throws Exception {
            //获取系统类加载器,也就是应用类加载器
            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
            System.out.println(systemClassLoader);
    
            //获取应用类加载器的父类加载器——扩展类加载器
            ClassLoader extClassLoader = systemClassLoader.getParent();
            System.out.println(extClassLoader);
    
            //获取扩展类加载器的父类加载器——引导类加载器
            ClassLoader bootStrapClassLoader = systemClassLoader.getParent();
            System.out.println(bootStrapClassLoader);
    
            //获取指定的类的类加载器,看是哪个类加载器加载的
            Class c1 = Class.forName("com.lai.springbootdemo.reflect.Tiger");
    //        Class c2 = Tiger.class;
            ClassLoader classLoader = c1.getClassLoader();
            System.out.println(classLoader);
    
            //JDK内置类是哪个类加载器加载的
            Class c3 = Class.forName("java.lang.String");
            ClassLoader classLoader2 = c3.getClassLoader();
            System.out.println(classLoader2);
    
            Class c4 = Class.forName("sun.net.spi.nameservice.dns.DNSNameService");
            ClassLoader classLoader3 = c4.getClassLoader();
            System.out.println(classLoader3);
        }
        /**
         * sun.misc.Launcher$AppClassLoader@18b4aac2
         * sun.misc.Launcher$ExtClassLoader@452b3a41
         * sun.misc.Launcher$ExtClassLoader@452b3a41
         * sun.misc.Launcher$AppClassLoader@18b4aac2
         * null
         * sun.misc.Launcher$ExtClassLoader@452b3a41
         */
    }
    

    获得类的属性、方法、和构造器

    public class ReflectTest2 {
        public static void main(String[] args) throws Exception {
            Class c1 = Class.forName("com.lai.springbootdemo.reflect.Tiger");
            //获得类的名字
            //获得 包名+类名
            System.out.println(c1.getName());  //com.lai.springbootdemo.reflect.Tiger
            //获得类名
            System.out.println(c1.getSimpleName());   //Tiger
            System.out.println("----------------------------------------");
    
            //获得类的属性,域名
            Field[] fields = c1.getFields();//只能获取到public修饰的属性
            Arrays.stream(fields).forEach((f)->{
                System.out.println(f);
            });
            System.out.println("----------------------------------------");
    
    
            //获得类所有的属性
            Field[] declaredFields = c1.getDeclaredFields();
            Arrays.stream(declaredFields).forEach((f)->{
                System.out.println(f);
            });
            System.out.println("----------------------------------------");
    
            //获得指定名字的类属性,最好用getDeclaredField,因为getField只能获取到public修饰的类属性
            //尝试获取会报错:Exception in thread "main" java.lang.NoSuchFieldException: age
            Field ageField = c1.getDeclaredField("age");
            System.out.println(ageField);//private int com.lai.springbootdemo.reflect.Tiger.age
            //Field ageField = c1.getField("age");
    
            //获得类的方法
            Method[] methods = c1.getMethods();//获取本类及父类的所有public方法
            Arrays.stream(methods).forEach((m)->{
                System.out.println(m);
            });
            System.out.println("----------------------------------------");
            Method[] declaredMethods = c1.getDeclaredMethods();//获取本类所有方法,只是本类自己声明的
            Arrays.stream(declaredMethods).forEach((m)->{
                System.out.println(m);
            });
            System.out.println("----------------------------------------");
            //获得指定名字的类方法
            Method setAgeMethod = c1.getDeclaredMethod("setAge", int.class);
            Method getNameMethod = c1.getDeclaredMethod("getName");
            System.out.println(setAgeMethod);
            System.out.println(getNameMethod);
            System.out.println("----------------------------------------");
    
            //获得类的构造器
            Constructor[] constructors = c1.getConstructors();//获取本类public修饰的构造器
            Arrays.stream(constructors).forEach((c)->{
                System.out.println(c);
            });
            System.out.println("----------------------------------------");
            //获取本类所有的构造器
            Constructor[] declaredConstructors = c1.getDeclaredConstructors();
            Arrays.stream(declaredConstructors).forEach((c)->{
                System.out.println(c);
            });
            System.out.println("----------------------------------------");
            //根据传参获得指定的构造器
            //获取有参私有构造器
            Constructor constructor1 = c1.getDeclaredConstructor(String.class);
            System.out.println(constructor1);
            //无参构造器
            Constructor constructor2 = c1.getDeclaredConstructor();
            System.out.println(constructor2);
        }
        /**
         * com.lai.springbootdemo.reflect.Tiger
         * Tiger
         * ----------------------------------------
         * public java.lang.String com.lai.springbootdemo.reflect.Tiger.skin
         * public static int com.lai.springbootdemo.reflect.Tiger.catogeryType
         * public static final java.lang.String com.lai.springbootdemo.reflect.Tiger.CATOGERY
         * ----------------------------------------
         * private java.lang.String com.lai.springbootdemo.reflect.Tiger.name
         * private int com.lai.springbootdemo.reflect.Tiger.age
         * public java.lang.String com.lai.springbootdemo.reflect.Tiger.skin
         * protected java.lang.String[] com.lai.springbootdemo.reflect.Tiger.food
         * java.lang.Long com.lai.springbootdemo.reflect.Tiger.length
         * public static int com.lai.springbootdemo.reflect.Tiger.catogeryType
         * public static final java.lang.String com.lai.springbootdemo.reflect.Tiger.CATOGERY
         * ----------------------------------------
         * private int com.lai.springbootdemo.reflect.Tiger.age
         * public java.lang.String com.lai.springbootdemo.reflect.Tiger.toString()
         * public java.lang.String com.lai.springbootdemo.reflect.Tiger.getName()
         * public static void com.lai.springbootdemo.reflect.Tiger.staticTest()
         * public final void java.lang.Object.wait() throws java.lang.InterruptedException
         * public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
         * public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
         * public boolean java.lang.Object.equals(java.lang.Object)
         * public native int java.lang.Object.hashCode()
         * public final native java.lang.Class java.lang.Object.getClass()
         * public final native void java.lang.Object.notify()
         * public final native void java.lang.Object.notifyAll()
         * ----------------------------------------
         * public java.lang.String com.lai.springbootdemo.reflect.Tiger.toString()
         * public java.lang.String com.lai.springbootdemo.reflect.Tiger.getName()
         * protected void com.lai.springbootdemo.reflect.Tiger.setName(java.lang.String)
         * int com.lai.springbootdemo.reflect.Tiger.getAge()
         * public static void com.lai.springbootdemo.reflect.Tiger.staticTest()
         * private void com.lai.springbootdemo.reflect.Tiger.setAge(int)
         * ----------------------------------------
         * private void com.lai.springbootdemo.reflect.Tiger.setAge(int)
         * public java.lang.String com.lai.springbootdemo.reflect.Tiger.getName()
         * ----------------------------------------
         * public com.lai.springbootdemo.reflect.Tiger(java.lang.String,int)
         * ----------------------------------------
         * private com.lai.springbootdemo.reflect.Tiger(java.lang.String)
         * com.lai.springbootdemo.reflect.Tiger(int)
         * protected com.lai.springbootdemo.reflect.Tiger()
         * public com.lai.springbootdemo.reflect.Tiger(java.lang.String,int)
         * ----------------------------------------
         * private com.lai.springbootdemo.reflect.Tiger(java.lang.String)
         * protected com.lai.springbootdemo.reflect.Tiger()
         */
    }
    

    通过反射动态得创建对象、通过反射操作方法、属性

    注意:我们之所以通过反射操作方法、属性是因为public Object invoke(Object obj, Object... args)这个invoke方法让我们可以动态得传递操作的对象和参数值进操作,而无需关系是哪个对象.操作。

    public class CreateTest {
        public static void main(String[] args) throws Exception {
            //获得Class对象
            Class c1 = Class.forName("com.lai.springbootdemo.reflect.Tiger");
    
            //通过Class创建对象 ,本质上是调用类的无参构造器
            Tiger tiger = (Tiger) c1.newInstance();
            System.out.println(tiger);
            //如果无参构造器是私有的,则报错:IllegalAccessException: Class com.lai.springbootdemo.reflect.CreateTest can not access a member of class com.lai.springbootdemo.reflect.Tiger with modifiers "private"
    
            System.out.println("-------------------------------------");
            //通过构造器创建对象
            Constructor constructor = c1.getDeclaredConstructor(String.class);
            //跳过安全检查即可以访问私有成员、构造器等
            constructor.setAccessible(true);
            Tiger t1 = (Tiger) constructor.newInstance("武松的虎");
            System.out.println(t1);
    
            Constructor constructor2 = c1.getDeclaredConstructor(String.class, int.class);
            //如果是频繁调用的反射语句加上 constructor.setAccessible(true);可以提高效率,
            // 因为减少了安全检查,public修饰的构造器就不需要加setAccessible也能创建对象成功了
            Tiger t2 = (Tiger)constructor2.newInstance("王婆的瓜虎", 18);
            System.out.println(t2);
            System.out.println("-------------------------------------");
    
            //通过反射调用普通方法
            Method setAge = c1.getDeclaredMethod("setAge", int.class);
            //setAge私有方法
            setAge.setAccessible(true);
            //注意调用是传参类型的正确性,否则报错:IllegalArgumentException: argument type mismatch
    //        setAge.invoke(t1, "998");
            //invoke(对象,方法的参数值)
            System.out.println(setAge.invoke(t1, 998));//null setAge方法返回的是void,所以反射返回的是null
            System.out.println(t1.getAge());//998
    
            Method getName = c1.getDeclaredMethod("getName");
            String name = (String) getName.invoke(t1);//武松的虎
            System.out.println(name);
    
            //静态方法
            Method staticTest = c1.getDeclaredMethod("staticTest");
            //静态方法invoke时第一个对象可以为null,因为静态方法与类相关,与对象无关,
            // 第二个参数是方法参数,有则传,没有则不传
            staticTest.invoke(null);//staticTest
    
            System.out.println("-------------------------------------");
            //通过反射操作属性
            Field age = c1.getDeclaredField("age");
            age.setAccessible(true);
            age.set(t2,1);
            System.out.println(t2.getAge());//1
    
            //静态常量属性是不能设置值的 public static final String CATOGERY = "Tiger";
    //        Field catogery = c1.getDeclaredField("CATOGERY");
    //        catogery.setAccessible(true);
    //        //IllegalAccessException: Can not set static final java.lang.String field com.lai.springbootdemo.reflect.Tiger.CATOGERY to java.lang.String
    //        catogery.set(t2,"OLDTIGER");
    
            Field catogeryType = c1.getDeclaredField("catogeryType");
            catogeryType.setAccessible(true);
            //IllegalAccessException: Can not set static final java.lang.String field com.lai.springbootdemo.reflect.Tiger.CATOGERY to java.lang.String
            catogeryType.set(t2,1000);
            System.out.println(Tiger.catogeryType);//1000
    //        System.out.println(t2.catogeryType);//1000 修改的是静态变量,所有的实例对象对应的静态变量都会改变
    //        System.out.println(t1.catogeryType);//1000
        }
        /**
         * Tiger{name='null', age=0, skin='null', food=null, length=null}
         * -------------------------------------
         * Tiger{name='武松的虎', age=0, skin='null', food=null, length=null}
         * Tiger{name='王婆的瓜虎', age=18, skin='null', food=null, length=null}
         * -------------------------------------
         * null
         * 998
         * 武松的虎
         * staticTest
         * -------------------------------------
         * 1
         * 1000
         */
    }
    

    普通方法和反射方法效率比较

    public class PerformanceTest {
        public static void main(String[] args) throws Exception {
            test1();
            test2();
            test3();
        }
        /**
         * 4ms
         * 1917ms
         * 3212ms
         */
    
        //普通方法调用耗时
        public static void test1(){
            Tiger tiger = new Tiger();
            long start = Instant.now().toEpochMilli();
            for (int i = 0; i < 10_0000_0000; i++) {
                tiger.getName();
            }
            long end = Instant.now().toEpochMilli();
            System.out.println((end-start)+"ms");
        }
    
        //反射方法调用耗时
        public static void test2() throws Exception {
            Class c1 = Class.forName("com.lai.springbootdemo.reflect.Tiger");
            Tiger t = (Tiger) c1.newInstance();
            Method getName = c1.getDeclaredMethod("getName");
            long start = Instant.now().toEpochMilli();
            for (int i = 0; i < 10_0000_0000; i++) {
                getName.invoke(t);
            }
            long end = Instant.now().toEpochMilli();
            System.out.println((end-start)+"ms");
        }
    
        //关闭安全检查反射方法调用耗时
        public static void test3() throws Exception {
            Class c1 = Class.forName("com.lai.springbootdemo.reflect.Tiger");
            Tiger t = (Tiger) c1.newInstance();
            Method getName = c1.getDeclaredMethod("getName");
            getName.setAccessible(true);
            long start = Instant.now().toEpochMilli();
            for (int i = 0; i < 10_0000_0000; i++) {
                getName.invoke(t);
            }
            long end = Instant.now().toEpochMilli();
            System.out.println((end-start)+"ms");
        }
    }
    

    利于注解和反射完成类和表结构的映射关系——ORM

    注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyTable {
        String value() default "";
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyField {
        String columnName();
    
        int lenth();
    
        String type();
    }
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyTransactional {
        String value() default "";
    
        int propagation() default 1;
    
        int timeout() default -1;
    }
    

    对象

    @MyTable("t_user")
    public class User {
        @MyField(columnName = "id",lenth = 10,type = "long")
        private Long id;
        @MyField(columnName = "user_age",lenth = 12,type = "int")
        private Integer age;
        //@MyField注解用于表示数据库中与类User对应的值和类型,比如数据库中名叫user_name,长度20,数据库类型为varchar
        @MyField(columnName = "user_name",lenth = 20,type = "varchar")
        private String name;
    
        public User(Long id, Integer age, String name) {
            this.id = id;
            this.age = age;
            this.name = name;
        }
    
        public User() {
        }
    
        @MyTransactional(propagation = 100,value = "writeable")
        public void setName(String name) {
            this.name = name;
        }
    }
    

    测试类

    public class ORMTest {
    
        public static void main(String[] args) throws Exception {
            Class c1 = Class.forName("com.lai.springbootdemo.reflect.User");
            //通过反射获得注解
            Annotation[] annotations = c1.getDeclaredAnnotations();
            Arrays.stream(annotations).forEach((annotation -> {
                System.out.println(annotation);
            }));
    
            //获得注解的value的值
            MyTable myTable = (MyTable)c1.getAnnotation(MyTable.class);//t_user
    //        MyTable myTable = User.class.getDeclaredAnnotation(MyTable.class);
            System.out.println(myTable.value());
    
            //获得属性上注解的value的值
            Field name = c1.getDeclaredField("name");
    //        Field name = User.class.getDeclaredField("name");
            MyField myField = name.getDeclaredAnnotation(MyField.class);
            System.out.println(myField.lenth());
            System.out.println(myField.type());
            System.out.println(myField.columnName());
    
            //获得方法上注解的value的值
            //1先获得指定方法对象
            Method setName = c1.getDeclaredMethod("setName", String.class);
            //2通过方法对象上指定注解获得方法的注解对象
            MyTransactional myTransactional = setName.getAnnotation(MyTransactional.class);
            //3读取对应的注解配置值
            System.out.println(myTransactional.propagation());
            System.out.println(myTransactional.value());
            System.out.println(myTransactional.timeout());
        }
        /**
         * @com.lai.springbootdemo.annotation.MyTable(value=t_user)
         * t_user
         * 20
         * varchar
         * user_name
         * 100
         * writeable
         * -1
         */
    }
    

    通过反射获取泛型类型

    public class GenericityTest {
        public static void main(String[] args) throws Exception {
            Class<?> c1 = Class.forName("com.lai.springbootdemo.reflect.Genericity");
            //通过反射获得指定名字的类方法
            Method test1 = c1.getDeclaredMethod("test1", List.class, Map.class);
            //获得带泛型参数的参数列表
            Type[] parameterTypes = test1.getGenericParameterTypes();
            Arrays.stream(parameterTypes).forEach((p) -> {
                System.out.println(p);
                //判断如果是带泛型参数则通过强转成ParameterizedType来获得真实的泛型参数getActualTypeArguments
                if (p instanceof ParameterizedType) {
                    Type[] types = ((ParameterizedType) p).getActualTypeArguments();
                    Arrays.stream(types).forEach((type -> {
                        System.out.println(type);
                    }));
                }
            });
            System.out.println("---------------------------------------------");
            //通过反射获得指定名字的类方法
            //通过反射获取方法返回值的真实泛型信息
            Method test2 = c1.getDeclaredMethod("test2");
            Type returnType = test2.getGenericReturnType();
            System.out.println(returnType);
            if (returnType instanceof ParameterizedType) {
                Type[] types = ((ParameterizedType) returnType).getActualTypeArguments();
                Arrays.stream(types).forEach((type -> {
                    System.out.println(type);
                }));
            }
        }
        /**
         * java.util.List<java.lang.String>
         * class java.lang.String
         * java.util.Map<java.lang.Integer, com.lai.springbootdemo.reflect.User>
         * class java.lang.Integer
         * class com.lai.springbootdemo.reflect.User
         * ---------------------------------------------
         * java.util.Map<java.lang.String, java.lang.String>
         * class java.lang.String
         * class java.lang.String
         */
    }
    
    class Genericity {
        public static void test1(List<String> list, Map<Integer, User> map) {
            System.out.println("test1");
        }
    
        public static Map<String, String> test2() {
            System.out.println("test2");
            return null;
        }
    }
    
  • 相关阅读:
    0121 集合类 ArrayList 的练习
    0121 有关接口的使用练习
    泛型相关知识
    0120 父类与子类创建、重写及转型练习
    0118练习 单例模式
    java设计模式 略版
    0117 面向对象OOP有关方法、类、构造方法及权限修饰符的练习
    0115 创建类并调用
    [luogu P2586] GCD 解题报告 (莫比乌斯反演|欧拉函数)
    POJ1284 Primitive Roots (原根)
  • 原文地址:https://www.cnblogs.com/castamere/p/14448308.html
Copyright © 2011-2022 走看看