注解、反射、动态代理——Java教案(十二)

反射

0. 学习资料

(87条消息) 高薪程序员&面试题精讲系列24之你熟悉反射吗?_一一哥-CSDN博客

1. 反射基础

介绍

Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。

通过Java语言中的反射机制可以操作字节码文件。可以读写字节码文件,操作代码片段(class文件)

反射相关的包:

java.lang.reflect.*;

反射相关的类:

  1. java. lang.class:代表整个字节码。代表一个类型,代表整个类
  2. java.lang.refleat.Field:代表字节码中的属性字节码。代表类中的成员变量**(静态变量和静态变量)**
  3. java.lang.reflect.constructor:代表字节码中的构造方法字节码。代表类中的构造方法
  4. java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。
//1.Class
public class User {
    //2.Filed
    private String name;
    //3.Constructor
    public User(){

    }
    //4.Method
    public void setName(String name){

    }
}

获取类

类相关

  1. static 类<?> forName(String className) 返回与给定的字符串名称的类或接口相关的类对象。
  2. ClassLoader getClassLoader() 返回类的类装载器。
  3. String getName() 返回单位名称(类,接口,数组类,原始类型,或无效)的 类对象表示,作为一个 String。
  4. String getSimpleName() 返回在源代码中给定的底层类的简单名称。
    类<? super T> getSuperclass() 返回表示的实体(类、接口类的 类,原始类型或void)的 类代表。
  5. int getModifiers() 返回该类或接口的java语言的修饰,在整数编码。
  6. T newInstance() 创建这个 类对象表示的类的一个新实例。
  7. String toString() 将对象转换为字符串。
  8. Type[] getGenericInterfaces() 返回表示接口,通过该对象表示的类或接口直接实现的 Types。
  9. Type getGenericSuperclass() 返回表示实体的直接父类的 Type(类、接口、简单类型或void)的 类代表。 类<?>[] getInterfaces() 确定由该对象表示的类或接口实现的接口。
  10. String getTypeName() 返回此类型的名称的信息字符串。

与注解相关

  1. boolean isAnnotation() 如果这 类对象表示注释类型返回true。
  2. boolean isAnnotationPresent(类<? extends Annotation> annotationClass) 如果在这个元素上存在指定类型的注释,则返回真,否则为假。

构造器相关

  1. Constructor getConstructor(类<?>… parameterTypes) 返回一个 Constructor对象反映指定的公共构造函数的 类对象表示的类。
  2. Constructor<?>[] getConstructors() 返回一个数组包含 Constructor物体反射所有的 类对象表示的类的公共构造函数。
  3. Constructor getDeclaredConstructor(类<?>… parameterTypes) 返回一个Constructor对象反映指定的构造函数的类或接口的 类对象表示。
  4. Constructor<?>[] getDeclaredConstructors() 返回 Constructor物体反射所有的构造函数通过 类对象表示的类中声明一个数组。

属性相关

  1. Field getDeclaredField(String name) 返回一个对象,反映了 Field指定声明字段的类或接口的 类对象表示。
  2. Field[] getDeclaredFields() 返回 Field物体反射所有字段的类或接口的 类对象表示声明数组。
  3. Field getField(String name) 返回一个 Field对象反映的类或接口的 类对象表示的指定公共成员。
  4. Field[] getFields() 返回一个数组包含 Field物体反射的类或接口的 类对象代表所有可访问的公共领域。

方法相关

  1. Method getDeclaredMethod(String name, 类<?>… parameterTypes) 返回一个 方法对象反映指定声明方法的类或接口的 类对象表示。
  2. Method [] getDeclaredMethods() 返回一个数组包含 方法物体反射所有声明的方法的类或接口的 类对象,代表包括公众、保护,默认(包)的访问,和私有方法,但不包括继承的方法。
  3. Method getMethod(String name, 类<?>… parameterTypes) 返回一个 方法对象反映的类或接口的 类对象表示的指定公共成员方法。
  4. Method [] getMethods() 返回一个数组包含 方法物体反射所有的类或接口的 类对象表示的公共方法,包括那些由类或接口的超类和超接口继承的声明。
获取类的三种方式

