1. 反射(理解)

在理解的基础上,能掌握最好;对之后框架原理的学习有帮助。

如果仅仅是使用框架,可以不需要理解反射。

提问:

  1. 怎么创建一个学生对象?

    答:new Student()

  2. 创建一个老师对象?

    答:new Teacher()

  3. 不修改代码,创建出不同的对象?

    肯定不能像上面那样写,上面的写法只要写出来就明确了创建对象的类型,不是通用的效果

1.0 导学

需求:

  • 不要修改代码,就可以创建不同的对象,甚至调用该对象的方法

使用反射就可以实现。

  • 运行day14_01_reflect_demo中ReflectDemo中main方法,查看结果
  • 修改配置文件,重新运行查看结果。

1.1 概述

  • 概念:类加载之后,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等)。
  • 白话概念:在程序运行期间,对于任意一个类,我们可以使用反射技术获取其任意变量、调用其任意方法;
  • 实现的效果:在不修改源代码的前提下,创建的对象、调用的方法、使用的成员变量可以随着配置文件的变化而变化

一句话:使用反射可以实现通用性代码

作用:

  1. 反射可以无视访问权限修饰符而使用变量、调用方法;
  2. 不修改代码的前提下实现通用逻辑:动态加载(文件、传参等),加载之后调用;加载的内容不同,调用也就不同了而变的很通用。

思维逻辑转变:

image-20210406163522517

需要注意的点:

  1. 思想的转变(直接new一步到位,转变到反射罗里吧嗦)
  2. 比较多的类和API

1.2 反射体系中相关的类

  • 一个类的基本组成

    java源文件/class文件)

    构造方法

    成员变量

    成员方法

    public class Student{
        // 构造方法
    
        // 成员变量
    
        // 成员方法
    }

    Java面向对象思想:万物皆对象

  • 对应的反射的类的体系中

    class文件 ,封装成为Class对象

    构造方法,封装构造方法对象

    成员变量,封装成员变量 对象

    成员方法,封装成员方法对象

image-20210406155442996

  • 注意

    1. classClass不同

      1. class是字节码文件的后缀;也是java中的一个关键字,用于声明一个类
      2. Class表示是是一个具体的类,可以创建该类的对象;Class对象里面包含了对某个类的所有描述(成员)
    2. 为什么我们要首先获取这个Class对象?

      • 想使用反射调用类的任意成员和方法,想让编写的代码更通用(反射的两个作用),肯定不能写死,eg: new Student() 或者 stu.study();而是要换种方式创建对象和调用方法等。
      • 创建对象要用构造方法,调用方法要知道方法的一些参数class文件中包含了这些所有信息,class文件加载到内存之后变成了Class对象,如果我们能获取到该对象,自然也能创建类的对象,调用其方法等等

1.3 获取Class对象

Class对象:class文件中加载到内存中成为一个Class对象

  • 获取方式相关方法

| 获取方式 | 说明 | 所属类 | 使用频率 |
| ----------------------------- | --------------------------------------- | ---------------- | -------------------------------------------- |
| 类名.class | 通过类名获取其字节码对象 | 任何类均可获取 | 类名硬编码不推荐 |
| Class.forName("全类名") | 从类路径下加载指定的类,返回Class对象 | Class | 全类名可以从配置文件加载,通用,推荐使用。 |
| 类的对象.getClass() | 获取对象所属类的字节码对象 | Object | 操作繁琐对象硬编码不推荐 |

  • 演示代码

    package com.itheima.reflect;
    
    /**
     * 反射第一步:获取Class对象
     *
     * @Author Vsunks.v
     * @Blog blog.sunxiaowei.net/996.mba
     * @Description: 反射第一步:获取Class对象
     */
    public class ClassDemo01 {
        public static void main(String[] args) throws ClassNotFoundException {
    
            // 方式1: 类名.class
            Class<Student> studentClassObj01 = Student.class;
            System.out.println("studentClassObj01 = " + studentClassObj01);
    
            // 方式2:Class.forName(String fullNameWithPackage)
            Class<?> studentClassObj02 = Class.forName("com.itheima.reflect.Student");
            System.out.println("studentClassObj02 = " + studentClassObj02);
    
            // 方式3:new Object().getClass()
            Class<? extends Student> studentClassObj03 = new Student().getClass();
            System.out.println("studentClassObj03 = " + studentClassObj03);
    
    
            // 比较多种方式多次获取到的class对象:同一个对象
            // 某个类的Class对象,在内存中有且只有一个。静态方法锁对象,默认是所属类的class对象
            System.out.println("studentClassObj03==studentClassObj01 = " + (studentClassObj03 == studentClassObj01));     // true
            System.out.println("studentClassObj02==studentClassObj01 = " + (studentClassObj02 == studentClassObj01));    // true
    
        }
    }

    被反射的学生类

    package com.itheima.reflect;
    
    /**
     * @Author Vsunks.v
     * @Blog blog.sunxiaowei.net
     * @Description:
     */
    public class Student {
        private String name = "反射niubility";
        public int age /*= 20*/;
    
        public Student() {
        }
    
        public Student(String name) {
            this.name = name;
            this.age = age;
        }
        private Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        private String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        private void setAge(int age) {
            System.out.println("setAge invoking...");
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
注意:
  • 特点:

    某个类有且只有一个Class对象。

  • 关联知识点

    同步静态方法的锁对象默认是所属类的字节码对象,使用这个对象绝对可以保证锁唯一。

  • 工作中怎么用?

    最常用的是通过Class.forName()方式获取。

接下来的所有对象,都要使用Class对象获取,也就是在使用Class对象获取类中其他成员对象。

1.4 获取Constructor对象

Constructor对象是对类中构造方法封装之后得到的对象

  • Class类中用于获取构造器对象的方法

| 方法签名 | 方法说明 |
| -------------------------------------------------------------- | ---------------------------------- |
| Constructor[] getConstructors() | 获取所有公共构造器对象的数组 |
| Constructor[] getDeclaredConstructors() | 获取所有构造器对象的数组 |
| Constructor getConstructor(Class... parameterTypes) | 获取指定参数的单个公共构造器对象 |
| Constructor getDeclaredConstructor(Class... parameterTypes) | 获取指定参数的单个构造器对象 |

// Constructor[]  getConstructors() 返回所有公共构造器对象的数组
// Constructor[]  getDeclaredConstructors()返回所有构造器对象的数组
// Constructor  getConstructor(Class... parameterTypes)          返回单个公共构造器对象
// Constructor  getDeclaredConstructor(Class... parameterTypes)  返回单个构造器对象
  • 获取步骤

    1. 获取学生类的Class对象
    2. 使用获取到的学生类Class对象获取Constructor对象

      2.1 获取所有公共构造器对象

      2.2 获取所有构造器对象

      2.3 获取指定的某一个公共的构造器对象

      2.4 获取指定的某个私有的构造器对象

  • 演示代码

    package com.itheima.reflect;
    
    import java.lang.reflect.Constructor;
    
    /**
     * 通过Class对象获取Constructor构造器对象
     *
     * @Author Vsunks.v
     * @Blog blog.sunxiaowei.net/996.mba
     * @Description: 通过Class对象获取Constructor构造器对象
     */
    public class ReflectDemo01 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
            // Constructor[]  getConstructors() 返回所有公共构造器对象的数组
            // Constructor[]  getDeclaredConstructors()返回所有构造器对象的数组
            // Constructor  getConstructor(Class... parameterTypes)          返回单个公共构造器对象
            // Constructor  getDeclaredConstructor(Class... parameterTypes)  返回单个构造器对象
    
            // 1. 获取学生类的Class对象
            Class stuClassObj = Class.forName("com.itheima.reflect.Student");
    
            // 2. 使用获取到的学生类Class对象获取Constructor对象
            // 2.1 获取所有公共构造器对象
            // Constructor[]  getConstructors() 返回所有公共构造器对象的数组
            Constructor[] stuConstructorObjs = stuClassObj.getConstructors();
    
            for (Constructor stuConstructorObj : stuConstructorObjs) {
                System.out.println("stuConstructorObj = " + stuConstructorObj);
            }
    
            // 2.2 获取所有构造器对象
            // Constructor[]  getDeclaredConstructors()返回所有构造器对象的数组
            Constructor[] stuConstructorObjs2 = stuClassObj.getDeclaredConstructors();
            for (Constructor stuCon : stuConstructorObjs2) {
                System.out.println("stuCon = " + stuCon);
            }
    
            // 2.3 获取指定的某一个公共的构造器对象
            // Constructor  getConstructor(Class... parameterTypes)          返回单个公共构造器对象
            Constructor constructorObj1 = stuClassObj.getConstructor(String.class);
            System.out.println("constructorObj1 = " + constructorObj1);
    
            // 通过getConstructor方法只能获取到公共的构造方法,无法获取私有构造方法,报错如下:
            // NoSuchMethodException: com.itheima.reflect.Student.<init>(java.lang.String, int)
            // Constructor constructorObj2 = stuClassObj.getConstructor(String.class, int.class);
            // System.out.println("constructorObj2 = " + constructorObj2);
    
    
            // 2.4 获取指定的某个私有的构造器对象
            Constructor constructorObj3 = stuClassObj.getDeclaredConstructor(String.class, int.class);
            System.out.println("constructorObj3 = " + constructorObj3);
    
            Constructor constructorObj4 = stuClassObj.getDeclaredConstructor(String.class);
            System.out.println("constructorObj4 = " + constructorObj4);
        }
    }

