• 反射机制(复习)


    反射机制

    定义

    动态调用类、对象的信息,方法和属性

    反射机制的功能

    • 在运行时判断任意一个对象所属的类
    • 在运行时构造任意一个类的对象
    • 在运行时判断任意一个类所具有的成员变量和方法
    • 在运行时获取泛型信息
    • 在运行时调用任意一个对象的成员变量和方法
    • 在运行时处理注解
    • 生成动态代理

    反射机制主要的API

    • java.lang.Class:代表一个类
    • java.lang.reflect.Method:代表类的方法
    • java.lang.reflect.Field:代表类的成员变量
    • java.lang.reflect.Constructor:代表类的构造器

    反射机制演示

    public class ReflectionTest {
        @Test
        public void test1() {
            // p普通方式创建对象,以及调用属性、方法
            Person p1 = new Person("Tom",21);
            p1.setName("张三");
            p1.methods1("aa");
            // 无法调用私有方法
            // p1.methods2();
            System.out.println(p1);
        }
        // 通过反射机制创建对象、调用属性、方法
        @Test
        public void test2() throws Exception{
            Class<Person> aClass = Person.class;
            // 通过反射调用构造器方法
            Constructor<Person> con1 = aClass.getConstructor(String.class, int.class);
            Person p1 = con1.newInstance("Mack", 21);
            System.out.println(p1);
    
            // 通过反射机制调用方法
            Method methods1 = aClass.getDeclaredMethod("methods1", String.class);
             methods1.invoke(p1,"哈哈哈");
    
             // 通过反射机制调用私有方法
            Method methods2 = aClass.getDeclaredMethod("methods2");
            methods2.setAccessible(true);
            methods2.invoke(p1);
    
            // 通过反射机制调用普通
            Field age = aClass.getDeclaredField("age");
            age.set(p1,22);
            System.out.println(p1);
    
             // 通过反射机制调用私有属性
            Field name = aClass.getDeclaredField("name");
            name.setAccessible(true);
            name.set(p1,"李四");
            System.out.println(p1);
    
    
        }
    }
     class Person {
        private String name;
        public int age ;
    
        public Person() {
        }
    
        public void methods1(String s) {
            System.out.println("我是一个public方法");
        }
        private void methods2(){
            System.out.println("我是一个私有方法");
        }
    
        @Override
        public String toString() {
            return "Persion{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        private Person(String name) {
            this.name = name;
        }
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public int getAge() {
            return 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
    • 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
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    疑问1 : 既然可以 new 对象为什么还要有 反射机制 ? 使用哪种方式呢?

    平常肯定是使用 new 的方式,但是在某些应用场景下,还是需要使用反射机制。比如:我们在编译期不确定要创建哪个对象,要执行哪个方法。就需要反射机制的动态调用。

    比如在 项目中,在项目启动时前端发送请求,这个时候后端就需要根据请求路径动态判断需要执行哪个 Servlet ,这就需要反射机制

    以下代码截取为 javaweb 书城的 反射机制代码:

    image-20221106151821384

    对 Class 的理解

    编写完代码后,由前端编译器对源代码文件编译成 .class 字节码文件。

    通过 类加载器 ClassLoader 将class文件加载到内存中去。

    这个被加载到内存中的类就被称为运行时类,这个类就是Class的一个实例

    Person p1 = new Person();

    p1 是 Person 的实例,而Person 是 Class的实例。(前提是编译运行之后)

    因此也可以说一个 Class实例对应一个运行时类

    Class实例获取的四种方式

    • 调用运行时类的属性: .class
    • 通过运行时类的对象,调用getClass()
    • 通过Class的静态方法,Class.forName(String classPath)
    • 通过类加载器中的 loadClass()
      @Test
        public void test1() throws ClassNotFoundException {
            // 第一种方式: 调用运行时类的属性
            Class<Person> clazz = Person.class;
            System.out.println(clazz);
            //第二种方式:通过运行时类的对象,调用 getClass
            Person person = new Person();
            Class clazz1 = person.getClass();
            System.out.println(clazz1);
            //第三种方式:调用Class的静态方法 forName
            Class<?> clazz2 = Class.forName("Person");
            System.out.println(clazz2);
            // 第四种方式:通过ClassLoader的 loadClass方法
            ClassLoader classLoader = ReflectionTest2.class.getClassLoader();
            Class<?> clazz3 = classLoader.loadClass("Person");
            System.out.println(clazz3);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Class 对应内存结构说明

    哪些类型可以有Class对象?

    (1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
    (2)interface:接口
    (3)[]:数组
    (4)enum:枚举
    (5)annotation:注解@interface
    (6)primitivetype:基本数据类型
    (7)void

        @Test
        public void test4() {
            Class s1 = Object.class;
            Class s2 = Comparable.class;
            Class s3 = String[].class;
            Class s4 = int[][].class;
            Class s5 = ElementType.class;
            Class s6 = Override.class;
            Class s7 = int.class;
            Class s8 = void.class;
            Class s9 = Class.class;
    
            int[] a = new int[10];
            int[] b = new int[100];
            Class s10 = a.getClass();
            Class s11 = b.getClass();
            // 只要数组的元素类型与维度一样,就是同一个Class
            System.out.println(s10 == s11);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    加载Properties文件的俩种方式

    第一种方式:使用 FileInputStream方式

        @Test
        public void test() throws Exception{
            // 第一种方式: 文件路径默认在module下
            Properties prop = new Properties();
            FileInputStream is = new FileInputStream("jdbc.properties");
            prop.load(is);
    
            String name = prop.getProperty("name");
            String age = prop.getProperty("age");
            System.out.println(name + "  "+age);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    第二种方式:使用CLassLoader加载

        @Test
        public void test1() throws IOException {
            // 第二种方式: 默认路径在 src 下
            Properties prop = new Properties();
            ClassLoader classLoader = this.getClass().getClassLoader();
            InputStream asStream = classLoader.getResourceAsStream("jdbc1.properties");
            prop.load(asStream);
    
            String name = prop.getProperty("name");
            String age = prop.getProperty("age");
            System.out.println(name + "  " + age);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    俩种方式的区别

    第一中方式默认加载的文件路径在 此module 下

    第二种方式默认加载文件路径在 src 目录下

    第一种方式也可以加载src目录下,增加目录 路径就行了:

    FileInputStream is = new FileInputStream("src/jdbc1.properties");
    
    • 1

    调用运行时类的结构

    调用运行时类的指定属性

    image-20221106174034649

    俩个方法,第一个方法要求类的属性权限至少是 public,第二个要求属性权限至少是private,一般都使用第二种。

        // 获取运行时类的属性
        @Test
        public void test() throws Exception {
            Class<Person> clazz = Person.class;
            // 创建运行时类对象
            Person person = clazz.newInstance();
            // 获取指定属性
            Field name = clazz.getDeclaredField("name");
            // 打破封装
            name.setAccessible(true);
            // 设置属性
            // 第一个参数为属性的对象,第二个参数为属性值
            name.set(person,"张三");
            // 获取属性
            Object name1 =name.get(person);
            System.out.println(name1);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    调用运行时类指定的方法

    image-20221106174952819

    使用情况和属性的一样

        @Test
        public void test1() throws Exception {
            Class<Person> clazz = Person.class;
            Person person = clazz.newInstance();
            // 获取指定方法: 第一个参数为方法名,第二个参数为方法形参类型
            Method methods1 = clazz.getDeclaredMethod("methods2",String.class);
            methods1.setAccessible(true);
            // invoke:执行方法,invoke方法的返回值是调用方法的返回值
            methods1.invoke(person,"李四");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    调用运行时类指定的构造器

    image-20221106175906863

    和上面一样不写了

        // 调用运行时类的指定构造器
        @Test
        public void test2() throws Exception {
            Class<Person> clazz = Person.class;
            Person person = clazz.newInstance();
            Constructor<Person> constructor = clazz.getDeclaredConstructor(String.class);
            constructor.setAccessible(true);
            Person person1 = constructor.newInstance("李四");
            System.out.println(person1);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    反射的应用:动态代理

    代理模式的原理

    使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

    • 静态代理:特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能

    • 动态代理:是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

    • 动态代理相比于静态代理的优点

      抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。

    静态代理演示:

    public class StaticProxyTest {
    
        interface ClothFactory {
            void createCloths();
        }
    
        // 代理类
    static   class ProxyClothFactory implements ClothFactory{
            private ClothFactory clothFactory;
    
            public ProxyClothFactory(ClothFactory clothFactory) {
                this.clothFactory = clothFactory;
            }
    
            @Override
            public void createCloths() {
                System.out.println("代理工厂的一些准备工作");
                clothFactory.createCloths();
                System.out.println("代理工厂的一些后序工作");
            }
        }
        // 被代理类
      static  class NikeClothFactory implements ClothFactory{
    
            @Override
            public void createCloths() {
                System.out.println("NIKE 生成了一批衣服");
            }
        }
    
        public static void main(String[] args) {
            NikeClothFactory nikeClothFactory = new NikeClothFactory();
            ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nikeClothFactory);
            proxyClothFactory.createCloths();
        }
    }
    
    • 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

    输出结果:

    代理工厂的一些准备工作
    NIKE 生成了一批衣服
    代理工厂的一些后序工作
    
    • 1
    • 2
    • 3

    动态代理演示:

    动态代理:通过动态生成代理类对象,去调用被代理类中的方法。

    一般代理类就是一个接口,只提供方法,不提供具体的服务。在被代理类中提供服务。

    • 如何通过加载到内存中的被代理类,动态生成代理类及其对象?
    • 通过代理对象调用方法时,如何动态的去调用被代理类的方法?
    /**
     *
     * Author: YZG
     * Date: 2022/11/6 18:25
     * Description: 动态代理:通过动态生成代理类对象调用被代理类中的方法
     */
    public class DynamicProxyTest {
        public static void main(String[] args) {
            Man man = new Man();
            // 代理类的对象
            Human human = (Human) Man.ProxyFactory.getInstance(man);
            human.eat("麻辣烫");
            human.sleep(8);
        }
    
        // 代理类
        interface Human {
            void eat(String food);
    
            void sleep(int hours);
        }
    
        // 被代理类
        static class Man implements Human {
    
            @Override
            public void eat(String food) {
                System.out.println("男人吃了 " + food);
            }
    
            @Override
            public void sleep(int hours) {
                System.out.println("男人睡了 " + hours + "小时");
            }
    
            // 动态代理类
            static class ProxyFactory {
                /**
                 * @description 调用此方法 生成一个代理类的对象
                 * @date 2022/11/6 19:06
                 * @param obj 被代理类的对象
                 * @return java.lang.Object
                 */
                public static Object getInstance(Object obj) {
                    return Proxy.newProxyInstance(
                        	obj.getClass().getClassLoader(),
                            obj.getClass().getInterfaces(),
                            /**
                             * @description 动态调用被代理类中的方法。需要实现  InvocationHandler 接口
                             * @date 2022/11/6 19:07
                             * @param obj 被代理类的对象
                             * @return java.lang.Object
                             */
                            (Object proxy, Method method, Object[] args) -> {
                                // 动态调用被代理类对象中的方法
                                return method.invoke(obj, args);
                            });
                }
            }
        }
    
    }
    
    • 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

    关于 Proxy.newProxyInstance 的三个参数: 被代理类的类加载器,被代理类实现的接口(代理类)、需要实现指定的接口

    image-20221106192321497

  • 相关阅读:
    Vue3核心知识点
    Hadoop HA集群全是standBy解决办法
    公司金融期末考试题
    uniapp实现微信小程序调用云函数【vue3】
    概论_第2章随机变量及其概率分布__离散型随机变量之二项分布
    使用 FFmpeg 将视频转换为 GIF 动画的技巧
    计算机毕业设计JAVA网上商城购物系统mybatis+源码+调试部署+系统+数据库+lw
    用动图详细讲解——栈
    [附源码]计算机毕业设计JAVA闲置物品线上交易系统
    hive-on-spark
  • 原文地址:https://blog.csdn.net/aetawt/article/details/127918133