Java提供了三种通过反射获取类的范式。

  1. Class.forName(“类的包路径.类名”)

    1. public class ReflectTest01 {
          public static void main(String[] args) {
              try {
                  Class c1 = Class.forName("java.lang.String");//c1表示String.class文件,表示String类型
                  Class c2 = Class.forName("java.util.Date");//表示Date类型
                  Class c3 = Class.forName("java.lang.Integer");//表示Integer类型
                  Class c4 = Class.forName("java.lang.System");//表示System类型
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
              }
          }
      }
      
    2. 注意:我们可以使用Class.forName会导致类加载,可以来执行某个类的静态代码块。

      1. 当我们只需要加载静态代码快,类的其他内容不执行时,我们可以通过Class.forName(“完整类名”)来实现。

      2. 该方法会导致类加载时,静态代码块执行。(jdbc注册驱动使用)。

      3. public class ReflectTest03 {
            public static void main(String[] args) {
                try {
                    Class.forName("lession_21_12_06_reflect.User");
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
        public class User {
            static {System.out.println("这是User类的静态代码块");}
        }
        
      4. image-20211206201753064

  2. 对象.class()

    1. public class ReflectTest01 {
          public static void main(String[] args) {
              String  str  = "123";
              Class<? extends String> x = str.getClass();
              System.out.println(aClass);//class java.lang.String
              Date date = new Date();
               Class<? extends Date> y = date.getClass();
              System.out.println(y);//class java.util.Date
          }
      }
      
    2. 进行对比

      1. 	Class c1 = Class.forName("java.lang.String");
          		Class<? extends Date> y = date.getClass();
               System.out.println(c1==x);//true
               System.out.println(c2==y);//true
        
      2. image-20211206155255923

  3. 任意类型.class;

    1. public class ReflectTest01 {
          public static void main(String[] args) {
              //方式三:
              Class<String> s = String.class;
              Class<Date> d = Date.class;
              Class<Integer> i = int.class;
              Class<Character> c = char.class;
          }
      }
      
      

汇总

public class ReflectTest01 {
    public static void main(String[] args) {
        //方式一:
        Class c1 =null;
        Class c2=null;
        try {
            c1 = Class.forName("java.lang.String");//c1表示String.class文件,表示String类型
             c2 = Class.forName("java.util.Date");//表示Date类型
            Class c3 = Class.forName("java.lang.Integer");//表示Integer类型
            Class c4 = Class.forName("java.lang.System");//表示System类型
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //方式二:
        String  str  = "123";
        Class<? extends String> x = str.getClass();
        System.out.println(x);//class java.lang.String
        Date date = new Date();
        Class<? extends Date> y = date.getClass();
        System.out.println(date.getClass());//class java.util.Date
        System.out.println(c1==x);
        System.out.println(c2==y);
        //方式三:
        Class<String> s = String.class;
        Class<Date> d = Date.class;
        Class<Integer> i = int.class;
        Class<Character> c = char.class;
    }
}

创建对象

通过反射创建对象的,使用的类的newInstance()方法,该方法在Java9已经淘汰。

new Instance()方法会调用User这个类的无参数构造方法,完成对象的创建。因此,如果一个类没有提供无参构造方法,是会出错的。

public class ReflectTest02 {
    public static void main(String[] args) {
        //创建对象
        User user = new User();
        System.out.println(user);//lession_21_12_06_reflect.User@1540e19d
        //使用反射创建对象
        try {
            Class aClass = Class.forName("lession_21_12_06_reflect.User");
            //通过反射机制来获取实例
            Object object = aClass.newInstance();
            System.out.println(object);//lession_21_12_06_reflect.User@677327b6
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

获取父类或接口
class  Father{

}
interface Action{

}
public class Student extends Father implements Action{}
public class QueryMethodTest {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class studentClass = Class.forName("lession_21_12_06_reflect.Student");
        Class superclass = studentClass.getSuperclass();
        System.out.println(superclass.getSimpleName());
        Class[] interfaces = studentClass.getInterfaces();
        for (int i = 0; i < interfaces.length; i++) {
            System.out.println(interfaces[i].getSimpleName());
        }
    }
课堂练习
public class Student {
    public static String name;
    static {
        System.out.println("静态代码块执行了");
        name = "123456";
    }

}
public class ReflectClass {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, NoSuchFieldException, InstantiationException, IllegalAccessException {
        //热身
        Class threadClass = Class.forName("java.lang.Thread");
        String threadClassName = threadClass.getName();
        System.out.println("类名称"+threadClassName);
        String threadSimpleName = threadClass.getSimpleName();
        System.out.println("类简称"+threadSimpleName);
        System.out.println("直接父类"+threadClass.getSuperclass());
        Class[] interfaces = threadClass.getInterfaces();
        for (Class item:interfaces
             ) {
            System.out.println("接口"+item);
        }
        Constructor constructor = threadClass.getConstructor();
        System.out.println("构造方法"+constructor);
        Method[] methods = threadClass.getMethods();
        for (Method method : methods
        ) {
            System.out.println("普通方法"+method);
        }
        Field[] fields = threadClass.getFields();
        for (Field field : fields
        ) {
            System.out.println("公共属性"+field);
        }
        Field[] declaredFields = threadClass.getDeclaredFields();
        for (Field field : declaredFields
        ) {
            System.out.println("所有属性"+field);
        }
        int modifiers = threadClass.getModifiers();
        System.out.println("类的修饰符"+Modifier.toString(modifiers));
        Field threadInitNumber = threadClass.getDeclaredField("threadInitNumber");
        System.out.println(Modifier.toString(threadInitNumber.getModifiers()));
//使用反射,获取类的几种方式。
        //1. 使用Class.forName()获取
        Class<?> stringClass = Class.forName("java.lang.String");
        System.out.println(stringClass);
        //2. 通过实例获取
        String str = new String("张三");
        Class strClass = str.getClass();
        System.out.println(strClass);
        System.out.println(stringClass==strClass);
        //3. 通过类获取
        Class stringClass1 = String.class;
        System.out.println(strClass==stringClass1);
//使用反射,进行类加载,静态代码块执行。
        Class studentClass = Class.forName("lession_21_12_08_reflect.Student");
        Student student1 = (Student)studentClass.newInstance();
        System.out.println(student1);
        Student student2 = (Student)studentClass.newInstance();
        System.out.println(student2);


    }
}

获取属性

  1. int getModifiers() 返回的 Field对象表示的java语言修饰符为整数。

  2. String getName() 返回的 Field对象表示的字段的名称。

  3. Object get(Object obj) 返回的 Field表示字段的值,指定对象上。

  4. 类<?> getType() 返回一个 类对象标识声明类型的 Field对象表示的类型。

  5. int hashCode() 返回该 Field hashCode。

  6. boolean isEnumConstant() 返回 true如果该字段表示枚举类型的元素;否则返回 false。

  7. void set(Object obj, Object value) 为对象obj的当前属性设置值为value

  8. void setAccessible(boolean flag) true,允许访问和修改private等权限修饰符修饰的属性。

基本使用

我们可以通过反射机制,获取每一个类的属性名称,属性类型,修饰符等信息。

常用API:

public class Student {
    //使用不同的权限修饰符修饰
    public static final int no=0;
    private String name;
    protected int age;
    boolean sex;
}
public class ReflectFiledTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class studentClass = Class.forName("lession_21_12_06_reflect.Student");
        //获取完整类名
        String className = studentClass.getName();
        System.out.println("完整类名"+className);
        //获取简写类名
        String simpleName = studentClass.getSimpleName();
        System.out.println("简写类名"+simpleName);
        //获取类中的所有public修饰的属性
        Field[] fields = studentClass.getFields();
        System.out.println(fields.length);
        Field field = fields[0];
        //获取属性名
        String fieldName = field.getName();
        System.out.println("属性名"+fieldName);
        //获取所有的属性
        Field[] allFields = studentClass.getDeclaredFields();
        System.out.println(allFields.length);
        for (int i =0; i <allFields.length; i++) {
            System.out.println("属性"+i);
            Field field1 = allFields[i];
            System.out.println("属性名"+field1.getName());
            //获取修饰符列表
            int modifiers = field1.getModifiers();
            String modifierName= Modifier.toString(modifiers);
            System.out.println("权限修饰附:"+modifiers+"——"+modifierName);
            //获取属性类型
            Class<?> type = field1.getType();
            String typeName = type.getName();
            System.out.println("属性类型名称:"+typeName);
        }

    }
}
获取/修改属性值

修改属性时,对于私有属性的修改和获取,我们需要设置nameField.setAccessible(true); 允许我们对私有属性参与修改。(有风险)

nameField.setAccessible(true); 打破封装,让外部类也可以访问私有成员

修改属性的要素:1. 对象 2. 属性 3.属性值

获取属性的要素:1. 对象 2. 属性

反射机制让我们代码更复杂,但是让程序更灵活,我们可以通过配置文件,来创建对象并为对象赋值(JavaEE企业级开发都是用这种方式。)

public class Student {
    //使用不同的权限修饰符修饰
    public static final int no=0;
    private String name;
    protected int age;
    boolean sex;
}
public class QueryFiledTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException {
        //设置producted修饰的属性
        Class studentClass = Class.forName("lession_21_12_06_reflect.Student");
        Object o = studentClass.newInstance();
        Field ageField = studentClass.getDeclaredField("age");
        ageField.set(o, 18);
        System.out.println(ageField.get(o));
        //设置private修饰的属性
        Field nameField = studentClass.getDeclaredField("name");
        nameField.setAccessible(true);//设置为可以修改私有成员
        nameField.set(o, "张三");
        System.out.println(nameField.get(o));
    }
}
课堂练习
public class Student {
    public static String name;
    private Integer age;
    private String school;

    static {
        System.out.println("静态代码块执行了");
        name = "123456";
    }

    public static String getName() {
        return name;
    }

    public static void setName(String name) {
        Student.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getSchool() {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", school='" + school + '\'' +
                '}';
    }
}
public class ReflectField {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {


        //获取属性信息
        Class aClass = Class.forName("lession_21_12_08_reflect.Student");
        Field schoolName = aClass.getDeclaredField("school");
        System.out.println("修饰符"+ Modifier.toString(schoolName.getModifiers()));
        System.out.println("类型"+schoolName.getType());
        System.out.println("属性名称"+schoolName.getName());
        System.out.println("GenericType"+schoolName.getGenericType());
        Student student = new Student();
        student.setAge(18);
        student.setSchool("河南师范大学");
        System.out.println(student);
        schoolName.setAccessible(true);
        schoolName.set(student,"河南大学");
        System.out.println(schoolName.get(student));
        System.out.println(student);


        //获取属性值和修改属性值
        Student student1 = new Student();
        student1.setAge(3);
        student1.setSchool("阳光幼儿园");
        Class<? extends Student> student1Class = student1.getClass();
        Field age = student1Class.getDeclaredField("age");
        Field school = student1Class.getDeclaredField("school");
        age.setAccessible(true);
        school.setAccessible(true);
        System.out.println(age.get(student1));
        System.out.println(school.get(student1));
        System.out.println(student1);
        //修改属性
        age.set(student1,8);
        school.set(student1,"太阳小学");
        System.out.println(student1);
        
        //设置静态属性值
        Field name = studentClass.getDeclaredField("name");
        name.set(null,"李四");
        System.out.println(name.get(null));
    }
}

获取方法

  1. int getModifiers() 返回该对象表示可执行的java语言 modifiers。
  2. 类<?> getReturnType() 返回一个 类表示这 方法对象表示法的形式返回类型。
  3. String getName() 返回的 方法对象表示的方法的名称,作为一个 String。
  4. 类<?> getDeclaringClass() 返回表示的类或接口声明可执行该对象表示的类对象。
  5. int getParameterCount() 返回该对象表示的可执行文件的形式参数(无论是否显式声明或隐式声明或隐式声明的或不隐式声明的)的数量。
  6. 类<?>[] getParameterTypes() 返回表示形式参数类型 类对象的数组,在声明顺序,该对象表示的可执行文件。
  7. TypeVariable<方法>[] getTypeParameters() 返回表示该类型声明的变量
  8. 类<?>[] getExceptionTypes() 返回表示异常的类型声明是由底层执行该对象表示的 类抛出对象数组。
  9. Object invoke(Object obj, Object… args) 调用底层的方法,这 方法对象表示,对指定对象的指定参数。

方法要素:

  1. 对象
  2. 方法名
  3. 参数
  4. 返回值类型
基本使用
public class QueryMethodTest {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class studentClass = Class.forName("lession_21_12_06_reflect.Student");
        Object o = studentClass.newInstance();
        Method[] methods = studentClass.getDeclaredMethods();
        for (Method method : methods
        ) {
            System.out.println("方法-------------");
            int modifiers = method.getModifiers();
            System.out.println("修饰符"+Modifier.toString(modifiers));
            System.out.println("返回值类型"+method.getReturnType().getName());
            System.out.println("方法名"+method.getName());
            Class[] parameters = method.getParameterTypes();
            for (Class parameter : parameters
            ) {
                System.out.println("参数"+parameter.getSimpleName());
            }

        }
    }
}
调用方法
public class QueryMethodTest {
    public static void main(String[] args) throws Exception {
        Method setNameAndAge = studentClass.getDeclaredMethod("setNameAndAge", String.class, int.class);
        Method setName = studentClass.getDeclaredMethod("setName", String.class);

        Object o1 = setNameAndAge.invoke(o, "张三", 18);
        //o为对象,o1,o2为方法的返回值
        System.out.println(o);
        System.out.println(o1);
        Object o2 = setName.invoke(o, "张三");
        System.out.println(o2);
    }
}
public class Student {
    //使用不同的权限修饰符修饰
    public static final int no = 0;
    private String name;
    protected int age;
    boolean sex;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String setName(String name) {
        this.name = name;
        return this.name;
    }

    public Student setNameAndAge(String name, int age) {
        this.name = name;
        this.age = age;
        new Student();
        return new Student(name, age);
    }
}
课堂练习
public class Student {
    public static String name;
    private Integer age;
    private String school;

    static {
        System.out.println("静态代码块执行了");
        name = "123456";
    }

    public static String getName() {
        return name;
    }

    public static void setName(String name) {
        Student.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getSchool() throws Exception {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", school='" + school + '\'' +
                '}';
    }
}
public class ReflectMethod {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
        //获取方法
        Class aClass = Class.forName("lession_21_12_08_reflect.Student");
        Method[] methods = aClass.getDeclaredMethods();
        //获取方法信息
        for (Method method : methods
        ) {
            System.out.println("修饰符" + Modifier.toString(method.getModifiers()));
            System.out.println("返回值类型" + method.getReturnType());
            System.out.println("名称" + method.getName());
            System.out.println("参数" + method.getParameters());
            System.out.println("泛型" + method.getTypeParameters());
            System.out.println("异常" + method.getExceptionTypes());
            System.out.println("注解" + method.getAnnotations());
            System.out.println(method);
        }

        Student student = new Student();
        System.out.println("初始" + student);
        Class studentClass = student.getClass();
        Method nameMethod = studentClass.getDeclaredMethod("setName", String.class);
        nameMethod.invoke(student, "张三");
        Method ageMethod = studentClass.getDeclaredMethod("setAge", Integer.class);
        ageMethod.invoke(student, 198);
        Method schoolMethod = studentClass.getDeclaredMethod("setSchool", String.class);
        schoolMethod.invoke(student, "河南大学");
        System.out.println(student);
         //调用静态方法
        nameMethod.invoke(null,"张三");
        System.out.println(Student.getName());

    }
}

获取构造器

  1. 类 getDeclaringClass() 返回表示的类或接口声明可执行该对象表示的类对象。

  2. 类<?>[] getExceptionTypes() 返回 类对象表示异常的类型声明是由底层执行该对象表示抛出数组。

  3. int getModifiers() 返回该对象表示可执行的java语言 modifiers。

  4. String getName() 返回这个构造函数的名称,作为一个字符串。

  5. int getParameterCount() 返回该对象表示的可执行文件的形式参数(无论是否显式声明或隐式声明或隐式声明的或不隐式声明的)的数量。

  6. 类<?>[] getParameterTypes() 返回表示形式参数类型 类对象的数组,在声明顺序,该对象表示的可执行文件。

  7. TypeVariable<Constructor>[] getTypeParameters() 返回 TypeVariable对象表示的类型变量的声明通过 GenericDeclaration对象表示的泛型声明一个数组,在声明顺序。

  8. T newInstance(Object… initargs) 利用这 Constructor对象创建和初始化的构造函数的声明类的一个新实例构造函数,用指定的初始化参数。

public class Student {
    //使用不同的权限修饰符修饰
    public static final int no = 0;
    private String name;
    protected int age;
    boolean sex;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

}
public class ConstructTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class studentClass = Class.forName("lession_21_12_06_reflect.Student");
        Constructor[] constructors = studentClass.getConstructors();
        for (Constructor constructor:constructors
             ) {
            System.out.println("权限修饰符"+Modifier.toString(constructor.getModifiers()));
            System.out.println("名称"+constructor.getName());
        }
        Constructor constructor = studentClass.getDeclaredConstructor(String.class, int.class);
        Object o = constructor.newInstance("张三", 18);
        System.out.println(o);

        Constructor constructor2 = studentClass.getDeclaredConstructor();
        Object o2 = constructor2.newInstance();
        System.out.println(o2);

    }
}

课堂练习

public class Student {
    public static String name;
    private Integer age;
    private String school;

    public Student() {
        System.out.println("无参构造");
    }

    public Student(String school) {
        System.out.println("单参构造");
        this.school = school;
    }

    public Student(Integer age, String school) {
        System.out.println("全参构造");
        this.age = age;
        this.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", school='" + school + '\'' +
                '}';
    }
}
public class ReflectConstructor {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //获取操作
        Class studentClass = Class.forName("lession_21_12_08_reflect.Student");
        Constructor constructor0 = studentClass.getConstructor();
        Constructor constructor1 = studentClass.getConstructor(String.class);
        Constructor constructor2 = studentClass.getConstructor(Integer.class, String.class);
        Constructor[] constructors = studentClass.getConstructors();
        for (Constructor constructor : constructors
        ) {
            System.out.println("修饰符" + Modifier.toString(constructor.getModifiers()));
            System.out.println("名称" + constructor.getName());
            System.out.println("注解");
            Arrays.stream(constructor.getParameterAnnotations()).forEach(System.out::println);
            System.out.println("参数" + constructor.getParameterTypes());

        }
        //使用
        Constructor constructor = studentClass.getConstructor(Integer.class, String.class);
        Student student = (Student)constructor.newInstance(18, "河南师范大学");
        System.out.println(student);
    }
}

4. 实战

需求:写一个判断对象的属性值是否全为null的工具类。

全为null:返回true

有一个不为null:返回false

final类型和静态类型的变量不需要判断

class Employee {
    Integer id;
    String name;
    Integer age;
}

class Teacher {
    private Integer id;
    static final String name = "123";
    static final Integer age = 123;
}

public class ReflectUtils {
    public static void main(String[] args) throws IllegalAccessException {
        Employee employee1 = new Employee();
        employee1.name = "雇员1";
        employee1.age = 19;
        Employee employee2 = new Employee();
//        employee2.name = "雇员2";
        Teacher teacher1 = new Teacher();
        System.out.println(ReflectUtils.isAllNull(employee1));//false
        System.out.println(ReflectUtils.isAllNull(employee2));//true
        System.out.println(ReflectUtils.isAllNull(teacher1));//false
        System.out.println(ReflectUtils.isAllNull(new Teacher()));//true
    }

    public static boolean isAllNull(Object obj) throws IllegalAccessException {
        Class objClass = obj.getClass();
        Field[] fields = objClass.getDeclaredFields();
        for (Field field : fields
        ) {
            if (Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())) continue;
            field.setAccessible(true);
            if (field.getName().equals("id")) continue;
            if (field.get(obj) != null) return false;
            field.setAccessible(false);
        }
        return true;
    }
}