1.5 使用Constructor对象

获取Constructor对象的目的:通过构造器对象,创建该构造器所属类(eg:Student)的对象 (eg:stuObj)

  • 相关方法

| 方法声明 | 说明 | 所属类 | |
| ---------------------------------- | ----------------------------------------------- | ------------- | -- |
| Object newInstance(Object… arg) | 通过Constructor对象创建所属类的对象,传递实参 | Constructor | |
| Object newInstance() | 通过Class对象调用无参构造创建所属类对象 | Class | |

使用步骤

  1. 获取当前类(Student)的Class对象
  2. 通过当前类(Student)的Class对象获取当前类(Student)的构造方法对象
  3. 使用当前类(Student)的Constructor对象的newInstance方法创建当前类(Student)的对象

    • 如果获取的Constructor对象是私有的,需要暴力反射获取访问权限后再使用
  • 演示代码

    package com.itheima.reflect;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    
    /**
     * 使用获取到的Constructor构造器对象
     *
     * @Author Vsunks.v
     * @Blog blog.sunxiaowei.net/996.mba
     * @Description: 使用获取到的Constructor构造器对象
     */
    public class ReflectDemo02 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    
            // 反射获取公共无参构造,并通过其创建所属类(Student)的对象
            // noArgsPublicConstructor();
    
            // 反射获取公共一个参数构造,并通过其创建所属类(Student)的对象
            // oneArgPublicConstructor();
    
            // 反射获取私有两个参数构造,并通过其创建所属类(Student)的对象
            // twoArgsPrivateConstructor();
    
            // 直接使用Class提供的方法通过无参构造创建所属类对象
            // 1. 获取当前类(Student)的Class对象
            Class stuClassObj = Class.forName("com.itheima.reflect.Student");
    
            // 2. Class对象提供了便捷创建所属类对象的方法:newInstance()
            // 反射调用最常用的无参构造方法创建对象
            Object stuObj = stuClassObj.newInstance();
            System.out.println("stuObj = " + stuObj);
    
    
        }
    
        private static void twoArgsPrivateConstructor() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
            // 1. 获取当前类(Student)的Class对象
            Class stuClassObj = Class.forName("com.itheima.reflect.Student");
    
            // 2. 通过当前类(Student)的Class对象获取当前类(Student)的构造方法对象 (传递形参类型)
            Constructor twoArgConstructorObj = stuClassObj.getDeclaredConstructor(String.class,int.class);
    
    
            // 对于私有的成员,想要访问,需要先暴力反射。否则报错:非法访问异常IllegalAccessException
            // IllegalAccessException: class com.itheima.reflect.ReflectDemo02 cannot access a member of class com.itheima.reflect.Student with modifiers "private"
            //     at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
            twoArgConstructorObj.setAccessible(true);
    
            // 3. 使用当前类(Student)的Constructor对象的newInstance方法创建当前类(Student)的对象(传递实参)
            // 这里就相当于 new Student("方方老师",17)
            Object stuObj = twoArgConstructorObj.newInstance("方方老师",17);
            System.out.println("stuObj = " + stuObj);
        }
    
        private static void oneArgPublicConstructor() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
            // 1. 获取当前类(Student)的Class对象
            Class stuClassObj = Class.forName("com.itheima.reflect.Student");
    
            // 2. 通过当前类(Student)的Class对象获取当前类(Student)的构造方法对象 (传递形参类型)
            Constructor oneArgConstructorObj = stuClassObj.getConstructor(String.class);
    
            // 3. 使用当前类(Student)的Constructor对象的newInstance方法创建当前类(Student)的对象(传递实参)
            // 这里就相当于 new Student("方方老师")
            Object stuObj = oneArgConstructorObj.newInstance("方方老师");
            System.out.println("stuObj = " + stuObj);
        }
    
        private static void noArgsPublicConstructor() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
            // 1. 获取当前类(Student)的Class对象
            Class stuClassObj = Class.forName("com.itheima.reflect.Student");
    
            // 2. 通过当前类(Student)的Class对象获取当前类(Student)的构造方法对象
            Constructor noArgsConObj = stuClassObj.getConstructor();
    
            // 3. 使用当前类(Student)的Constructor对象的newInstance方法创建当前类(Student)的对象
            // 这里就相当于 new Student()
            Object stuObj = noArgsConObj.newInstance();
            System.out.println("stuObj = " + stuObj);
        }
    }
  • 总结图示

    image-20210406163522517

  • 如果能看懂是上面的图示,就能举一反三的轻松学会获取并使用Field对象、Method对象的内容。

    不同之处:无非是反射步骤2、3调用的方法稍有不一样。

1.6 获取Field对象

Field对象是某个类中某个成员变量封装成的对象。

  • 相关方法

