• 2-Java进阶知识总结-8-反射-动态代理


    反射

    基本概念

    什么是反射

    专业的解释(了解一下):

    • 是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
    • 对于任意一个对象,都能够调用它的任意属性和方法;
    • 这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

    通俗的理解(掌握)

    • 利用反射创建的对象可以无视修饰符调用类里面的内容

    • 可以跟配置文件结合起来使用,把要创建的对象信息和方法写在配置文件中

      • 读取到什么类,就创建什么类的对象
      • 读取到什么方法,就调用什么方法
      • 此时当需求变更的时候不需要修改代码,只要修改配置文件即可

    字节码文件和字节码文件对象的理解

    概念理解
    java文件编写的java代码,后缀为.java
    字节码文件编译java文件后生成的class文件(在硬盘上真实存在)
    字节码文件对象class文件加载到内存后,虚拟机自动创建出来的对象(对象包含:构造方法、成员变量、成员方法)

    反射要获取的是字节码文件对象,这个对象在内存中是唯一的

    获取【字节码文件对象】的三种方法

    获取手段
    根据Class类的静态方法获取(用的最多)Class.forName(“全类名”)
    根据要获取类名的class属性获取类名.class
    根据类的实例对象获取实例对象.getClass()
    // 1、根据Class类的静态方法获取(用的最多)
    // 全类名 = 包名 + 类名
    Class clazz1 = Class.forName("com.itheima.reflectdemo.Student");
    
    // 2、根据要获取类名的class属性获取
    Class clazz2 = Student.class;
    
    System.out.println(clazz1 == clazz2);//true,因为class文件在硬盘中是唯一的,所以,当这个文件加载到内存之后产生的对象也是唯一的
    
    // 3、根据类的实例对象获取
    Student s = new Student();
    Class clazz3 = s.getClass();
    System.out.println(clazz1 == clazz2);//true
    System.out.println(clazz2 == clazz3);//true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    利用获取到的字节码文件对象,可以获取类的相关信息:构造方法、成员变量、成员方法等

    【根据字节码文件对象】获取构造方法

    获取构造方法

    方法名说明
    Constructor[] getConstructors()获得所有的构造(只能public修饰)
    Constructor[] getDeclaredConstructors()获得所有的构造(包含private修饰)
    Constructor getConstructor(Class… parameterTypes)获取指定构造(只能public修饰)
    Constructor getDeclaredConstructor(Class… parameterTypes)获取指定构造(包含private修饰)
    public class ReflectDemo2 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
            //1、获得整体(class字节码文件对象)
            Class clazz = Class.forName("com.itheima.reflectdemo.Student");
    
    
            //2-1、获取所有(public)构造方法
            Constructor[] constructors1 = clazz.getConstructors();
            for (Constructor constructor : constructors1) {
                System.out.println(constructor);
            }
            System.out.println("=======================");
            
            //2-2、获取所有构造方法(共有和私有的都算上)
            Constructor[] constructors2 = clazz.getDeclaredConstructors();
            for (Constructor constructor : constructors2) {
                System.out.println(constructor);
            }
            System.out.println("=======================");
            
            //2-3、获取指定的空参构造(public)
            Constructor con1 = clazz.getConstructor();
            System.out.println(con1);
            
            //2-4、获取指定的带参构造(public)
            Constructor con2 = clazz.getConstructor(String.class,int.class);
            System.out.println(con2);
            System.out.println("=======================");
            
            //2-5、获取指定的空参构造(共有+私有都可以获取到)
            Constructor con3 = clazz.getDeclaredConstructor();
            System.out.println(con3);
    		System.out.println(con3 == con1); // false,每一次获取构造方法对象的时候,都会新new一个。
    
            //2-6、获取指定的空参构造(共有+私有都可以获取到)
            Constructor con4 = clazz.getDeclaredConstructor(String.class);
            System.out.println(con4);
        }
    }
    
    • 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

    根据获取到的构造方法创建对象

    根据空参构造方法创建对象

    //1.获取整体的字节码文件对象
    Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
    //2.获取空参的构造方法
    Constructor con = clazz.getConstructor();
    //3.利用空参构造方法创建对象
    Student stu = (Student) con.newInstance();
    System.out.println(stu);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    根据带参构造方法创建对象

    • 如果获取到的构造方法是私有的,需要临时修改访问权限,否则无法使用该构造方法创建对象
    //1.获取整体的字节码文件对象
    Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
    //2.获取有参构造方法
    Constructor con = clazz.getDeclaredConstructor(String.class, int.class);
    //3.临时修改构造方法的访问权限(暴力反射)
    con.setAccessible(true);
    //4.直接创建对象
    Student stu = (Student) con.newInstance("zhangsan", 23);
    System.out.println(stu);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    【根据字节码文件对象】获取成员变量

    获取成员变量

    方法名说明
    Field[] getFields()返回所有成员变量对象的数组(只能拿public的)
    Field[] getDeclaredFields()返回所有成员变量对象的数组,存在就能拿到
    Field getField(String name)返回单个成员变量对象(只能拿public的)
    Field getDeclaredField(String name)返回单个成员变量对象,存在就能拿到
    public class ReflectDemo4 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
            //获取成员变量对象
    
            //1.获取字节码文件对象
            Class clazz = Class.forName("com.itheima.reflectdemo.Student");
    
            //2.获取成员变量的对象(Field对象)只能获取public修饰的
            Field[] fields1 = clazz.getFields();
            for (Field field : fields1) {
                System.out.println(field);
            }
    
            System.out.println("===============================");
    
            //获取成员变量的对象(public + private)
            Field[] fields2 = clazz.getDeclaredFields();
            for (Field field : fields2) {
                System.out.println(field);
            }
    
            System.out.println("===============================");
            //获得单个成员变量对象
            //如果获取的属性是不存在的,那么会报异常
            //Field field3 = clazz.getField("aaa");
            //System.out.println(field3);//NoSuchFieldException
    
            Field field4 = clazz.getField("gender");
            System.out.println(field4);
    
            System.out.println("===============================");
            
            //获取单个成员变量(私有)
            Field field5 = clazz.getDeclaredField("name");
            System.out.println(field5);
        }
    }
    
    • 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

    根据获取到的成员变量(Field):获取实例化对象的成员值、修改实例化对象的成员值

    方法说明
    void set(Object obj, Object value)赋值
    Object get(Object obj)获取值
    public class ReflectDemo5 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    		// 实例化对象
            Student s = new Student("zhangsan",23,"广州");
            Student ss = new Student("lisi",24,"北京");
    
            //1.获取字节码文件对象
            Class clazz = Class.forName("com.itheima.reflectdemo.Student");
    
            //2.获取name成员变量
            Field field = clazz.getDeclaredField("name");	//field就表示name这个属性的对象
            field.setAccessible(true);						//临时修改访问权限
    
            //3.修改实例化对象name属性的值
            field.set(s,"wangwu");		// 参数1:实例化对象;参数2:要将属性设置的值
    
            //4、获取实例化对象name属性的值
            String result = (String)field.get(s);	// 参数1:实例化对象
    
            System.out.println(s);
            System.out.println(ss);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    【根据字节码文件对象】获取成员方法

    获取成员方法

    方法名说明
    Method[] getMethods()返回所有成员方法对象的数组(只能拿public的)
    Method[] getDeclaredMethods()返回所有成员方法对象的数组,存在就能拿到
    Method getMethod(String name, Class… parameterTypes)返回单个成员方法对象(只能拿public的)
    Method getDeclaredMethod(String name, Class… parameterTypes)返回单个成员方法对象,存在就能拿到
    public class ReflectDemo6 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
            //1.获取class对象
            Class<?> clazz = Class.forName("com.itheima.reflectdemo.Student");
    
            //2.获取方法
            //getMethods可以获取父类中public修饰的方法
            Method[] methods1 = clazz.getMethods();
            for (Method method : methods1) {
                System.out.println(method);
            }
            System.out.println("===========================");
            
            //获取所有的方法(包含私有)
            //但是只能获取自己类中的方法
            Method[] methods2 = clazz.getDeclaredMethods();
            for (Method method : methods2) {
                System.out.println(method);
            }
    
            System.out.println("===========================");
            
            //获取指定的方法(空参)
            Method method3 = clazz.getMethod("sleep");
            System.out.println(method3);
    
            Method method4 = clazz.getMethod("eat",String.class);	// 参数1:方法名称;后续参数:方法的参数类型
            System.out.println(method4);
    
            //获取指定的私有方法
            Method method5 = clazz.getDeclaredMethod("playGame");
            System.out.println(method5);
        }
    }
    
    • 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

    运行获取到的成员方法

    package com.itheima.a02reflectdemo1;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class ReflectDemo6 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            //1.获取字节码文件对象
            Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
    		
            //2.获取一个对象,需要用这个对象去调用方法
            Student s = new Student();
            
            //3.获取一个指定的方法
            Method eatMethod = clazz.getMethod("eat",String.class); // 参数1:方法名;参数后续:参数列表
            
            //运行
            //eatMethod:方法对象
            //s:执行方法的实例化对象
            //"重庆小面":方法的具体参数
            //result:方法的返回值
            String result = (String) eatMethod.invoke(s, "重庆小面");
            System.out.println(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

    动态代理

    什么是动态代理?

    动态代理就是:

    在程序运行期间,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术

    • 这句话解释了动态代理的一个好处:可以给目标对象的方法进行增强

    在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法

    • 这句话解释了动态代理的另一个好处:增强的手段是无侵入式的,因为目标对象不变

    可以理解为运行期间,对目标对象中某方法的动态拦截,在拦截某方法的前后执行相应的功能操作

    动态代理三要素

    • 目标对象:真正干活的对象
    • 代理对象:用来代理目标对象的对象
    • 目标方法:代理对象中要调用的目标对象的方法,可以在代理对象中对该方法完成增强

    示例:请明星演唱表演

    需求:

    • 明星会唱歌、跳舞等才艺表演
    • 一些地方或人想请明星进行表演,明星助理就负责安排相关活动

    涉及到的动态代理三要素:

    • 目标对象:明星
    • 代理对象:明星助理
    • 目标方法:唱歌、跳舞

    目标对象:明星

    • 明星接口:接口里面的方法是明星应该具备的基本才艺
    public interface Star {
        //唱歌
        public abstract String sing(String name);
        //跳舞
        public abstract void dance();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 明星对象:一个明星必须会基本明星该有的所有才艺(明星对象需要实现明星接口里面所有的方法)
    public class BigStar implements Star {
        private String name;
    
    	// 空参、带参构造方法、tostring()方法:省略
    
        //唱歌
        @Override
        public String sing(String name){
            System.out.println(this.name + "正在唱" + name);
            return "谢谢";
        }
    
        //跳舞
        @Override
        public void dance(){
            System.out.println(this.name + "正在跳舞");
        }
        
        // 明星的其他才能:省略
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    代理对象:助理协助明星,完善明星表演的前后期工作

    // 动态代理的工具类
    public class ProxyUtil {
    	// 创建明星对象的代理类
        public static Star createProxy(BigStar bigStar){
           // java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法,该方法参数列表、返回值内容如下:
           // public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
           // 参数一:用于指定用哪个类加载器,去加载生成的代理类
           // 参数二:指定接口,目标对象的接口
           // 参数三:用来指定生成的代理对象要干什么事情:匿名内部类写法
            Star star = (Star) Proxy.newProxyInstance(
                    bigStar.getclass().getClassLoader(),
                    bigStar.getClass().getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            /*
                            * 参数一:代理对象
                            * 参数二:目标对象要运行的方法,sing
                            * 参数三:调用sing方法时,传递的参数列表
                            * */
                            if("sing".equals(method.getName())){
                                System.out.println("准备话筒,收钱");
                            }else if("dance".equals(method.getName())){
                                System.out.println("准备场地,收钱");
                            }
                            //去找大明星开始唱歌或者跳舞
                            //代码的表现形式:调用大明星里面唱歌或者跳舞的方法
                            return method.invoke(bigStar,args);
                        }
                    }
            );
            return star;
        }
    }
    
    • 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

    完成明星演唱工作

    public class Test {
        public static void main(String[] args) {
            //1. 获取代理的对象
            BigStar bigStar = new BigStar("鸡哥");
            Star proxy = ProxyUtil.createProxy(bigStar);
    
            //2. 代理调用唱歌的方法
            String result = proxy.sing("只因你太美");
            System.out.println(result);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    【扩展】方法拦截

    在这个故事中,经济人作为代理,如果别人让邀请大明星去唱歌,打篮球,经纪人就允许。但是如果别人让大明星去扫厕所,经纪人就要加以拦截

    public class ProxyUtil {
        public static Star createProxy(BigStar bigStar){
            Star star = (Star) Proxy.newProxyInstance(
                    bigStar.getclass().getClassLoader(),
                    bigStar.getClass().getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            if("cleanWC".equals(method.getName())){
                                System.out.println("拦截,不调用大明星的方法");
                                return null;
                            }
                            //如果是其他方法,正常执行
                            return method.invoke(bigStar,args);
                        }
                    }
            );
            return star;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    示例:增强List集合的add、remove方法

    public class MyProxyDemo1 {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            
            List proxyList = (List) Proxy.newProxyInstance(
                   list.getclass().getClassLoader(),
                   list.getclass().getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            //对add方法做一个增强,统计耗时时间
                            if (method.getName().equals("add")) {
                                long start = System.currentTimeMillis();
                                method.invoke(list, args);
                                long end = System.currentTimeMillis();
                                System.out.println("耗时时间:" + (end - start));
                                return true;	//需要进行返回,返回值要跟真正增强或者拦截的方法保持一致
                            }else if(method.getName().equals("remove") && args[0] instanceof Integer){
                                System.out.println("拦截了按照索引删除的方法");
                                return null;
                            }else if(method.getName().equals("remove")){
                                System.out.println("拦截了按照对象删除的方法");
                                return false;
                            }else{
                                System.out.println("如果当前调用的是其他方法,我们既不增强,也不拦截")
                                method.invoke(list,args);
                                return null;
                            }
                        }
                    }
            );
    
            proxyList.add("aaa");
            proxyList.add("bbb");
            proxyList.add("ccc");
            proxyList.add("ddd");
    
            proxyList.remove(0);
            proxyList.remove("aaa");
    
    
            //打印集合
            System.out.println(list);
        }
    }
    
    • 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

    动态代理的注意事项

    • 搞清楚:目标对象、代理对象、目标方法
    • 在代理对象中增强目标方法的时候:要注意返回值与目标对象的返回值结果保持一致
    • 在代理对象拦截目标对象方法的时候:两者返回值就没必要一致了
  • 相关阅读:
    解析器模式(Parser Pattern)
    【译】什么时候使用 Spring 6 JdbcClient
    黑马全套Java教程(十一)
    【自然语言处理】关系抽取 —— GDPNet 讲解
    CAN bus总线静电保护方案
    JAVA计算机毕业设计音乐社交平台设计Mybatis+系统+数据库+调试部署
    第5章——以程序方式处理MySQL数据表的数据
    【论文笔记】Content-based Unrestricted Adversarial Attack
    MyBatis-Plus批量插入方法saveBatch
    java计算机毕业设计基于springboot 垃圾回收分类网站
  • 原文地址:https://blog.csdn.net/qq_45445505/article/details/134038354