2. 文件处理优化

公共Student类

public class Student {
    public static String name;
    private Integer age;
    private String school;

    public Student() {
        System.out.println("无参构造");
    }


    static {
        System.out.println("静态代码块执行了");
        name = "123456";
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", school='" + school + '\'' +
                '}';
    }
}

文件处理

我们可以使用反射来读取相应的配置文件,并且生成相应对象,这样更加灵活。我们可以根据配置文件,在系统中创建不同的对象。

优点:代码不用改动,修改配置文件来创建不同对象。非常灵活

高级框架底层都采用了反射机制。

public class ReflectFile {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, FileNotFoundException {
        //普通方式创建对象,代码写死,只能创建Student对象
//        Student u = new Student();
//        System.out.println(u);

        //通过反射创建对象
        FileReader reader = new FileReader("user.properties");
        Properties properties = new Properties();
        properties.load(reader);
        reader.close();
        String user = properties.getProperty("user");
        String age = properties.getProperty("age");
        String name = properties.getProperty("name");
        String school = properties.getProperty("school");
        Class aClass = Class.forName(user);
        Object o = aClass.newInstance();
        Class<?> studentClass = o.getClass();
        Field[] fields = studentClass.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            fields[i].setAccessible(true);
            if (fields[i].getName().equals("name")) {
                fields[i].set(o, name);
                continue;
            } else if (fields[i].getName().equals("age")) {
                fields[i].set(o, Integer.valueOf(age));
                continue;
            } else if (fields[i].getName().equals("school")) {
                fields[i].set(o, school);
                continue;
            }
        }
        System.out.println(o);
    }
}