| 方法签名 | 方法说明 |
| ------------------------------------- | ---------------------------------- |
| Field[] getFields() | 获取所有公共成员变量对象的数组 |
| Field[] getDeclaredFields() | 获取所有成员变量对象的数组 |
| Field getField(String name) | 获取指定名称的公共的成员变量对象 |
| Field getDeclaredField(String name) | 获取指定名称的成员变量对象 |

  • 方法注释

    //  Field[] getFields()            获取所有公共成员变量对象的数组   
    //  Field[] getDeclaredFields()    获取所有成员变量对象的数组     
    //  Field getField(String name)    获取指定名称的公共的成员变量对象 
    //  Field getDeclaredField(String name)  获取指定名称的成员变量对象     
  • 获取步骤

    1. 获取当前类(Student)的Class对象
    2. 通过当前类(Student)的Class对象获取当前类(Student)的属性Filed对象

      2.1 获取所有公共成员变量对象

      2.2 获取所有成员变量对象

      2.3 获取指定的某一个公共的成员变量对象

      2.4 获取指定的某个私有的成员变量对象

  • 演示代码

    package com.itheima.reflect;
    
    import java.lang.reflect.Field;
    
    /**
     * 通过Class对象获取Field成员变量对象
     *
     * @Author Vsunks.v
     * @Date 2023/5/22 12:05
     * @Blog blog.sunxiaowei.net/996.mba
     * @Description: 通过Class对象获取Field成员变量对象
     */
    public class ReflectDemo03 {
        public static void main(String[] args) throws Exception {
            //  Field[] getFields()            获取所有公共成员变量对象的数组
            //  Field[] getDeclaredFields()    获取所有成员变量对象的数组
            //  Field getField(String name)    获取指定名称的公共的成员变量对象
            //  Field getDeclaredField(String name)  获取指定名称的成员变量对象
    
            // 1. 获取当前类(Student)的Class对象
            Class stuClassObj = Class.forName("com.itheima.reflect.Student");
    
            // 2. 通过当前类(Student)的Class对象获取当前类(Student)的属性Filed对象
            // 2.1 获取所有公共的成员变量对象
            //  Field[] getFields()            获取所有公共成员变量对象的数组
            Field[] fieldObjs = stuClassObj.getFields();
            for (Field fieldObj : fieldObjs) {
                System.out.println("fieldObj = " + fieldObj);
            }
    
            // 2.2 获取所有成员变量对象
            //  Field[] getDeclaredFields()    获取所有成员变量对象的数组
            Field[] fieldObjs2 = stuClassObj.getDeclaredFields();
            for (Field fieldObj : fieldObjs2) {
                System.out.println("fieldObj >>> " + fieldObj);
            }
    
            // 2.3 获取指定的公共成员变量对象
            //  Field getField(String name)    获取指定名称的公共的成员变量对象
            Field ageFieldObj = stuClassObj.getField("age");
            System.out.println("ageFieldObj = " + ageFieldObj);
    
    
            // 2.4 获取指定的任意一个(公共或私有等)成员变量对象
            //  Field getDeclaredField(String name)  获取指定名称的成员变量对象
            Field nameFieldObj = stuClassObj.getDeclaredField("name");
            System.out.println("nameFieldObj = " + nameFieldObj);
        }
    }

1.7 使用Field对象

获取Field对象的目的:使用该属性(获取值/设置值,eg:stu.age = 23)

Field类分别提供了两个方法,用于实现上述两个功能:

方法签名方法说明
Object get(Object instance)获取指定对象instance的某个属性的值
void set(Object instance, Object value)为指定对象instance的某个属性设置值为value
  • 使用步骤

    1. 获取当前类(Student)的Class对象
    2. 通过当前类(Student)的Class对象获取当前类(Student)的属性Field对象
    3. 利用当前类(Student)的属性Field对象的get/set方法获取属性值或者为属性赋值

      • 如果获取的属性对象是私有的,需要暴力反射获取访问权限后再使用

      3.1 准备一个当前类(Student)对象

      3.2 为成员变量赋值

      3.3 获取成员变量的值

  • 方法注释

    // Object get(Object instance)    获取指定对象instance的某个属性的值
    // void set(Object instance, Object value)    为指定对象instance的某个属性设置值为value
  • 演示代码

    package com.itheima.reflect;
    
    import java.lang.reflect.Field;
    
    /**
     * 使用获取到的Field对象为成员变量赋值或取值
     *
     * @Author Vsunks.v
     * @Blog blog.sunxiaowei.net/996.mba
     * @Description: 使用获取到的Field对象为成员变量赋值或取值
     */
    public class ReflectDemo04 {
        public static void main(String[] args) throws Exception{
    
            // 获取公共的成员变量age并使用
            ageFieldPublic();
    
            // 获取私有的成员变量name并使用
            // nameFieldPrivate();
    
        }
    
        private static void nameFieldPrivate() throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException {
            // 1. 获取当前类(Student)的Class对象
            Class stuClassObj = Class.forName("com.itheima.reflect.Student");
    
            // 2. 通过当前类(Student)的Class对象获取当前类(Student)的属性Field对象
            Field nameFieldObj = stuClassObj.getDeclaredField("name");
    
    
            // 3. 利用当前类(Student)的属性Field对象的get/set方法获取属性值或者为属性赋值
            // 3.1 准备一个学生对象
            Object stuObj = stuClassObj.newInstance();
    
            // 暴力反射,否则报错:非法访问异常 IllegalAccessException
            // .IllegalAccessException: class com.itheima.reflect.ReflectDemo04 cannot access a member of class com.itheima.reflect.Student with modifiers "private"
            nameFieldObj.setAccessible(true);
    
            // 3.2 为成员变量赋值
            // void set(Object 所属类的对象, Object 要赋的值)
            nameFieldObj.set(stuObj,"方方");
    
            // 3.3 获取成员变量的值
            // Object get(Object 所属类的对象)
            Object nameValue = nameFieldObj.get(stuObj);
            System.out.println("nameValue = " + nameValue);
    
    
            System.out.println("stuObj = " + stuObj);
        }
    
        private static void ageFieldPublic() throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException {
            // 1. 获取当前类(Student)的Class对象
            Class stuClassObj = Class.forName("com.itheima.reflect.Student");
    
            // 2. 通过当前类(Student)的Class对象获取当前类(Student)的属性Field对象
            Field ageFieldObj = stuClassObj.getField("age");
    
    
            // 3. 利用当前类(Student)的属性Field对象的get/set方法获取属性值或者为属性赋值
            // 3.1 准备一个学生对象
            Object stuObj = stuClassObj.newInstance();
    
            // 3.2 为成员变量赋值
            // void set(Object 所属类的对象, Object 要赋的值)
            ageFieldObj.set(stuObj, 17);
    
            // 3.3 获取成员变量的值
            // Object get(Object 所属类的对象)
            Object ageValue = ageFieldObj.get(stuObj);
            System.out.println("ageValue = " + ageValue);
    
    
            System.out.println("stuObj = " + stuObj);
        }
    }
注意:
  • 私有的成员变量需要通过暴力反射后才能调用(设置值或获取值)
  • 总结图示

    image-20210406184736971

1.8 获取Method对象

Method对象是把某个类的某个方法封装成的对象

  • 相关方法

