• Java进阶(十四)单元测试、反射、注释、动态代理


    十四、单元测试、反射、注释、动态代理

    需要学会什么?

    • 单元测试:开发好的系统中存在很多的方法,如何对这些方法的正确性进行测试。
    • 反射:如何在程序运行时去得到Class对象,然后去获取Class中的每个成分。
    • 注解:注解是什么,具体是如何在Java程序中解决问题的。
    • 动态代理:框架技术的底层会用到。

    1.单元测试

    a.单元测试概述

    单元测试:

    • 单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法。因此单元测试就是针对Java方法的测试,进而简称方法的正确性。

    目前测试方法是怎么进行的,存在什么问题?

    • 只有一个main方法,如果一个方法的测试失败了,其他方法测试会收到影响。
    • 无法得到测试的结果报告,需要程序员自己去观察测试是否成功。
    • 无法实现自动化测试。

    JUnit单元测试框架:

    • JUnit是使用Java语言实现的单元测试框架,它是开源的,Java开发者都应当学习并使用JUnit编写单元测试。
    • 几乎所有的IDE工具都集成了JUnit,这样我们就可以直接在IDE中编写并运行JUnit测试,JUnit目前最新版本是5。

    JUnit优点:

    • JUnit可以灵活的选择执行哪些测试方法,可以一键执行全部测试方法。
    • JUnit可以生成全部方法的测试报告。
    • 单元测试中的某个方法测试失败了,不会影响其他测试方法的测试。

    总结:

    1. JUnit单元测试是做什么的?
      • 测试类中方法的正确性。
    2. JUnit单元测试的优点报告是什么?
      • JUnit可以选择执行哪些测试方法,可以一键执行全部测试方法的测试。
      • JUnit可以生成测试报告,如果是测试良好则是绿色,如果是测试失败则是红色。
      • 单元测试中的某个方法测试失败了,不会影响其他测试方法的测试。

    b.单元测试快速入门

    需求:

    • 使用单元测试进行业务方法预期结果、正确性测试的快速入门。

    分析:

    1. 将JUnit的jar包导入到项目中。
      • IDEA通常整合好了JUnit框架,一般不需要导入。(输入@Test后报红提示,Alt + Enter选择Add 'JUnit5.x.x' to classpath,之后点击OK即可)
      • 如果IDEA没有整合好,需要自己手工导入如下2个JUnit的Jar包到模块。
    2. 编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法。
    3. 在测试方法上使用@Test注解:标准该方法是一个测试方法。
    4. 在测试方法中完成被测试方法的预期正确性测试。
    5. 选中测试方法,选择”JUnit运行“,如果测试良好则是绿色,如果测试失败则是红色。

    UserService.java

    /**
     * 用户服务
     */
    public class UserService {
    
        /**
         * 用户登录
         * @param username 用户名
         * @param password 密码
         * @return 登录状态
         */
        public String loginName(String username, String password) {
            if ("admin".equals(username) && "123456".equals(password)) {
                return "登录成功!";
            } else {
                return "用户名或密码错误!";
            }
        }
    
        /**
         * 查看全部用户
         */
        public void selectUsers() {
            System.out.println(10 / 0);
    
            System.out.println("查询全部用户成功!");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    TestUserService.java

    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;
    
    /**
     * 测试用户服务类
     */
    public class TestUserService {
    
        /**
         * 测试用户登录方法
         * 注意:
         *  1.必须是公开的 无参数 无返回值的方法
         *  2.测试方法必须使用@Test注解标记
         */
        @Test
        public void testLoginName() {
            UserService userService = new UserService();
            String result = userService.loginName("admin", "123456");
    
            // 进行预期结果的正确性测试:断言
            // 期待值 实际值 期待值与实际值不符时提示信息
            Assertions.assertEquals("登录成功!", result, "用户登录业务可能出现问题!");
        }
    
        /**
         * 测试查看全部用户方法
         */
        @Test
        public void testSelectUsers() {
            UserService userService = new UserService();
            userService.selectUsers();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    JUnit单元测试执行结果:
    在这里插入图片描述

    注意:

    • 单元测试可以一次一个方法独单进行测试。
    • 单元测试可以一次整个类进行测试。
    • 单元测试可以一次整个项目进行测试。(IDEA中对项目文件目录右击 -> Run “All Tests”)

    总结:

    1. JUnit单元测试的实现过程是什么样的?
      • 必须导入JUnit框架的jar包。
      • 定义的测试方法必须是无参数无返回值且公开的方法。
      • 测试方法使用@Test注解标记。
    2. JUnit测试某个方法,测试全部方法怎么处理?成功的标志是什么?
      • 测试某个方法直接右键该方法启动测试。
      • 测试全部方法,可以选择类或者模块启动。
      • 红色失败,绿色通过,黄色表示没有出异常但是结果没有通过。

    c.单元测试常用注解

    JUnit常用注解

    JUnit4JUnit5说明
    @Test@Test测试方法。
    @Before@BeforeEach用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。
    @After@AfterEach用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。
    @BeforeClass@BeforeAll用来修饰静态方法,该方法会在所有测试方法执行之前执行一次。
    @AfterClass@AfterAll用来修饰静态方法,该方法会在所有测试方法执行之后执行一次。
    • 开始执行的方法:初始化资源。
    • 执行完之后的方法:释放资源。

    TestUserService.java

    import org.junit.jupiter.api.*;
    
    /**
     * 测试用户服务类
     */
    public class TestUserService {
    
        @BeforeEach
        public void beforeEach() {
            System.out.println("===beforeEach方法执行一次===");
            
            // 作用:如初始化I/O流
        }
    
        @AfterEach
        public void afterEach() {
            System.out.println("===afterEach方法执行一次===");
            
            // 作用:如关闭I/O流
        }
    
        @BeforeAll
        public static void beforeAll() {
            System.out.println("===beforeAll方法执行一次===");
            
            // 作用:如初始化静态资源
        }
    
        @AfterAll
        public static void afterAll() {
            System.out.println("===afterAll方法执行一次===");
            
            // 作用:如回收静态资源
        }
    
        /**
         * 测试用户登录方法
         * 注意:
         *  1.必须是公开的 无参数 无返回值的方法
         *  2.测试方法必须使用@Test注解标记
         */
        @Test
        public void testLoginName() {
            System.out.println("===测试用户登录方法===");
    
            UserService userService = new UserService();
            String result = userService.loginName("admin", "123456");
    
            // 进行预期结果的正确性测试:断言
            // 期待值 实际值 期待值与实际值不符时提示信息
            Assertions.assertEquals("登录成功!", result, "用户登录业务可能出现问题!");
        }
    
        /**
         * 测试查看全部用户方法
         */
        @Test
        public void testSelectUsers() {
            System.out.println("===测试查看全部用户方法===");
    
            UserService userService = new UserService();
            userService.selectUsers();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    JUnit常用注解执行结果:
    在这里插入图片描述

    2.反射

    a.反射概述

    反射的概述:

    • 反射是指对于任何一个Class类,在“运行的时候”都可以直接得到这个类全部成分。
    • 在运行时,可以直接得到这个类的构造器对象:Constructor。
    • 在运行时,可以直接得到这个类的成员变量对象:Field。
    • 在运行时,可以直接得到这个类的成员方法对象:Method。
    • 这种运行时动态获取类信息以及动态调用类中成分的能力被称为Java语言的反射机制。

    反射的关键:

    • 反射的第一步都是先得到编译后的Class对象,然后就可以得到Class的全部成分。

      HelloWorld.java -> javac -> HelloWorld.class
      
      Class c = HelloWorld.class;
      
      • 1
      • 2
      • 3

    总结:

    1. 反射的基本作用、关键?
      • 反射是在运行时获取类的字节码文件对象:可以解析类中的全部成分。
      • 反射的核心思想和关键就是:得到编译以后的Class文件对象。

    b.反射获取类对象

    反射的第一步:获取Class类的对象

    编译
    提取到内存中去
    java文件
    Class文件/字节码文件
    Class对象/内存中
    内存
    Class对象/Student.class
    private String name
    Field
    private int age
    无参构造器
    Constructor
    有参构造器
    成员方法
    Method

    Student.java

    public class Student {
    }
    
    
    • 1
    • 2
    • 3

    Test.java

    /**
     * 目标:反射的第一步 获取Class对象
     */
    public class Test {
        public static void main(String[] args) throws Exception {
            // 1.Class类中的一个静态方法 forName 全限名/包名+类名
            Class<?> studentClass = Class.forName("com.javase.reflectclassdemo.Student");
            // class com.javase.reflectclassdemo.Student
            System.out.println(studentClass);
    
            // 2.类名.class
            Class<Student> studentClass1 = Student.class;
            // class com.javase.reflectclassdemo.Student
            System.out.println(studentClass1);
    
            // 3.对象.getClass() 获取对象对应类的Class对象
            Student student = new Student();
            Class<? extends Student> studentClass2 = student.getClass();
            // class com.javase.reflectclassdemo.Student
            System.out.println(studentClass2);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    总结:

    1. 反射的第一步是什么?
      • 获取Class类对象,可以解析类的全部成分。
    2. 获取Class类的对象的三种方式。
      • 方式一:Class c1 = Class.forName(“全限名”);
      • 方式二:Class c2 = 类名.class;
      • 方式三:Class c3 = 对象.getClass();

    c.反射获取构造器对象

    内存
    Student
    private String name
    2.获得Constructor对象
    无参构造器
    有参构造器
    3.创建对象
    1.获取对象
    private int age
    成员方法

    使用反射技术获取构造器对象并使用:

    • 反射得第一步是先得到类对象,然后从类对象中获取类得成分对象。
    • Class类中用于获取构造器得方法。
    方法说明
    Constructor[] getConstructors()返回所有构造器对象得数组(只能拿public的)。
    Constructor[] getDeclaredConstructors()返回所有构造器对象的数组,存在就能拿到。
    Constructor getConstructor(Class… parameterTypes)返回单个构造器对象(只能拿public的)。
    Constructor getDeclaredConstructor(Class… parameterTypes)返回单个构造器对象,存在就能拿到。

    Student.java

    public class Student {
        private String name;
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        private Student() {
            System.out.println("无参构造器执行!");
        }
    
        public Student(String name, int age) {
            System.out.println("有参构造器执行!");
    
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    使用反射技术获取构造器对象并使用:

    • 获取构造器的作用依然是初始化一个对象返回。

    Constructor类中用于创建对象的方法:

    符号说明
    T newInstanc(Object… initargs)根据指定的构造器创建对象。
    public void setAccessible(boolean flag)设置未true,表示取消访问检查,进行暴力反射。

    Student.java

    public class Student {
        private String name;
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        private Student() {
            System.out.println("无参构造器执行!");
        }
    
        public Student(String name, int age) {
            System.out.println("有参构造器执行!");
    
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    TestStudent.java

    import org.junit.jupiter.api.Test;
    
    import java.lang.reflect.Constructor;
    
    public class TestStudent {
        /**
         * 获取单个构造器
         */
        @Test
        public void getDeclaredConstructor() throws Exception {
            // 1.获取类对象
            Class<Student> studentClass = Student.class;
    
            // 2.获取单个的构造器对象 此处为无参构造器
            Constructor constructor = studentClass.getDeclaredConstructor();
            System.out.println(constructor.getName() + "==>" + constructor.getParameterCount());
    
            // 如果遇到了私有的构造器 可以暴力反射 只在本次生效
            constructor.setAccessible(true);
    
            // 3.创建Student对象
            Student student = (Student) constructor.newInstance();
            System.out.println(student);
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    总结:

    1. 利用反射技术获取构造器对象的方式?
      • getDeclaredConstructors()
      • getDeclaredConstructors(Class… parameterTypes)
    2. 反射得到的构造器可以做什么?
      • 依然用来是创建对象的
        • public newInstance(Object… initargs)
      • 如果是非public的构造器,需要打卡权限(暴力反射),然后再创建对象。
        • setAccessible(boolean)
        • 反射可以破坏封装性,私有的也可以执行了。

    d.反射获取成员变量对象

    内存
    Student
    private String name
    2.获得Field对象
    private int age
    3.赋值或获取值
    1.获取对象
    无参构造器
    有参构造器
    成员方法

    使用反射技术获取成员变量对象并使用:

    • 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象。
    • Class类中用于获取成员变量的方法。
    方法说明
    Field[] getFields()返回所有成员变量对象的数组(只能拿public的)。
    Field[] getDeclaredField()返回所有成员变量对象的数组,存在就能拿到。
    Field getField(String name)返回单个成员变量对象(只能拿public的)。
    Field getDeclareField(String name)返回单个成员变量对象,存在就能拿到。

    Student.java

    public class Student {
        private String name;
        private int age;
        public static String schoolName;
        public static final String Country = "中国";
    
        private String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public static String getSchoolName() {
            return schoolName;
        }
    
        public static void setSchoolName(String schoolName) {
            Student.schoolName = schoolName;
        }
    
        public Student() {
            System.out.println("无参构造器执行!");
        }
    
        public Student(String name, int age) {
            System.out.println("有参构造器执行!");
    
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    FieldDemo1.java

    import org.junit.jupiter.api.Test;
    
    import java.lang.reflect.Field;
    
    public class FieldDemo1 {
        /**
         * 1.获取全部的成员变量
         */
        @Test
        public void getDeclaredFields() {
            // a.定位Class对象
            Class<Student> studentClass = Student.class;
    
            // b.定位全部成员变量
            Field[] fields = studentClass.getDeclaredFields();
    
            // c.遍历
            for (Field field : fields) {
                // name==>class java.lang.String
                // age==>int
                // schoolName==>class java.lang.String
                // Country==>class java.lang.String
                System.out.println(field.getName() + "==>" + field.getType());
            }
        }
    
        /**
         * 2.获取指定的成员变量
         */
        @Test
        public void getDeclareField() throws Exception {
            // a.定位Class对象
            Class<Student> studentClass = Student.class;
    
            // b.根据名称定位某个成员变量
            Field field = studentClass.getDeclaredField("age");
            // age==>int
            System.out.println(field.getName() + "==>" + field.getType());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    使用反射技术获取成员变量对象并使用:

    • 获取成员变量的作用依然是在某个对象中取值、赋值。
    符号说明
    void set(Object obj, Object value)赋值。
    Object get(Object obj)取值。

    Student.java

    public class Student {
        private String name;
        private int age;
        public static String schoolName;
        public static final String Country = "中国";
    
        private String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public static String getSchoolName() {
            return schoolName;
        }
    
        public static void setSchoolName(String schoolName) {
            Student.schoolName = schoolName;
        }
    
        public Student() {
            System.out.println("无参构造器执行!");
        }
    
        public Student(String name, int age) {
            System.out.println("有参构造器执行!");
    
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    FieldDemo2.java

    import org.junit.jupiter.api.Test;
    
    import java.lang.reflect.Field;
    
    public class FieldDemo2 {
        @Test
        public void setField() throws Exception {
            // a.获取类对象
            Class<Student> studentClass = Student.class;
    
            // b.提取某个成员变量
            Field ageField = studentClass.getDeclaredField("age");
    
            // 暴力打开权限
            ageField.setAccessible(true);
    
            // c.赋值
            Student student = new Student();
            ageField.set(student, 18);
    
            // Student{name='null', age=18}
            System.out.println(student);
    
            // d.取值
            int age = (int) ageField.get(student);
            // 18
            System.out.println(age);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    总结:

    1. 利用反射技术获取成员变量的方式?
      • 获取类中成员变量对象的方法:
        • getDeclaredFields()
        • getDeclareField(String name)
    2. 反射得到成员变量可以做什么?
      • 依然是在某个对象中取值和赋值。
        • void set(Object obj, Object value)
        • Object get(Object obj)
      • 如果某成员变量是非public的,需要打开权限(暴力反射),然后再取值、赋值。
        • setAccessible(booleann)

    e.反射获取成员方法对象

    内存
    Student
    private String name
    2.获得Field对象
    成员方法
    3.运行方法
    1.获取对象
    private int age
    无参构造器
    有参构造器

    使用反射技术获取方法对象并使用:

    • 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象。
    • Class类中用于获取成员方法的方法。
    方法说明
    Method[] getMethods()返回所有成员方法对象的数组。(只能拿public的)
    Method[] getDeclaredMethods()返回所有成员方法对象的数组,存在就能拿到。
    Method getMethod(String name, Class… parameterTypes)返回单个成员方法对象。(只能拿public的)
    Method getDeclaredMethod(String name, Class… parameterTypes)返回单个成员方法对象,存在就能拿到。

    使用反射技术获取方法对象并使用:

    • 获取成员方法的作用依然是在某个对象中进行执行此方法。

    Method类中用于触发执行的方法:

    方法说明
    Object invoke(Object obj, Object… args)运行方法:
    参数一:用obj对象调用该方法。
    参数二:调用方法的传递的参数(如果没有就不写)
    返回值:方法的返回值(如果没有就不写)

    Dog.java

    public class Dog {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Dog() {
        }
    
        public Dog(String name) {
            this.name = name;
        }
    
        public void run() {
            System.out.println("狗跑的很快!");
        }
    
        public void eat() {
            System.out.println("狗啃骨头!");
        }
    
        public String eat(String name) {
            System.out.println("狗吃" + name);
    
            return "吃的很开心!";
        }
    
        public static void address() {
            System.out.println("灌江口");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    MethodDemo.java

    import org.junit.jupiter.api.Test;
    
    import java.lang.reflect.Method;
    
    public class MethodDemo {
        /**
         * 1.获取全部方法
         */
        @Test
        public void getDeclaredMethods() {
            // a.获取类对象
            Class<Dog> dogClass = Dog.class;
    
            // b.提取全部方法
            Method[] methods = dogClass.getDeclaredMethods();
    
            // c.遍历全部方法
            for (Method method : methods) {
                // getName 返回值类型:class java.lang.String 参数个数:0
                // run 返回值类型:void 参数个数:0
                // setName 返回值类型:void 参数个数:1
                // address 返回值类型:void 参数个数:0
                // eat 返回值类型:void 参数个数:0
                // eat 返回值类型:class java.lang.String 参数个数:1
                System.out.println(method.getName() + " 返回值类型:" + method.getReturnType() + " 参数个数:" + method.getParameterCount());
            }
        }
    
        @Test
        public void getDeclardMethod() throws Exception {
            // a.获取类对象
            Class<Dog> dogClass = Dog.class;
    
            // b.提取单个方法对象
            Method method = dogClass.getMethod("eat");
            Method method1 = dogClass.getMethod("eat", String.class);
    
            // 暴力打开权限
            method.setAccessible(true);
            method1.setAccessible(true);
    
            // c.触发方法的执行
            Dog dog = new Dog();
            // 注意:方法如果是没有返回结果的,那么返回的是null
            // 狗啃骨头!
            Object result = method.invoke(dog);
            // null
            System.out.println(result);
            
            // 狗吃骨头
            Object result1 = method1.invoke(dog, "骨头");
            // 吃的很开心!
            System.out.println(result1);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    总结:

    1. 利用反射技术获取成员方法对象的方式:
      • 获取类中成员方法对象:
        • getDeclaredMethods()
        • getDeclaredMethod(String name, Class… parameterTypes)
    2. 反射得到成员方法可以做什么?
      • 依然是在某个对象中触发该方法执行:
        • Object invoke(Object obj, Object… args)
      • 如果某成员方法是非public的,需要打开权限(暴力反射),然后再触发执行:
        • setAccessible(boolean)

    f.反射的作用-泛型擦除/绕过编译阶段为集合添加数据

    反射的作用-绕过编译阶段为集合添加数据:

    • 反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时是可以为集合存入任意类型的元素的

      ArrayList<Integer> list = new ArrayList<>();
      
      list.add(100);
      // 报错
      // list.add("白子画");
      list.add(99);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段的时候,其真实类型都是ArrayList,泛型相当于被擦除了。

    ReflectDemo.java

    import java.lang.reflect.Method;
    import java.util.ArrayList;
    
    public class ReflectDemo {
        public static void main(String[] args) throws Exception {
            // 需求: 反射实现泛型擦除后 加入其他类型的元素
            ArrayList<String> list = new ArrayList<>();
            ArrayList<Integer> list1 = new ArrayList<>();
    
            System.out.println(list.getClass());
            System.out.println(list1.getClass());
    
            // ArrayList.class
            System.out.println(list.getClass() == list1.getClass());
    
            System.out.println("-----------------");
            ArrayList<Integer> list2 = new ArrayList<>();
    
            list2.add(9);
            list2.add(10);
    
            Class studentClass = list2.getClass();
    
            // 定位studentClass类中的add方法
            Method add = studentClass.getDeclaredMethod("add", Object.class);
            boolean result = (boolean) add.invoke(list2, "Python");
    
            // true
            System.out.println(result);
            // [9, 10, Python]
            System.out.println(list2);
    
            /* 不用反射突破泛型限制 */
            ArrayList list3 = list2;
            list3.add("Java");
            // [9, 10, Python, Java]
            System.out.println(list3);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    总结:

    1. 反射为什么可以给约定了泛型的集合存入其他类型的元素?
      • 编译成Class文件进入运行阶段的时候,泛型会自动擦除。
      • 反射是作用在运行时的技术,此时已经不存在泛型了。

    g.反射的作用-通用框架的底层原理

    反射做通用框架:

    需求:

    • 给任意一个对象,在不清楚对象字段的情况,可以把对象的字段名称和对应值存储到文件中去。

    分析:

    1. 定义一个方法,可以接收任意类的对象。
    2. 每次收到一个对象后,需要解析这个对象的全部成员变量名称。
    3. 这个对象可能是任意的,怎么样才可以知道这个对象的全部成员变量名称?
    4. 使用反射获取对象的Class类对象,然后获取全部成员变量信息。
    5. 遍历成员变量信息,然后提取本成员变量在对象中的具体值。
    6. 存入成员变量名称和值到文件中去即可。

    Student.java

    /**
     * 学生类
     */
    public class Student {
        private String name;
        private char sex;
        private int age;
        private String className;
        private String hobby;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public char getSex() {
            return sex;
        }
    
        public void setSex(char sex) {
            this.sex = sex;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getClassName() {
            return className;
        }
    
        public void setClassName(String className) {
            this.className = className;
        }
    
        public String getHobby() {
            return hobby;
        }
    
        public void setHobby(String hobby) {
            this.hobby = hobby;
        }
    
        public Student() {
        }
    
        public Student(String name, char sex, int age, String className, String hobby) {
            this.name = name;
            this.sex = sex;
            this.age = age;
            this.className = className;
            this.hobby = hobby;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", sex=" + sex +
                    ", age=" + age +
                    ", className='" + className + '\'' +
                    ", hobby='" + hobby + '\'' +
                    '}';
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    Teacher.java

    **
     * 老师类
     */
    public class Teacher {
        private String name;
        private char sex;
        private double salary;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public char getSex() {
            return sex;
        }
    
        public void setSex(char sex) {
            this.sex = sex;
        }
    
        public double getSalary() {
            return salary;
        }
    
        public void setSalary(double salary) {
            this.salary = salary;
        }
    
        public Teacher() {
        }
    
        public Teacher(String name, char sex, double salary) {
            this.name = name;
            this.sex = sex;
            this.salary = salary;
        }
    
        @Override
        public String toString() {
            return "Teacher{" +
                    "name='" + name + '\'' +
                    ", sex=" + sex +
                    ", salary=" + salary +
                    '}';
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    MyUtil.java

    import java.io.FileOutputStream;
    import java.io.PrintStream;
    import java.lang.reflect.Field;
    
    public class MyUtil {
        /**
         * 保存任意类型的对象
         * @param object 任意类型的对象
         */
        public static void save(Object object) {
            try {
                // 创建打印流对象 追加写入
                PrintStream printStream = new PrintStream(
                        new FileOutputStream("day17-oop-demo/src/com/javase/reflectframework/data.txt", true)
                );
    
                // 1.提取该对象的全部成员变量 只有反射可以解决
                Class<?> objectClass = object.getClass();
                // 保存类名信息 getSimpleName()获取当前类名 getName()获取全限名
                printStream.println("===" + objectClass.getSimpleName() + "===");
    
                // 2.提取它的全部成员变量
                Field[] fields = objectClass.getDeclaredFields();
    
                // 3.获取成员变量的信息
                for (Field field : fields) {
                    // 成员变量名称
                    String variableName = field.getName();
    
                    // 取值 提取该成员变量在object对象中的值
                    field.setAccessible(true);
                    String variableValue = field.get(object) + "";
    
                    // 保存变量名称和变量值
                    printStream.println(variableName + "=" + variableValue);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    ReflectDemo.java

    /**
     * 目标:提供一个通用框架 支持保存所有对象的具体信息
     */
    public class ReflectDemo {
        public static void main(String[] args) {
            Student student = new Student();
            student.setName("花千骨");
            student.setClassName("葵班");
            student.setAge(16);
            student.setHobby("修仙");
            student.setSex('女');
    
            // 保存student对象
            MyUtil.save(student);
    
            Teacher teacher = new Teacher();
            teacher.setName("朽木清流");
            teacher.setSex('男');
            teacher.setSalary(10000);
    
            // 保存teacher对象
            MyUtil.save(teacher);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    生成的文件:data.txt

    ===Student===
    name=花千骨
    sex=女
    age=16
    className=葵班
    hobby=修仙
    ===Teacher===
    name=朽木清流
    sex=男
    salary=10000.0
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    总结:

    1. 反射的作用?
      • 可以在运行时得到一个类的全部成分然后操作。
      • 可以破坏封装性(很突出)。
      • 可以破坏泛型的约束性(很突出)。
      • 更重要的用途是:做Java高级框架。

    3.注解

    a.注解概述

    注解概述:

    • Java注解(Annotation)又称Java标志,是JDK5.0引入的一种注释机制。
    • Java语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注。

    注解的作用是什么呢?

    • 对Java中类、方法、成员变量做标记,然后进行特殊处理,至于到底做何种处理业务需求来决定。

    b.自定义注解

    自定义注解(格式):

    • 自定义注解就是自己做一个注解来使用。

      public @interface 注解名称 {
          public 属性类型 属性名() default 默认值;
      }
      
      • 1
      • 2
      • 3
    • 特殊属性:

      • value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写。
      • 如果有多个属性,且多个属性没有默认值,那么value名称是不能省略的。

    MyBook.java

    public @interface MyBook {
        String name();
        String[] authors();
        double price();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Book.java

    public @interface Book {
        // 特殊属性
        String value();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    AnnotationDemo.java

    /**
     * 目标:学会自定义注解 掌握其定义格式和语法
     */
    @MyBook(name = "《硅谷之火》", authors = {"迈克尔·斯韦因"}, price = 33.9)
    @Book("/add")
    public class AnnotationDemo {
    
        @MyBook(name = "《硅谷之火》", authors = {"迈克尔·斯韦因"}, price = 33.9)
        private AnnotationDemo() {
    
        }
    
        @MyBook(name = "《硅谷之火》", authors = {"迈克尔·斯韦因"}, price = 33.9)
        public static void main(String[] args) {
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    c.元注解

    元注解:

    • 注解注解的注解。

    元注解有两个:

    • @Target:约束自定义注解只能在哪些地方使用,@Target中可使用的值定义在ElementType枚举类中,常用值如下:
      • TYPE:类、接口。
      • FIELD:成员变量。
      • METHOD,成员方法。
      • PARAMETER,方法参数。
      • CONSTRUCTOR,构造器。
      • LOCAL_VARIABLE,局部变量。
    • @Retention:申明注解的生命周期,@Retention中可使用的值定义在RetentionPolicy枚举类中,常用之如下:
      • SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在。
      • CLASS:注解作用在源码阶段、字节码文件阶段、运行阶段不存在,默认值。
      • RUNTIME:注解作用在源码阶段、字节码文件阶段、运行阶段(开发常用)。

    JUnit框架中源码:

    @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @API(
        status = Status.STABLE,
        since = "5.0"
    )
    @Testable
    public @interface Test {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    MyTest.java

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 自定义注解
     * 约束只能用于方法、成员变量
     */
    @Target({ElementType.METHOD, ElementType.FIELD})
    // 注解一直活着 在运行阶段也不消失
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyTest {
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    AnnotationDemo.java

    /**
     * 目标:认识元注解
     */
    // 此处用该注解会报错
    // @MyTest
    public class AnnotationDemo {
        
        @MyTest
        private String name;
        
        @MyTest
        public void test() {
            
        }
    
        @MyTest
        public static void main(String[] args) {
            
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    总结:

    1. 元注解是什么?
      • 注解注解的注解。
      • @Target约束自定义注解可以标记的范围,@Retention用来约束自定义注解的存活范围。

    d.注解解析

    注解的解析:

    • 注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容。

    与注解解析相关的接口:

    • Annotation:注解的顶级接口,注解都是Annotation类型的对象。

    • AnnotatedElement:该接口定义了与注解解析相关的解析方法。

      方法说明
      Annotation[] getDeclaredAnnotations()获得当前对象上使用的所有注解,返回注解数组。
      T gteDeclaredAnnotation(Class annotationClass)根据注解类型获得对应注解对象。
      boolean isAnnotationPresent(Class annotationClass)判断当前对象是否使用了指定的注解,如果使用了则返回true,否则返回false。
    • 所有的类成分:Class、Method、Field,Constructor,都实现了AnnotatedElement接口他们都拥有解析注解的能力。

    解析注释的技巧:

    • 注解在哪个成分上,我们就先拿哪个成分对象。
    • 比如:注解作用成员方法,则要获得该成员方法对应得Metho对象,再来拿上面得注解。
    • 比如:注解作用在类上,则要该类得Class对象,再来拿上面得注解。
    • 比如:注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解。

    案例:注解解析

    需求:

    • 定义注解Book,要求如下:
      • 包含属性:String value() 书名。
      • 包含属性:double price() 价格,默认值100元。
      • 包含属性:String [] authors() 多位作者。
      • 限制注解使用的位置:类和成员方法上。
      • 指定注解的有效范围:RUNTIME。
    • 定义BookStore类,在类和成员方法上使用Book注解。
    • 定义AnnotationDemo测试类获取Book注解上的数据。

    Book.java

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Book {
        // 特殊属性
        String value();
    
        double price() default  100;
        String[] author();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    BookStoreTest.java

    import org.junit.jupiter.api.Test;
    
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    /**
     * 目标:完成注解的解析
     */
    public class BookStoreTest {
    
        @Test
        public void parseClass() {
            // a.先得到类对象
            Class<BookStore> bookStoreClass = BookStore.class;
    
            // b.判断这个类上面是否存在这个注解
            if (bookStoreClass.isAnnotationPresent(Book.class)) {
                // c.直接获取该注释对象
                Book book = (Book) bookStoreClass.getDeclaredAnnotation(Book.class);
    
                System.out.println(book.value());
                System.out.println(book.price());
                System.out.println(Arrays.toString(book.author()));
            }
        }
    
        @Test
        public void parseMethod() throws Exception {
            // a.先得到类对象
            Class<BookStore> bookStoreClass = BookStore.class;
    
            Method method = bookStoreClass.getDeclaredMethod("test");
    
            // b.判断这个类上面是否存在这个注解
            if (method.isAnnotationPresent(Book.class)) {
                // c.直接获取该注释对象
                Book book = (Book) method.getDeclaredAnnotation(Book.class);
    
                System.out.println(book.value());
                System.out.println(book.price());
                System.out.println(Arrays.toString(book.author()));
            }
        }
    
    }
    
    @Book(value = "《一往无前》", price = 78, author = {"范海涛"})
    class BookStore {
    
        @Book(value = "《价值》", price = 118, author = {"张磊"})
        public void test() {
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    e.注解的应用场景一:jUnit框架

    案例:模拟jUnit框架

    需求:

    • 定义若干个方法,只要加了MyTest注解,就可以在启动时被触发执行。

    分析:

    1. 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在。
    2. 定义若干个方法,只要有@MyTest注解的方法就能在启动时被触发执行,没有这个注解的方法不能执行。

    MyTest.java

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyTest {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    AnnotationDemo.java

    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class AnnotationDemo {
        @MyTest
        public void test1() {
            System.out.println("===test1===");
        }
    
        public void test2() {
            System.out.println("===test2===");
        }
    
        @MyTest
        public void test3() {
            System.out.println("===test3===");
        }
    
        /**
         * 启动菜单:有注解的方法才调用 没有注解的方法不调用
         */
        public static void main(String[] args) throws Exception {
    
            AnnotationDemo annotationDemo = new AnnotationDemo();
    
            // a.获取类对象
            Class<AnnotationDemo> annotationDemoClass = AnnotationDemo.class;
    
            // b.提取全部方法
            Method[] methods = annotationDemoClass.getDeclaredMethods();
    
            // c.遍历方法
            for (Method method : methods) {
    
                // 如果有MyTest注解 启动该方法
                if (method.isAnnotationPresent(MyTest.class)) {
    
                    // d.启动方法
                    method.invoke(annotationDemo);
                }
    
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    执行结果:

    ===test1===
    ===test3===
    
    • 1
    • 2

    4.动态代理

    a.动态代理概述、快速入门

    什么是代理?

    • 代理指:某些场景下对象会找一个代理对象,来辅助自己完成一些工作,如果:歌星(经纪人),买房的人(房产中介)。

    代理主要干什么,是如何工作的?

    • 代理主要对“对象的行为”额外做一些辅助操作。

    如何创建代理对象:

    • Java中代理的代表类是:java.lang.reflect.Proxy

    • Proxy提供了一个静态方法,用于为对象产生一个代理对象返回。

      public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
      
      /* 为对象返回一个代理对象 */
      // 参数一:定义代理类的类加载器。
      // 参数二:代理类要实现的接口列表。
      // 参数三:将方法调用分派到的处理程序。(代理对象的核心处理程序)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    Java中如何生成代理,并指定代理干什么事?

    对象
    接口
    proxy
    newProxyInstance
    代理对象

    Skill.java

    /**
     * 唱歌跳舞技能接口
     */
    public interface Skill {
    
        /**
         * 唱歌
         */
        void sing();
    
        /**
         * 跳舞
         */
        void jump();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Star.java

    /**
     * 明星类
     */
    public class Star implements Skill {
        private String name;
    
        public Star(String name) {
            this.name = name;
        }
    
        /**
         * 唱歌
         */
        @Override
        public void sing() {
            System.out.println(name + "开始唱歌,唱得很好!");
        }
    
        /**
         * 跳舞
         */
        @Override
        public void jump() {
            System.out.println(name + "开始跳舞,跳得很好!");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    StarProxy.java

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 明星代理对象
     */
    public class StarProxy {
        /**
         * 设计一个方法来返回一个明星对象的代理对象
         * @param star 明星对象
         * @return 明星代理对象
         */
        public static Skill getProxy(Star star) {
            // 为明星对象生成一个代理对象
            return (Skill) Proxy.newProxyInstance(
                    star.getClass().getClassLoader(),
                    star.getClass().getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                            System.out.println("收首付款...");
                            System.out.println("把明星送出去唱歌跳舞。");
    
                            // 让明星唱歌跳舞
                            Object result = method.invoke(star, objects);
    
                            System.out.println("收尾款,把明星接回来。");
    
                            return result;
                        }
                    }
            );
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    Test.java

    public class Test {
        public static void main(String[] args) {
            /* 目标:学习开发出一个动态代理的对象出来,理解动态代理的执行流程 */
    
            // 1.创建一个明星类对象 明星类对象必须实现 唱歌跳舞技能接口
            Star star = new Star("花千骨");
    
            // 2.为明星对象生成一个明星代理对象
            Skill starProxy = StarProxy.getProxy(star);
            
            // 3.执行方法
            starProxy.sing();
            // starProxy.jump();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    总结:

    1. 代理是什么?
      • 一个对象,用来对被代理对象的行为额外做一些辅助工作。
    2. 在Java中实现动态代理的步骤是什么样的?
      • 必须存在接口。
      • 被代理对象需要实现接口。
      • 使用Proxy类提供的方法,得到对象的代理对象。
    3. 通过代理对象调用方法,执行流程是什么样的?
      1. 先走代理。
      2. 代理可以为方法额外做一些辅助工作。
      3. 开发真正触发对象的方法执行。
      4. 回到代理中,由代理负责返回结果给方法的调用者。

    b.动态代理的应用案例:做性能分析、代理的好处

    案例:模拟企业业务功能开发,完成每个功能的性能统计

    需求:

    • 模拟某企业用户管理业务,需包含用户登录、用户删除、用户查询功能,并要统计每个功能的耗时。

    分析:

    1. 定义一个UserService表示用户业务接口,规定必须完成用户登录,用户删除,用户查询功能。
    2. 定义一个实现类UserServicelmpl实现UserService,并完成相关功能,且统计每个功能的耗时。
    3. 定义测试类,创建实现类对象,调用方法。

    优化代码之前:

    UserService.java

    /**
     * 用户服务接口
     */
    public interface UserService {
    
        /**
         * 登录
         *
         * @param username 用户名
         * @param password 密码
         * @return 是否登录成功
         */
        String login(String username, String password);
    
        /**
         * 删除用户
         */
        void deleteUsers();
    
        /**
         * 查询所有用户
         * @return 所有用户
         */
        String selectUsers();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    UserServiceImpl.java

    /**
     * 用户服务类
     */
    public class UserServiceImpl implements UserService {
    
        /**
         * 登录
         *
         * @param username 用户名
         * @param password 密码
         * @return 是否登录成功
         */
        @Override
        public String login(String username, String password) {
            // 开始时间
            long startTime = System.currentTimeMillis();
    
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            String result = "登录名或密码错误!";
    
            if ("admin".equals(username) && "123456".equals(password)) {
                result = "登录成功!";
            }
    
            // 结束时间
            long endTime = System.currentTimeMillis();
    
            System.out.println("login方法耗时:" + (endTime - startTime) / 1000.0 + "秒");
    
            return result;
        }
    
        /**
         * 删除用户
         */
        @Override
        public void deleteUsers() {
            // 开始时间
            long startTime = System.currentTimeMillis();
    
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            // 结束时间
            long endTime = System.currentTimeMillis();
    
            System.out.println("deleteUsers方法耗时:" + (endTime - startTime) / 1000.0 + "秒");
        }
    
        /**
         * 查询所有用户
         *
         * @return 所有用户
         */
        @Override
        public String selectUsers() {
    
            // 开始时间
            long startTime = System.currentTimeMillis();
    
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            // 结束时间
            long endTime = System.currentTimeMillis();
    
            System.out.println("selectUsers方法耗时:" + (endTime - startTime) / 1000.0 + "秒");
            return null;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    Test.java

    /**
     * 目标:掌握使用动态代理解决问题, 理解使用动态代理的优势
     * 用户服务类多个方法都需要统计执行时间, "统计执行时间代码重复", 使用代理来解决,只需要写一次"统计时间代码".
     */
    public class Test {
        public static void main(String[] args) {
            // 创建用户服务对象
            UserService userService = new UserServiceImpl();
    
            // 登录
            System.out.println(userService.login("admin", "123456"));
            // 删除用户
            userService.deleteUsers();
            // 查询所有用户
            userService.selectUsers();
            
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    执行结果:

    login方法耗时:1.0秒
    登录成功!
    deleteUsers方法耗时:1.0秒
    selectUsers方法耗时:1.0秒
    
    • 1
    • 2
    • 3
    • 4

    优化代码:

    UserServiceImpl.java

    /**
     * 用户服务类
     */
    public class UserServiceImpl implements UserService {
    
        /**
         * 登录
         *
         * @param username 用户名
         * @param password 密码
         * @return 是否登录成功
         */
        @Override
        public String login(String username, String password) {
    
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            String result = "登录名或密码错误!";
    
            if ("admin".equals(username) && "123456".equals(password)) {
                result = "登录成功!";
            }
    
            return result;
        }
    
        /**
         * 删除用户
         */
        @Override
        public void deleteUsers() {
    
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        /**
         * 查询所有用户
         *
         * @return 所有用户
         */
        @Override
        public String selectUsers() {
    
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return null;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    UserServiceProxy.java

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 用户服务代理类
     */
    public class UserServiceProxy {
    
        /**
         * 通过一个静态方法, 为用户业务对象返回一个代理对象
         */
        public static UserService getProxy(UserService userService) {
            return (UserService) Proxy.newProxyInstance(
                    userService.getClass().getClassLoader(),
                    userService.getClass().getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                            // 开始时间
                            long startTime = System.currentTimeMillis();
    
                            // 真正触发对象的行为执行
                            Object result = method.invoke(userService, objects);
    
                            // 结束时间
                            long endTime = System.currentTimeMillis();
    
                            System.out.println(method.getName() + ":" + (endTime - startTime) / 1000.0 + "秒");
    
                            return result;
                        }
                    }
            );
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    Test.java

    /**
     * 目标:掌握使用动态代理解决问题, 理解使用动态代理的优势
     * 用户服务类多个方法都需要统计执行时间, "统计执行时间代码重复", 使用代理来解决,只需要写一次"统计时间代码".
     */
    public class Test {
        public static void main(String[] args) {
    
            // 创建用户服务代理对象
            UserService userServiceProxy = UserServiceProxy.getProxy(new UserServiceImpl());
            // 登录
            System.out.println(userServiceProxy.login("admin", "123456"));
            // 删除用户
            userServiceProxy.deleteUsers();
            // 查询所有用户
            userServiceProxy.selectUsers();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    执行结果:

    login:1.0秒
    登录成功!
    deleteUsers:1.001秒
    selectUsers:1.001秒
    
    • 1
    • 2
    • 3
    • 4

    动态代理的优点:

    • 可以在不改变方法源码的情况下,实现对方法功能的增强,提高了代码的复用。

    • 简化了编程工作、提高了开发效率,同时提高了软件系统的可扩展性。

    • 可以被代理对象的所有方法做代理。

    • 非常的灵活,支持任意接口类型实现类对象做代理,也可以直接为接口本身做代理。

      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.lang.reflect.Proxy;
      
      /**
       * 用户服务代理类
       */
      public class UserServiceProxy {
      
          /**
           * 通过一个静态方法, 为用户业务对象返回一个代理对象
           */
          public static <T> T getProxy(T t) {
              return (T) Proxy.newProxyInstance(
                      t.getClass().getClassLoader(),
                      t.getClass().getInterfaces(),
                      new InvocationHandler() {
                          @Override
                          public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                              // 开始时间
                              long startTime = System.currentTimeMillis();
      
                              // 真正触发对象的行为执行
                              Object result = method.invoke(t, objects);
      
                              // 结束时间
                              long endTime = System.currentTimeMillis();
      
                              System.out.println(method.getName() + ":" + (endTime - startTime) / 1000.0 + "秒");
      
                              return result;
                          }
                      }
              );
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
  • 相关阅读:
    ELK之Logstash解析时间相差8h的问题
    Qt自定义日志输出
    JavaScript预备知识
    LeetCode笔记:Biweekly Contest 91
    51单片机入门——数模\模数转换
    Carla自动驾驶仿真八:两种查找CARLA地图坐标点的方法
    植物大战僵尸杂交版v2.1最新直装版,苹果+安卓+PC+防闪退工具+修改工具+高清工具+通关存档整合包更新
    AI绘画开源王者归来,Stable Diffusion 3.5 AI绘画模型开源发布
    数据结构之:链表
    LeetCode 第 310 场周赛
  • 原文地址:https://blog.csdn.net/weixin_42856871/article/details/127706432