user.properties在当前项目目录下,内容是:

user=lession_21_12_08_reflect.Student
age=18
name=张三
school=河南师范

文件路径优化

在idea中,我们使用相对路径默认是以当前工程目录为起始目录。如果我们在此写死,很可能使用其他软件运行项目的时候,运行不起来。因为不同的编辑器对路径的处理不同。

如果我们使用绝对路径,以盘符开始,此时当我们切换操作系统运行代码时,如我们的代码在mac和linux上运行,他们就没有盘符,因此此方法也不可行。

推荐优先使用类路径存储文件,即当前模块的src目录。我们的文件都存储在这个下面,这样就可以适配不同的编辑器和操作系统。

image-20211206203416209

通过类路径获取文件(麻烦)

user.properties在当前模块的src目录下。

public class ReflectFileTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
         String path = Thread.currentThread()//当前线程对象
                .getContextClassLoader()//当前类加载器
                .getResource("user.properties")//获取资源(默认从类路径(当前模块的src)下加载资源)
                .getPath();//获取绝对路径(该方法跨平台)
        System.out.println(path);
        FileReader reader = new FileReader(path);
        Properties properties = new Properties();
        properties.load(reader);
        String user = properties.getProperty("user");
        String age = properties.getProperty("age");
        String name = properties.getProperty("name");
        String school = properties.getProperty("school");
        Class studentClass = Class.forName(user);
        Object o = studentClass.newInstance();
        Field[] fields = studentClass.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            fields[i].setAccessible(true);
            if (fields[i].getName().equals("name")) {
                fields[i].set(o, name);
                continue;
            } else if (fields[i].getName().equals("age")) {
                fields[i].set(o, Integer.valueOf(age));
                continue;
            } else if (fields[i].getName().equals("school")) {
                fields[i].set(o, school);
                continue;
            }
        }
        System.out.println(o);
        //优化
        InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("user.properties");
        Properties properties1 = new Properties();
        properties1.load(resourceAsStream);
        String user = properties1.getProperty("user");
        Class aClass = Class.forName(user);
        Object o = aClass.newInstance();
        System.out.println(o);
    }
}