| 方法签名 | 方法说明 |
| ------------------------------------------------------------ | -------------------------------------------------------- |
| Method[] getMethods() | 获取所有公共的方法对象数组(包含从父类继承的方法) |
| Method[] getDeclaredMethods() | 获取所有的方法对象数组(含私有,不含从父类继承的方法) |
| Method getMethod(String methodName, Class... args) | 获取单个指定名称和参数的公共的方法对象 |
| Method getDeclaredMethod(String methodName, Class... args) | 获取单个指定名称和参数的方法对象 |

  • 使用步骤

    1. 获取当前类(Student)的Class对象
    2. 通过当前类(Student)的Class对象获取当前类(Student)的某个方法对象
  • 方法注释

    //  Method[] getMethods()   获取所有公共的方法对象数组(包含从父类继承的方法)
    //  Method[] getDeclaredMethods() 获取所有的方法对象数组(含私有,不含从父类继承的方法)
    //  Method getMethod(String methodName, Class... args)  获取单个指定名称和参数的公共的方法对象
    //  Method getDeclaredMethod(String methodName, Class... args)  获取单个指定名称和参数的方法对象
  • 演示代码

    package com.itheima.reflect;
    
    import java.lang.reflect.Method;
    
    /**
     * 通过Class对象获取Method成员方法对象
     *
     * @Author Vsunks.v
     * @Blog blog.sunxiaowei.net/996.mba
     * @Description: 通过Class对象获取Method成员方法对象
     */
    public class ReflectDemo05 {
        public static void main(String[] args) throws Exception {
            //  Method[] getMethods()   获取所有公共的方法对象数组(包含从父类继承的方法)
            //  Method[] getDeclaredMethods() 获取所有的方法对象数组(含私有,不含从父类继承的方法)
            //  Method getMethod(String methodName, Class... args)  获取单个指定名称和参数的公共的方法对象
            //  Method getDeclaredMethod(String methodName, Class... args)  获取单个指定名称和参数的方法对象
    
    
            // 1. 获取当前类(Student)的Class对象
            Class stuClassObj = Class.forName("com.itheima.reflect.Student");
    
            // 2. 通过当前类(Student)的Class对象获取当前类(Student)的某个方法对象
            // 2.1 获取所有公共的成员方法
            //  Method[] getMethods()   获取所有公共的方法对象数组(包含从父类继承的方法)
            Method[] allMethodObjs = stuClassObj.getMethods();
            for (Method methodObj : allMethodObjs) {
                System.out.println("methodObj = " + methodObj);
            }
    
            // 2.2 获取所有的成员方法
            // Method[] getDeclaredMethods() 获取所有的方法对象数组(含私有,不含从父类继承的方法)
            Method[] allDeclaredMethodObjs = stuClassObj.getDeclaredMethods();
            for (Method methodObj : allDeclaredMethodObjs) {
                System.out.println("methodObj >>> " + methodObj);
            }
    
            // 2.3 获取指定的某个公共的成员方法
            //  Method getMethod(String methodName, Class... args)  获取单个指定名称和参数的公共的方法对象
            Method setNameMethodObj = stuClassObj.getMethod("setName", String.class);
            System.out.println("setNameMethodObj = " + setNameMethodObj);
    
            // 2.4 获取指定的某个私有的成员方法
            //  Method getDeclaredMethod(String methodName, Class... args)  获取单个指定名称和参数的方法对象
            Method getNameMethodObj = stuClassObj.getDeclaredMethod("getName");
            System.out.println("getNameMethodObj = " + getNameMethodObj);
    
        }
    }
    

1.9 使用Method对象

获取Method对象的目的:调用其封装的方法(eg:stu.study())

  • Method类中提供的API:

    Object invoke(Object instance, Object... args)

    • 属于Method
    • 作用:调用某个对象的某个方法
    • 返回值:目标方法返回值。因为不确定该方法返回值类型,所以用Object。
    • 参数1:调用者对象。因为不确定该方法属于哪个对象,所以用Object。
    • 参数2:调用时传递的参数实参列表。不确定参数类型和个数,所以使用Object可变参数。
  • 使用步骤

    1. 获取当前类(Student)的Class对象
    2. 通过当前类(Student)的Class对象获取当前类(Student)的某个方法Method对象
    3. 通过当前类(Student)的方法Method对象的invoke方法获调用方法

      • 需要出传递的两个参数

        1. 目标类的对象
        2. 目标类对象的成员方法的实参
      • invoke方法的返回值,就是目标方法的返回;目标方法无返回值,则invoke返回null
      • 如果获取的方法对象是私有的,需要暴力反射获取访问权限后再使用
  • 演示代码

    package com.itheima.reflect;
    
    import java.lang.reflect.Method;
    
    /**
     * 使用获取到的Method对象反射调用成员方法
     *
     * @Author Vsunks.v
     * @Date 2023/5/22 15:36
     * @Blog blog.sunxiaowei.net/996.mba
     * @Description: 使用获取到的Method对象反射调用成员方法
     */
    public class ReflectDemo06 {
        public static void main(String[] args) throws Exception {
    
            /*
                Object invoke(Object instance, Object... args)
                    - 属于Method类
                    - 作用:调用某个对象的某个方法
                    - 返回值:目标方法返回值。因为不确定该方法返回值类型,所以用Object。
                    - 参数1:调用者对象。因为不确定该方法属于哪个对象,所以用Object。
                    - 参数2:调用时传递的参数实参列表。不确定参数类型和个数,所以使用Object可变参数。
    
              */
    
            // 1. 获取当前类(Student)的Class对象
            Class stuClassObj = Class.forName("com.itheima.reflect.Student");
    
            // 2. 通过当前类(Student)的Class对象获取当前类(Student)的某个方法Method对象
            Method setAgeMethodObj = stuClassObj.getDeclaredMethod("setAge", int.class);
    
    
            // 私有成员,使用前需要暴力反射,否则报错非法访问异常:IllegalAccessException
            // IllegalAccessException: class com.itheima.reflect.ReflectDemo06 cannot access a member of class com.itheima.reflect.Student with modifiers "private"
            setAgeMethodObj.setAccessible(true);
    
    
            // 3. 通过当前类(Student)的方法Method对象的invoke方法获调用方法
            // 3.1 准备一个当前类(Student)的对象
            Object stuObj = stuClassObj.newInstance();
    
            // 3.2 反射调用成员方法
            // public Object invoke(Object 目标方法所属类对象, Object... 实参列表) 反射调用成员方法
            Object returnValue = setAgeMethodObj.invoke(stuObj, 17);
            System.out.println("returnValue = " + returnValue);
            System.out.println("stuObj = " + stuObj);
    
        }
    }
    
  • 总结图示

    image-20210406184720839

==2. 注解==

注解可以理解为另外一种形式的配置(其他形式的配置:配置文件:xml、properties)

注解的解析,也需要用到反射

2.0 导学

使用模块day14_02_anno演示注解的效果

  • 定义了一个Test注解(不要关注怎么注解怎么自定义)
  • 把注解标注在了一些方法上(不要关注怎么标注,为什么这样标注,怎么给注解属性赋值等等0.0)
  • 运行TestDemo中方法,查看运行结果

    重点关注哪些方法执行了,执行了几遍;这些执行效果和Test注解之间存在什么样的关联。

2.1 注解 & 注释 & 配置

注解和注释:

  • 注释:对程序解释说明的文字,给程序员看的
  • 注解:可以作为配置的一种,可作为一种标志、校验等等。给编译器/程序看的。

配置方式:

  • 配置文件:文件可以作为配置使用,eg:properties、yaml、xml等。配置繁琐。
  • 注解:同样可以配置,更简单清爽,框架基本上都是可以通过注解实现配置。

==2.2 概述==

注解概念:可以理解成一个标志/标签,我们可以根据是否有该标志通过反射做区别处理

定义格式:

  • public @interface 注解类名{}

    使用格式:(在类、方法、变量等位置)

  • @注解类名
注意:
  • 配置本身不会产生效果(配置文件、注解一样)
  • 配置只有被读取并识别了之后,才能通过某些技术添加一些特殊的效果(反射)

作为配置,注解比xml文件简单很多;

这两种配置方式,我们在之后的框架学习过程中都会用到,并且最终会选择更简单的注解。

==2.4 已经学习过的注解==

  • 相关注解

| 注解 | 标注位置(@Target) | 作用 |
| ---------------------- | ---------------------------------------------------------------------- | ---------------------- |
| @Override | 方法上METHOD | 校验是否是重写 |
| @FunctionalInterface | 类上TYPE | 校验是否是函数式接口 |
| @Deprecated | CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE | 声明已过时 |
| @SuppressWarnings | TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE | 压制警告 |

  • 演示代码

    package com.itheima;
    
    
    /**
     * 已经学习过的注解展示
     *
     * @Author Vsunks.v
     * @Blog blog.sunxiaowei.net/996.mba
     * @Description: 已经学习过的注解展示
     */
    @SuppressWarnings("all")
    public class AnnoDemo01 {
    
        /*
            @Override 就是最常用的一个注解
                告诉编译器,被标注的方法是在重写父类的方法,并检查是否符合重写语法的要求
            任何注解,本身只能作为一个标签;标注某个注解后,并不会产生什么神奇的效果
                只有配合反射,才能实现某些效果
         */
        @Override
        public String toString() {
            return "";
        }
    
    
        public static void testLambda(Animal a) {
            a.eat();
        }
    
    
        /**
         * 被标注的方法等内容,已经过时,不推荐使用
         */
        @Deprecated
        public void show(){
            System.out.println("我正在定义一个已经过时的show方法");
        }
    
    
        /**
         * 把标注元素上的警告压制
         */
        @SuppressWarnings("all")
        public void test(){
            System.out.println("这是一个从没有被调用过的test方法");
        }
    
    
        public static void main(String[] args) {
            // lambda能简化特性条件的匿名内部类对象的属性:其中的抽象方法有且只有一个的接口
            testLambda(() -> System.out.println("xxx"));
            new AnnoDemo01().show();
        }
    
    }
    
    /**
     * @FunctionalInterface 该注解标注在某个接口上,用于检查被标注的接口是否是函数式接口
     * <p>
     * 函数式接口:接口中的抽象方法有且只有一个,这种接口被称为函数是接口
     */
    @FunctionalInterface
    @SuppressWarnings("all")
    interface Animal {
        void eat();
    
        default void eat2() {
            System.out.println();
        }
    }

2.5 自定义注解(理解)

2.5.1 定义

定义

  • 通过@interfaece定义一个注解
  • 注解中只有属性,没有方法
  • 属性的数据类型可以是:基本类型、StringClass、注解、枚举、及上述类型的一维数组
  • 属性访问权限默认只能是public,可以省略不写
  • 属性需要有小括号,类似于类中的成员方法
  • 属性可以通过default指定默认值
  • 演示代码

    package com.itheima.demo2;
    
    import com.itheima.anno.Test;
    
    /**
     * 自定义注解演示
     *
     * @Author Vsunks.v
     * @Blog blog.sunxiaowei.net
     * @Description:
     */
    // 通过@interfaece定义一个注解
    public @interface Test02 {
    
        // 注解中只有属性,没有方法
        // 属性的数据类型可以是:基本类型、String、Class、注解、枚举、及上述类型的一维数组
        // 属性需要有小括号,类似于类中的成员方法
        public int num();
    
        // 属性访问权限默认只能是public,可以省略不写
        int num2();
    
        // 属性可以通过default指定默认值
        String name() default "zs";
    
        // 定义一个Class类型的属性
        Class clazz() default String.class;
    
    
        // 定义一个注解类型的属性
        // @Test 表示是一个@Test注解的对象
        // @Test注解的类型就是去掉@:     Test
        Test test() default @Test;
    
        // 定义一个枚举类型的属性
        Season season() default Season.AUTUMN;
    
        // 定义一个基本类型一维数组的属性
        public int[] nums() default {0,1,2};
    
        // 定义一个String类型一维数组的属性
        String[] names() default {"张三","李四"};
    
    
        // 定义一个Class类型一维数组的属性
        Class[] clazzes() default String.class;
    
    
        // 定义一个注解类型一维数组的属性@Test
        Test[] tests() default @Test;
    
        // 定义一个枚举类型一维数组的属性
        Season[] seasons() default Season.SPRING;
    
    }
    
    enum Season {
        SPRING, SUMMER, AUTUMN, WINTER;
    }

2.5.2 使用

把注解标注在类、方法、变量、构造方法、方法形参等位置,就是在使用。

  • 在使用注解时,注解的所有属性必须要有值(默认或者使用时手动赋值)
  • 使用时为属性赋值的格式为@注解名称(属性名1=属性值1,名2=值2,名3={值31,值32})

使用:仅仅是打了一个标记,并不会有什么神奇的效果。

演示代码:

  • 使用注解AnnoDemo.java

    package com.itheima.demo2;
    
    /**
     * 使用自定义注解的类
     */
    public class StudentOp {
    
        // 使用注解,要求使用时该注解的属性都必须有值(默认值,或者手动赋值)
        // 仅仅是打了一个标记,并不会有什么神奇的效果。
        @Test02(num = 123,num2 = 2)
        public void save() {
            System.out.println("save  running....");
        }
    
    
        public void delete() {
            System.out.println("delete  running....");
        }
    
    
        public void update() {
            System.out.println("update  running....");
        }
    
        public void get() {
            System.out.println("get  running....");
        }
    }

2.5.3value属性

  • value属性使用频率较高,所以可以在使用注解的时候可以简化

    • 使用注解的时候,如果只有一个属性需要手动赋值,并且该属性为value属性,其中value=就可以省略不写

      //以下两种方式效果一样
      // @Test02(value = "zs")
      @Test02("zs")
      public void delete() {
          System.out.println("delete  running....");
      }
注意
  • 使用注解标注某个元素后,仅仅是打了一个标记/记号/标签,但是他还不能产生效果
  • 如果想要有效果,就需要通过反射获取被标注的元素,再根据这个元素获取其标注的注解;根据注解类型以及注解的属性值,写代码做不同的处理。这个时候,注解才有了效果/作用。

    eg:定身符。如果普通人拿张纸写个符,贴上没用;道士,拿张纸写个符,再对符施个法,这个符贴上去才有用。施法≈反射

2.6 元注解

元注解:meta-annotation,就是负责标注其他注解的注解。

JDK5定义了4个标准元注解,它们被用来对其它注解做限制说明。

2.6.1 常见元注解

注解作用
@Target限定目标注解可以标注的位置,包括类/接口/枚举、方法、参数、构造方法、变量等位置
@Retention限定目标注解存活时间。
默认是字节码文件,表示保留到字节码,可取值源码、字节码、运行时
@Inherited目标注解可以被继承
@Document该注解参与JDKAPI文档的生成

1669605786028

@Target

// 常用的位置如下,在枚举ElementType中
// public enum ElementType {}

/** Class, interface (including annotation type), or enum declaration */
/** 类, 接口(包括注解), 或者枚举的声明上 */
TYPE,

/** Field declaration (includes enum constants) */
/** 属性 (包括枚举项)声明上 */
FIELD,

/** Method declaration */
/** 方法声明上 */
METHOD,

/** Formal parameter declaration */
/** 形参上 */
PARAMETER,

/** Constructor declaration */
/** 构造方法上 */
CONSTRUCTOR,

演示代码:

需求:

  • 定义一个MyTest3注解,并添加@Target注解用来限定MyTest3的只能标注在类上

@Retention

该注解的属性值类型为RetentionPolicy

RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;

RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;

RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;常用值。
这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码对象。