path打印: /G:/ComputerStudy/JavaLession/hsdlession/lession_12/data_lesssion/out/production/ready_lession/user.properties

使用资源绑定器(简单)

资源绑定器ResourceBundle可以帮助我们快速获取并处理类路径下的文件。

注意:

  1. ResourceBundle只能获取类路径下的properties文件,不能获取其他文件
  2. 文件不能带后缀名
public class ReflectFileTest {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException  {
        //使用资源绑定器
           ResourceBundle bundle = ResourceBundle.getBundle("lession_21_12_08_reflect.user");
        String user = bundle.getString("user");
        Class aClass = Class.forName(user);
        Object o = aClass.newInstance();
        System.out.println(o);
    }
}

文件获取路径和Class路径的写法区别

使用Stream获取文件的时候,我们必须填写文件的后缀名。因此路径是以/或者\ \进行分割

使用Class.forName()获取的是某个包下的类,因为我们的写法是所在包的写法,以.进行分割

使用ResourceBundle,.进行分割。只不过它只能获取类路径(src)下的properties文件。

3. 类加载器

类加载器是负责加载类的命令/工具。ClassLoader

JDK中自带了3个类加载器

  1. 启动类加载器:C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar
  2. 扩展类加载器: C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext/*.jar
  3. 应用类加载器: classpath中配置的路径.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar

String s = “abc”;代码的执行步骤

代码在开始执行之前,会将所需要类全部加载到JVM当中。

通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载。

加载流程:

  1. 首先通过"启动类加载器"加载。
    1. 注意:启动类加载器专门加载:C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar
    2. rt.jar中都是JDK最核心的类库。
  2. 如果通过"启动类加载器"加载不到的时候,会通过"扩展类加载器"加载。
    1. 注意:扩展类加载器专门加载: C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext/*.jar
  3. 如果"扩展类加载器"没有加载到,那么会通过"应用类加载器"加载。
    1. 注意:应用类加载器专门加载: classpath中的类。

双亲委派机制

java中为了保证类加载的安全,使用了双亲委派机制。

优先从启动类加载器中加载,,这个称为"父",父"无法加载到,再从扩展类加载器中加载,这个称为"母"。

双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。

注解

1. 注解的定义

注解的定义

**注解:**又称注释类型,是类的一种,编译后生成.class文件。

语法格式:

//自定义注解
[修饰符列表] @interface 注解类型名称 {}

注解使用地方:

注解可以定义的地方:注解,接口,抽象类,类,类成员变量,类方法,构造方法,实例变量,实例方法,形参注解,

注解不可以定义的地方:静态代码块,初始化块

//自定义注解
public @interface MyAnnotation {}

//注解在注解上使用
@MyAnnotation
public @interface MyAnnotationTwo {}

//注解在类里面使用
@MyAnnotation
public class AnnotationTest {
    
    @MyAnnotation
    String name;
    
    @MyAnnotation
    static  String age;
    
    @MyAnnotation //报错
    static {}
    
    @MyAnnotation
    public AnnotationTest(){}
    @MyAnnotation
    public static void getName(){}
    
    @MyAnnotation //报错
    {}
    
    @MyAnnotation
    public void getAge(@MyAnnotation String name,@MyAnnotation Integer age){ }
}

内置注解

java.lang包下提供了三个注解:

Deprecated:用@Deprecated注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择或已过时。

Override:表示一个方法声明打算重写超类中的另一个方法声明。

Suppresswarnings指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告

两个元注解:

Target和Retention

Overide注解

标识性注解,给编译器作参考。

该注解只能注解方法,编译器看到方法上有这个注解后,会自动检查该方法是否重写了父类的方法,如果没有重写则报错。

该注解只在编译阶段起作用,和运行期无关!

表示一个方法声明打算重写超类中的另一个方法声明。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}    


    @Override
    public String toString() {
        return "AnnotationTest{" +
                "name='" + name + '\'' +
                '}';
    }
Deprecated注解

标注的元素已过时,有更好的方法或很危险

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

使用

public class DeprecatedTest {
    @Deprecated
    public static  void eat(){ }
    @Deprecated
    public static  void drink(){ }
}

image-20211207203411247

元注解

标注“注解类型的注解”称为原注解。

常用的元注解:

  1. Target:“被标注的注解”可以出现在那些位置上

    1. Target(ElementType枚举的实例)

      1. public enum ElementType { 	
        	TYPE,
            FIELD,
            METHOD,
            PARAMETER,
            CONSTRUCTOR,
            LOCAL_VARIABLE,
            ANNOTATION_TYPE,
            PACKAGE,
            TYPE_PARAMETER,
            TYPE_USE
        }
        
      2. 如注解:

        1. @Documented
          @Retention(RetentionPolicy.RUNTIME)
          @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
          public @interface Deprecated {
          }
          
  2. Retention(保留):“被标注的注解”最终保存在哪里

    1. Retention(RetentionPolicy枚举的实例)

      1. public enum RetentionPolicy {
            SOURCE,//表示该注解只被保留在Java源文件中(编译后生成的.class文件中不包含本注解)
            CLASS,//便是该注解被保存在class文件中
            RUNTIME//表示该注解被保存在class文件中,并且可以被反射机制读取到
        }
        
    2. 如注解

      1. @Target(ElementType.METHOD)
        @Retention(RetentionPolicy.SOURCE)
        public @interface Override {
        }
        

2. 注解的使用

注解的使用事项:

  1. 注解内只能创建属性
  2. 创建属性时,可以设置默认值,设置过默认值的注解在使用的时候可以不重新赋值。如果重新赋值,新附的值会覆盖默认值。
  3. 使用注解的时候,没有默认值的注解要赋值,赋值的时候要指定属性名称
  4. 如果注解中只有一个注解,并且注解的名字是value,在使用注解的时候进行赋值时,可以不指定属性名称。

创建注解

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String name();
    String color();
    int age() default 25;
    String[] arr();
    Season season();
}

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotaionTwo {
    String value();
}

使用注解

  @MyAnnotation(name = "张三", color = "红色", arr = {"12", "34"}, season = Season.SPRING)
public class MyAnnotationTest02 {
    @MyAnnotation(name = "张三", color = "红色", arr = {"12", "34"}, season = Season.SPRING)
    @MyAnnotaionTwo("王者")
    public void eat() {
    }
    
    @MyAnnotaionTwo("王者")
    public void drink() {
    }
}

通过反射机制获取注解

@MyAnnotation(name = "张三", color = "红色", arr = {"12", "34"}, season = Season.SPRING)
public class MyAnnotationTest02 {
    @MyAnnotation(name = "张三", color = "红色", arr = {"12", "34"}, season = Season.SPRING)
    @MyAnnotaionTwo("王者")
    public void eat() {
    }

    @MyAnnotaionTwo("王者")
    public void drink() {
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        Class aClass = Class.forName("lession_21_12_07_annotation.MyAnnotationTest02");
        //获取类注解
        if (aClass.isAnnotationPresent(MyAnnotation.class)) {
            Annotation annotation = aClass.getAnnotation(MyAnnotation.class);
            MyAnnotation myAnnotation=  (MyAnnotation)annotation;
            System.out.println(myAnnotation.age());
            System.out.println(myAnnotation.name());
            System.out.println(myAnnotation.color());
        }
        //获取方法注解
        Method name = aClass.getDeclaredMethod("eat");
        System.out.println(name);
        System.out.println(name.isAnnotationPresent(MyAnnotaionTwo.class));
        if (name.isAnnotationPresent(MyAnnotaionTwo.class)){
            Annotation annotation = name.getAnnotation(MyAnnotaionTwo.class);
            MyAnnotaionTwo myAnnotation=  (MyAnnotaionTwo)annotation;
            System.out.println(myAnnotation.value());
        }
    }
}

3. 注解实战

创建一个@ID注解,该注解只能修饰类,如果该类中没有int 类型的id属性,则进行报错。

注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ID {
}

逻辑判断

public class AnnotationTest03 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class aClass = Class.forName("lession_21_12_07_annotation.Student");
        boolean flag = false;
        if (aClass.isAnnotationPresent(ID.class)){
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field field:declaredFields
                 ) {
                field.setAccessible(true);
                if ("id".equals(field.getName())&&"int".equals(field.getType().getSimpleName())){
                    flag=true;
                }

            }
        }
        if (!flag){
            throw new HasNotIdPropertiesException("被@ID注解的属性必须有属性名");
        }
    }
}

自定义异常类

public class HasNotIdPropertiesException extends RuntimeException {
    public HasNotIdPropertiesException(String s) {
        super(s);
    }
}

动态代理

简介

功能:

可以增强方法传入的参数,还可以增强方法的本身

  1. 源代码不动的情况下,增加功能

    1. 在原来的类不变的情况下,通过动态代理,给原来的类添加一些功能。
    2. 可以在程序的执行过程中,创建代理对象。通过代理对象执行方法,给目标类的方法增加额外的功能(功能增强)
    3. 可以增强方法传入的参数,还可以增强方法的本身
  2. 减少代码的重复

  3. 专注于业务逻辑

  4. 解耦合,让你的业务功能和日志,事务非业务功能分离。

实现方式

  1. jdk动态代理

    1. jdk动态代理要求目标对象必须实现接口,java设计上的要求
  2. CGLIB动态代理 (spring框架的)

    1. 不需要接口,原理是继承,目标类必须有继承(不一定),不能是final类,方法也不能是final
    2. 原理是生成目标类的子类,而子类是增强过得,这个子类对象就是代理对象。
    3. 对类的要求低,性能比较高
    4. 不是最终类不可代理,其他的都可以代理。
  • 使用spring框架动态代理时,有接口,默认使用jdk,没有接口,自动转到cglib

  • 如果有接口,想用cglib实现动态代理,可以在主配置文件中加入

    • <aop**:aspectj-autoproxy** proxy-target-class**=“true”**/>

举例

假如现在又增删改查四个方法,我想要获取这四个方法的执行时间。

  1. 在每个方法前输入一个new Date()对象实现
  2. 写一个工具类,在这些方法的前面调用

问题:

如果采用上面这两种方式,我们都对方法内部进行了添加修改,这些添加的代码与源代码执行的结果是无关的,属于赋予它的功能。直接添加到原方法里面,会降低可读性,同时也降低了性能。假如有1000个方法,我们不可能为这1000个方法都添加这样的代码。

我们想要的效果是:原方法不改动,但是调用这些方法时,我们也要得到一些方法之外的信息。即增强方法。我们可以使用动态代理来实现。

jdk动态代理

使用步骤

  1. 创建目标类,Student目标类。目的:为他的drink,eat增加输出时间,事务。
  2. 创建一个InvocationHandler接口的实现类,在这个类实现给目标方法增强功能。
  3. 使用jdk中类 Proxy,创建代理对象。实现创建对象的能力。

目标类

public class Student implements Person{
    @Override
    public void drink(String food) {
    }

    @Override
    public void eat(String food) {
    }
}
public interface Person {
    void drink(String food);
    void eat(String food);
}

工具类

public class MyLogUtil {
    public static void operateTime() {
        System.out.println("代理运行:操作时间" + new Date());
    }