/**
 * 表示目标注解的保留策略。
 * 该枚举的枚举项作为@Retention注解的value属性值,确定被标注注解的保留策略/存活时间。
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
public enum RetentionPolicy {
    /**
     * 只存活在源码中,源文件中有效。
     */
    SOURCE,

    /**
     * 存活到字节码文件中,字节码中有效。默认值。
     */
    CLASS,

    /**
     * 存活到字节码对象中,运行时有效。常用值。
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

2.7 解析自定义注解(理解)

需要先定义自定义注解,才有注解可以解析;

定义好之后,通过反射解析自定义好的注解

2.7.1 相关API

Class、Method、Field、Constructor都实现了AnnotatedElement接口,他们都拥有解析注解的能力。

AnnotatedElement接口定义的解析注解方法如下:

方法签名说明
boolean isAnnotationPresent(Class annotationClass)判断当前元素上是否标注有指定的注解
T getAnnotation(Class annotationClass);获取当前元素上标注的指定注解的对象
Annotation[] getAnnotations()获取当前元素上标注的所有注解对象

image-20230314155715663

2.7.2 案例

  • 需求

    • 自定义一个注解@Test,限定标注在方法上;如果一个类的某个方法标注了@Test注解,就执行该方法
  • 实现步骤

    1. 自定义一个注解Test,且限定为只能标注在方法上

      (选做:为注解定义属性times,用于指定被标注的方法自动调用的次数)

    2. 新建一个类StudentOp,其中定义多个方法
    3. 部分方法被@Test注解标注(标注之后,没有任何效果,需要配合反射才能产生效果)
    4. 在测试类中,获取StudentOp类的Class对象
    5. 获取StudentOp类中所有的方法的对象
    6. 遍历每一个方法对象
    7. 判断是否标注有对应的注解
    8. 如果有,就通过反射调用之;否则,不作任何处理。

演示代码:

  • 自定义一个注解@Test

    package com.itheima.demo04;
    
    /**
     * 自定义注解解析:自定义注解
     */
    // 1. 自定义一个注解Test,且限定为只能标注在方法上
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Test {
    
        // 2. 为注解定义属性times,用于指定被标注的方法自动调用的次数
        int times() default 1;
    
    }
  • 定义一个类,使用该注解

    package com.itheima.demo04;
    
    /**
     * 自定义注解解析:学生操作类
     */
    // 3. 新建一个类StudentOp,其中定义多个方法
    public class StudentOp {
    
        // 4. 部分方法被@Test注解标注(标注之后,没有任何效果,需要配合反射才能产生效果)
        // @Test
        public void save() {
            System.out.println("save  running....");
        }
    
        @Test(times = 100)
        public void delete() {
            System.out.println("delete  running....");
        }
    
        public void update() {
            System.out.println("update  running....");
        }
    
        @Test(times = 5)
        public void get() {
            System.out.println("get  running....");
        }
    
    }
  • 定义一个测试类,内部编写:解析注解并给出不同处理的逻辑代码

    package com.itheima.demo04;
    
    
    /**
     * 自定义注解解析:测试类
     */
    public class AnnoParseDemo {
        public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {
            // 5. 在测试类中,获取StudentOp类的Class对象
            Class stuOpClassObj = Class.forName("com.itheima.demo04.StudentOp");
    
            Object stuOpObj = stuOpClassObj.newInstance();
    
            // 6. 获取StudentOp类中所有的方法的对象
            Method[] allMethodObjs = stuOpClassObj.getDeclaredMethods();
    
            // 7. 遍历每一个方法对象,
            for (Method methodObj : allMethodObjs) {
                // 8. 判断是否标注有对应的注解;
                if (methodObj.isAnnotationPresent(Test.class)) {
                    // 9. 如果有,就通过反射调用之;否则,不作任何处理。
                    // 目标方法返回值 invoke(目标方法所属类对象,调用目标方法传递的实参)
    
                    // 10. 获取标注的注解对象
                    Test testObj = methodObj.getAnnotation(Test.class);
    
                    //11. 获取注解对象times属性的值
                    int times = testObj.times();
    
                    for (int i = 0; i < times; i++) {
                        methodObj.invoke(stuOpObj);
                    }
                }
            }
        }
    }

==3. Junit==

3.1 概述

JUnit:开源的单元测试工具,提供注解识别并运行要被测试的方法。

3.2 特点

  • 简单优雅。直接在已有编码上测试运行即可。
  • 结果直观。测试结果通过颜色区分(绿色成功,红色失败),原有的所有信息都可以被正常展示。

3.3 实现步骤

  1. 导入jar包并添加进类库:junit-4.9.jar
  2. 正常编写代码
  3. 在要被测试的方法上添加注解@Test,标注有该注解的方法就可以直接运行了,而“不依赖”main方法。

3.4 代码演示

package com.itheima;

import org.junit.Test;

/**
 * Junit入门案例
 *
 * @Author Vsunks.v
 * @Date 2023/5/24 9:09
 * @Blog blog.sunxiaowei.net/996.mba
 * @Description: Junit入门案例
 */
public class JunitDemo01 {


    // 1. 导入jar包并添加进类库:junit-4.9.jar
    // 2. 正常编写代码
    @Test
    public void add(){
        System.out.println(1/0);
        int num1 = 10;
        int num2 = 20;
        int sum = num1 + num2;
        System.out.println("sum = " + sum);
    }

    // 3. 在要被测试的方法上添加注解@Test,标注有该注解的方法就可以直接运行了,而“不依赖”main方法。
    /* public static void main(String[] args) {

    } */


}

3.5 常用注解

  • 相关注解

| 注解 | 作用 | 备注 |
| ---------------- | -------------------------------------------------------------------------- | ------ |
| @Test | 标注的目标方法为测试方法,可以直接运行测试,要求方法无参无返回值且非静态 | |
| @Before | 标注的目标方法在每个测试方法运行前都要运行一次 | |
| @After | 标注的目标方法在每个测试方法运行后都要运行一次 | |
| @BeforeClass | 标注的目标方法在当前测试类加载时被执行一次,目标方法需要static修饰 | |
| @AfterClass | 标注的目标方法在所有测试方法执行完后被执行一次,目标方法需要static修饰 | |

? 主要用作开始测试前的准备工作和结束时的收尾工作。

  • 演示代码

    import org.junit.*;
    
    public class JunitDemo2 {
    
        // 在加载类的时候执行,只会被执行一次。
        // 方法需要被static修饰,
        @BeforeClass
        public static void beforeClass() {
            System.out.println("beforeClass");
        }
    
        // 在销毁之前执行,只会被执行一次
        // 方法需要被static修饰
        @AfterClass
        public static void afterClass() {
            System.out.println("afterClass");
        }
    
        // 每个测试方法执行前都会执行
        @Before
        public void before() {
            System.out.println("before");
        }
    
        @Test
        public void test1() {
            System.out.println("test1");
        }
        @Test
        public void test2() {
            System.out.println("test2");
        }
    
        // 每个测试方法执行后都要执行
        @After
        public void after() {
            System.out.println("after");
        }
    }
注意:
  • 同一个类中可以存在多个方法被@Test标注
  • 被@Test标注的方法要求公共无参无返回值非静态。
  • 对于有参有返回值的方法,可以再被一个无参无返回值的方法包裹后再测试

4. 代理(理解)

目标:

  • 理解代理的概念,
  • 掌握静态代理编码。
  • 了解动态代理。

4.0 代理导学(理解)

先用生活中形象的例子讲解一下代理。