    public static void operateContent() {
        System.out.println("代理完毕:调用了方法");
    }
}

InvocationHandler接口的实现类

  1. 创建一个类实现InvocationHandler接口。该类为代理对象的代理功能的实现类
  2. 创建一个对象,用来存储目标类
  3. 写加强目标类的内容,增强方法或者增强参数
  4. 执行目标类的方法
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

  method:目标类当前调用的方法

   args:目标类当前调用的方法的参数

}

res = method.invoke(target, args);暂时理解为调用目标类的方法   args为目标类的参数

代理实现

public class MyIncationHandler implements InvocationHandler {
    //定义一个对象,保存目标(要代理谁)Student
    private Object target;

    public MyIncationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object res = null;
        //在目标方法之前输出时间(工具类)
        MyLogUtil.operateTime();
        //执行目标类的方法,通过Method类实现
        System.out.println("当前方法名:" + method.getName());//获取被代理对象要调用的方法的方法名称
        //获取目标类的参数
        System.out.println("当前参数:" + args[0]);
        //被代理对象的方法的执行  target表示被代理的对象。args表示方法的参数
        res = method.invoke(target, args);
        //在目标方法之后输出执行完毕(工具类)
        MyLogUtil.operateContent();
        return res;
    }
}

创建代理对象

  1. 创建目标类,以及增强目标类功能的类的对象。将目标类作为参数传入进去
  2. 创建代理对象,将目标类,目标类的接口,以及增强目标类功能的类的对象传入
  3. 通过代理对象,调用目标类的相关方法,实现方法增强。