有一个大歌星杨超越,它有唱歌跳舞的本领,并以此赚钱

但是每次做节目,唱歌的时候要准备话筒、收钱,再唱歌;跳舞的时候也要准备场地、收钱、再跳舞

杨超越想专注于唱歌、跳舞;所以找中介公司(经纪人公司),请了一个代理人

之后,杨超越就可以专注于自己擅长的领域;

如果有人想请杨超越演出,直接找代理人就可以了;

代理人做好各种安排,等唱歌、跳舞的时候请杨超越本人上场即可。

代码实现上述逻辑:

  • 杨超越(杨超越是个具体的对象,应该属于大歌星类)

    package com.itheima.staticproxy;
    
    /**
     * 歌星类
     *
     * @Author Vsunks.v
     * @Blog blog.sunxiaowei.net/996.mba
     * @Description: 歌星类
     */
    public class SongStar {
        /**
         * 歌星名称
         */
        private String name;
    
        /**
         * 构造方法
         */
        public SongStar(String name) {
            this.name = name;
        }
    
        /**
         * 唱歌
         */
        public String sing(String songName) {
            System.out.println(name + "正在激情似火的唱" + songName);
            return "谢谢大家!谢谢大家!";
        }
    
        /** 跳舞  */
        public void dance(){
            System.out.println(name+"正在跳优美的舞蹈……");
        }
    }
  • 代理人

    package com.itheima.staticproxy;
    
    /**
     * 明星代理人类
     *
     * @Author Vsunks.v
     * @Blog blog.sunxiaowei.net/996.mba
     * @Description: 明星代理人类
     */
    public class StarProxy {
        /*
            1. 代理人类中应该要定义哪些方法?
                代理人需要和被代理人(歌星杨超越)有相同的功能。
                别人找这个代理人接活的时候才能轻松知道是否找对人。
                从Java代码角度来说:
                    让代理人类和歌星类有同名的方法
    
            2. 代理人类和歌星类是什么关系,在Java代码中如何体现?
                代理人会安排被代理人的日程,并要求在特定时间场合出演
                从Java代码角度来说
                    在代理人类中定义一个被代理人类型的成员变量,可以轻松调用被代理人对象的功能方法
         */
    
        // 1. 添加被代理人类型的成员变量
        /** 被代理的大明星  */
        private SongStar songStar;
    
        public StarProxy(SongStar songStar) {
            this.songStar = songStar;
        }
    
        // 2. 在代理人类中定义和被代理人同名方法,并在方法内部调用被代理人对应的方法
        /** 唱歌 */
        public String sing(String songName) {
            //代理人做准备工作
            System.out.println("收钱、准备话筒等工作……");
            // 大明星出面唱歌
            return songStar.sing(songName);
        }
    
        /** 跳舞  */
        public void dance(){
            //代理人做准备工作
            System.out.println("收钱,准备场地等工作……");
    
            // 大明星出面跳舞
            songStar.dance();
    
        }
    
    }
  • 测试类

    package com.itheima.staticproxy;
    
    /**
     * 测试类
     *
     * @Author Vsunks.v
     * @Blog blog.sunxiaowei.net/996.mba
     * @Description: 测试类
     */
    public class ProxyDemo {
        public static void main(String[] args) {
            // 1. 大明星杨超越出道
            SongStar ycy = new SongStar("杨超越");
    
            // 2. 代理人上场,代理杨超越
            StarProxy ycyProxy = new StarProxy(ycy);
    
            // 3. 找代理人唱歌
            String result = ycyProxy.sing("姬霓太美");
            System.out.println("唱完了!" + result);
    
            // 4. 找代理人跳舞
            ycyProxy.dance();
    
        }
    }

上述代码图示如下:

image-20230327164905475

我们要规范代理,要求代理人向外宣传的内容需要和被代理的大歌星已经掌握的技能相符。

也就是要求代理类的方法要和大歌星的方法要一致,最理想的状态是方法声明(签名)完全一致。

所以可以定义一个接口,让代理人和大歌星都实现这个接口

  • 父接口类(拜师。两者都要实现这个接口)

    package com.itheima.proxy02;
    
    /**
     *  父接口,规定唱歌跳舞的技能
     */
    public interface Star {
        // 规定子类必须有唱歌的功能
        String sing(String songName);
    
        // 规定子类必须有跳舞的功能
        void dance();
    }
    
  • 大歌星类

    package com.itheima.proxy02;
    
    /**
     * 歌星类,实现Star接口
     */
    public class SongStar implements Star{
        // 内部和原来一样
    }
  • 代理人类

    package com.itheima.proxy02;
    
    /**
     * 代理人类,实现Star接口
     */
    public class StarProxy implements Star {
        // 内部和原来一样
    }
    
  • 测试类代码不变

图示

image-20230327171812581

效果:

image-20230327172100540

4.1 静态代理(理解)

上述实现过程就是静态代理。

静态代理:定义一个类A,类A中持有被增强的类B的对象,并在A类的方法中调用B类中方法前后,编写额外的增强代码。

静态:代理类A要真正定义出来。写死了,不能变了,所以就是静态。

代理:A代理了B,A就需要持有并增强B;

  • 持有:A中定义B类型的变量(以便A可以调用B的功能,代理人在有必要的时候可以要求B在某某时间场合表演)
  • 增强:A可以在B表演的前后,做一些准备工作、收尾工作、收钱工作等。

代理模式:为其他对象提供 一种代理以控制对这个对象的访问。是23种设计模式之一。

思考:

  1. 怎么保证代理和被代理的人对外表现的行为一致(有相同的方法)?

    实现同一个接口

  2. 代理和被代理人之间什么关系?

    师门兄弟,代理人是大师兄,被代理人是小师妹

  3. 代理和被代理人搭配工作时,工作流程是什么样的?

    代理人接活并准备,被代理人干活

4.2 动态代理概述(了解)

静态代理需要定义出来一个代理类,比较麻烦;JDK中提供了动态代理,简化代理模式的编码。

动态代理:在不修改源码的前提下,在程序运行期间,动态的对方法进行增强。

  • 动态:不需要编写出来代理类,在程序运行期间动态生成;也就是看不到代理对象所属的类。
  • 代理:A代理了B,A就需要持有并增强B;

    • 持有:A中定义B类型的变量(以便A可以调用B的功能,代理人在有必要的时候可以要求B在某某时间场合表演)
    • 增强:A可以在B表演的前后,做一些准备工作、收尾工作、收钱工作等。
注意:
  • JDK动态代理是基于接口的代理,所以必须要有接口;
  • 在程序运行期间对接口或者接口的子类增强。

4.3 代码实现(了解)

准备工作:

  1. 定义明星接口

    public interface Star{
        String sing(String name);
        void dance();
    }
  2. 定义大歌星,实现明星接口

    public class SongStar implements Star{
        private String name;
    
        public SongStar(String name){
            this.name = name;
        }
    
        public String sing(String songName){
            System.out.println(name + "正在唱:"+songName);
            return "谢谢!谢谢大家";
        }
        public void dance(){
            System.out.println(name + "正在优美的跳舞");
        }
    }
  3. 测试类中,创建一个大歌星:杨超越

    public class DynamicProxyDemo{
        public static void main(String[] args){
            // 1. 创建大歌星对象
            SongStar ycy = new SongStar("杨超越");
    
        }
    }

相关API:

  • Java为开发者提供的一个生成代理对象的类叫Proxy类。
  • 通过Proxy类的newProxyInstance(...)方法可以为实现了某一接口的类生成代理对象。
  • 调用方法时需要传递三个参数,该方法的参数解释可以查阅API文档,如下。

    ,如下。

    1669620794550

演示代码:

大歌星类和明星接口复用之前代码。

  • 测试类中代码

    为SongStar对象生成代理对象

    package com.itheima.dynamicproxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class DynamicProxyDemo{
        public static void main(String[] args){
            // 1. 创建大歌星对象
            SongStar ycy = new SongStar("杨超越");
    
            // 2. 代理人上场,代理杨超越
            /*
            java.lang.reflect.Proxy类中提供了静态方法newProxyInstance。
                可以根据被代理对象生成一个代理对象
                    代理对象可以在被代理对象执行方法前后执行增强代码
                    代理对象也会实现和被代理对象一样的接口
                public static Object newProxyInstance(
                      ClassLoader loader,       类加载器,要使用和被代理对象相同的类加载器
                      Class<?>[] interfaces,    拜师。代理对象和被代理对象实现相同接口
                      InvocationHandler h       增强逻辑。通常使用匿名内部类编写逻辑
                )
    
             */
            Star starProxyObj = (Star) Proxy.newProxyInstance(
                    ycy.getClass().getClassLoader(),
                    new Class[]{Star.class},
                    new InvocationHandler() {
                        /**
                         * 内部编写增强逻辑和对目标方法的调用。
                         * @param proxy     最终生成的代理对象。我们一般不用他
                         * @param method    目标方法封装的Method对象
                         * @param acutalArgs      目标方法执行时传递的实参
                         * @return 目标方法返回值。我们需要把目标方法的返回值返回出去
                         * @throws Throwable
                         */
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] acutalArgs) throws Throwable {
                            // 1. 在目标方法调用前后,做准备工作:收钱、准备场地
                            System.out.println("收钱、准备场地");
    
                            // 2. 对目标方法调用(让杨超越唱歌、跳舞)
                            Object returnValue = method.invoke(ycy, acutalArgs);
    
                            // TODO 目标方法调用后,收尾工作:收拾场地,维护粉丝……
    
                            // 3. 获取到目标方法的返回值之后,一定要把该值再return出去,否则外界无法获取目标方法的返回值
                            return returnValue;
                        }
                    }
    
            );
    
    
            // 3. 找代理人唱歌
            String result = starProxyObj.sing("基尼太美");
            System.out.println("result = " + result);
    
            // 4. 找代理人跳舞
            starProxyObj.dance();
        }
    }

4.3 动态代理应用(了解)

学习完动态代理的基本使用之后,接下来我们再做一个应用案例。

1669621165245

现有如下代码

/**
 *  用户业务接口
 */
public interface UserService {
    // 登录功能
    void login(String loginName,String passWord) throws Exception;
    // 删除用户
    void deleteUsers() throws Exception;
    // 查询用户,返回数组的形式。
    String[] selectUsers() throws Exception;
}

下面有一个UserService接口的实现类,下面每一个方法中都有计算方法运行时间的代码。

/**
 * 用户业务实现类(面向接口编程)
 */
public class UserServiceImpl implements UserService{
    @Override
    public void login(String loginName, String passWord) throws Exception {
        long time1 = System.currentTimeMillis();
        if("admin".equals(loginName) && "123456".equals(passWord)){
            System.out.println("您登录成功,欢迎光临本系统~");
        }else {
            System.out.println("您登录失败,用户名或密码错误~");
        }
        Thread.sleep(1000);
        long time2 = System.currentTimeMillis();
        System.out.println("login方法耗时:"+(time2-time1));
    }

    @Override
    public void deleteUsers() throws Exception{
        long time1 = System.currentTimeMillis();
        System.out.println("成功删除了1万个用户~");
        Thread.sleep(1500);
        long time2 = System.currentTimeMillis();
        System.out.println("deleteUsers方法耗时:"+(time2-time1));
    }

    @Override
    public String[] selectUsers() throws Exception{
        long time1 = System.currentTimeMillis();
        System.out.println("查询出了3个用户");
        String[] names = {"张全蛋", "李二狗", "牛爱花"};
        Thread.sleep(500);
        long time2 = System.currentTimeMillis();
        System.out.println("selectUsers方法耗时:"+(time2-time1));
        return names;
    }
}

观察发现如下问题:

  1. 统计耗时的代码不属于核心业务代码,不应该写在核心业务类中
  2. 统计耗时的方法高度相似,代码复用性差

1669621335888

接下来使用动态代理优化:

  1. 删除UserService中计算耗时相关代码。修改后如下:

    /**
     * 用户业务实现类(面向接口编程)
     */
    public class UserServiceImpl implements UserService{
        @Override
        public void login(String loginName, String passWord) throws Exception {
            if("admin".equals(loginName) && "123456".equals(passWord)){
                System.out.println("您登录成功,欢迎光临本系统~");
            }else {
                System.out.println("您登录失败,用户名或密码错误~");
            }
            Thread.sleep(1000);
        }
    
        @Override
        public void deleteUsers() throws Exception{
            System.out.println("成功删除了1万个用户~");
            Thread.sleep(1500);
        }
    
        @Override
        public String[] selectUsers() throws Exception{
    
            System.out.println("查询出了3个用户");
            String[] names = {"张全蛋", "李二狗", "牛爱花"};
            Thread.sleep(500);
    
            return names;
        }
    }
  2. 工具类中为UserService生成动态代理对象

    在动态代理中调用目标方法,在调用目标方法之前和之后记录毫秒值,并计算方法运行的时间。代码如下

    public class ProxyUtil {
        public static UserService createProxy(UserService userService){
            UserService userServiceProxy
                = (UserService) Proxy.newProxyInstance(
                ProxyUtil.class.getClassLoader(),
                new Class[]{UserService.class}, 
                new InvocationHandler() {
                                                                                            @Override
                public Object invoke(                                                                             Object proxy, 
                                  Method method, 
                                      Object[] args) throws Throwable {                             if(
                        method.getName().equals("login") ||                                             method.getName().equals("deleteUsers")||
                        method.getName().equals("selectUsers")){
                        //方法运行前记录毫秒值     
                        long startTime = System.currentTimeMillis();
                        //执行方法
                        Object rs = method.invoke(userService, args);
                        //执行方法后记录毫秒值
                        long endTime = System.currentTimeMillis();
    
                        System.out.println(method.getName() + "方法执行耗时:" + (endTime - startTime)/ 1000.0 + "s");
                        return rs;
                   }else {
                        Object rs = method.invoke(userService, args);
                        return rs;                                                                }
               }                                                                 });
            //返回代理对象
            return userServiceProxy;
        }
    }
  3. 测试类中为UserService创建代理对象并测试

    /**
     * 目标:使用动态代理解决实际问题,并掌握使用代理的好处。
     */
    public class Test {
        public static void main(String[] args) throws Exception{
            // 1、创建用户业务对象。
            UserService userService = ProxyUtil.createProxy(new UserServiceImpl());
    
            // 2、调用用户业务的功能。
            userService.login("admin", "123456");
            System.out.println("----------------------------------");
    
            userService.deleteUsers();
            System.out.println("----------------------------------");
    
            String[] names = userService.selectUsers();
            System.out.println("查询到的用户是:" + Arrays.toString(names));
            System.out.println("----------------------------------");
    
        }
    }
  4. 执行结果如下图所示

    1669622545712

最后修改:2023 年 05 月 27 日
如果觉得我的文章对你有用,请随意赞赏