public class JdkProxyTest {
    public static void main(String[] args) {
        //使用jdk的Proxy创建代理对象
        //创建目标对象
        Person student = new Student();
        //创建InvocationHandler对象
        InvocationHandler handler = new MyIncationHandler(student);
        //使用Proxy创建代理
        Person proxy = (Person) Proxy.newProxyInstance(
                student.getClass().getClassLoader(),
                student.getClass().getInterfaces(), handler);
        //调用代理的方法。
        proxy.drink("牛奶");
        proxy.eat("面包");
    }
}

案例:买电脑

客户去原厂买电脑,需要花费8000元,通过代理购买,也需要花费8000元,但是代理商只用给原厂掏4800元,净赚3200.但是用户通过代理商够吗,代理商可以提供相关的服务,比如车接车送,赠送物品等。

image-20211208001026557

interface SaleComputer {
     String saleComputer(double money);

     void show();
}

class Lenovo implements SaleComputer {
    //真实类
    @Override
    public String saleComputer(double money) {
        System.out.println("我买了一台" + money + "的电脑");
        return "购买成功";
    }

    @Override
    public void show() {
        System.out.println("展示电脑");
    }
}

//代理类
public class Proxyer {
    public static void main(String[] args) {
        //1.创建真实对象
        Lenovo lenovo = new Lenovo();
        //2.动态代理增强lenovo对象
        //三个参数
        //类加载器:真实对象.getClass().getClassLoader()
        //接口数组:真实对象.getClass().getInterfaces()
        //处理器: new InvocationHandler()
//此处实现的是接口方法,所以可以强制转换成接口类型
        SaleComputer saleComputer = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {

            /**
             * 代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行。
             * 参数:
             * 1.proxy:代理对象(基本不用)
             * 2.method:代理对象调用的方法被封装成的对象
             * 3. args:代理对象调用方法时,传递的实际参数。
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //使用真实对象调用该方法
                //1.判断方法名是够是saleComputer方法
                if (method.getName().equals("saleComputer")) {
                    //获取参数
                    double money = (double) args[0];
                    //修改参数
                    money = money * 0.6;
                    //修改方法体
                    System.out.println("接你.....");
                    //使用真实对象调用该方法
                    //原因:代理商并不具备真正卖电脑的功能,真正有卖电脑功能的是lenove公司
                    //参数:真实对象和代理对象传入的参数
                    String obj = (String) method.invoke(lenovo, money);
                    System.out.println(obj);
                    System.out.println("免费送货");
                    //2.增强返回值
                    return obj + "_鼠标垫";
                } else {
                    Object obj = method.invoke(lenovo, args);
                    return obj;
                }

            }
        });
        System.out.println("电脑原价");
        lenovo.saleComputer(8000);   //返回8000
        //代理商购买价
        System.out.println("代理商购买价");
        saleComputer.saleComputer(8000); // 返回4800
        // 相当于客户去原厂买电脑,需要花费8000元,通过代理购买,也需要花费8000元,但是代理商只用给原厂掏4800元,净赚3200.但是代理商可以提供相关的服务,比如车接车送,赠送物品等。
    }
}

送你一个鼠标

送你一个鼠标垫

送你一个键盘

联想

代理商

给你一个电脑

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

See you !

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值