【Java基础】Java 反射知识及实战

简介: Class类的实例表示java应用运行时的类或接口,每个java类运行时都在JVM里表现为一个class对象,可通过类名.class、类型.getClass()、Class.forName("类名")等方法获取class对象。类的加载流程可在另一篇文章查看。

在了解反射前,我们先要知道一些相关知识

一、Class类

Class类的实例表示java应用运行时的类或接口,每个java类运行时都在JVM里表现为一个class对象,可通过类名.class、类型.getClass()、Class.forName("类名")等方法获取class对象。

类的加载流程可在另一篇文章查看。

二、反射的定义

Java 反射(Reflection)是指在运行时动态地获取类的信息、调用方法、获取属性等,从而实现运行时的类型检查、动态代码生成、设置和操作类的信息等功能的一种机制。(动态获取的信息以及动态调用对象)。有几个特点:

1、对于任意一个类,都能够知道这个类的所有属性和方法;

2、对于任意一个对象,都能够调用它的任意一个方法和属性;

3、可以构建任意一个类的对象。

通俗的说,反射就是把java类中的各种成分映射成一个个的Java对象。

一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。

需要注意的是:

反射机制的使用可能会带来一些性能上的损失,因为它需要在运行时进行类型检查和方法调用。此外,反射机制还可能会导致安全漏洞,因为它可以执行任意代码。因此,在实际开发中,需要谨慎使用反射,并且根据具体情况选择是否使用反射。

三、反射的实现原理及类介绍

基于java虚拟机的动态加载并依赖于 Class 类,java虚拟机在运行时动态的加载类,并生成对应的class对象,这个对象包含了类的所有信息,可通过class对象获取类的信息,进而实现对类的操作。

使用时大致可分为几个步骤:

反射获取类实例 -> 反射获取方法 -> 调用method.invoke()方法

反射获取类实例 -> 反射获取属性 -> 操作

1、获取 Class 对象:通过 Class.forName() 方法获取要反射的类的 Class 对象。
2、获取 Method 对象:通过getMethod() 等方法获取要调用的方法的 Method 对象。
3、调用方法:method.invoke() 方法调用该方法(最终是由jvm执行invoke0()执行),可获取返回值。
4、获取 Field 对象:通过getField() 等方法获取要访问的属性的 Field 对象。
5、设置属性值:通过 Field.setAccessible(true) 方法将属性设置为可访问。然后可以通过 Field.get(object) 方法获取该属性的值。

image.gif

下面我们一步步分析一波。

3.1 Class类对象的获取

在类加载的时候,jvm会创建一个class对象,获取class对象的方式的主要有三种:

    • 根据类名:类名.class
    • 根据对象:对象.getClass()
    • 根据全限定类名:Class.forName(全限定类名)
    Class clz = String.class;
    //类的全路径名
    Class clz = Class.forName("java.lang.String");
    //使用类对象的 getClass() 方法
    String str = new String("Hello");
    Class clz = str.getClass();
    • image.gif

    Class类的方法

    方法名

    说明

    forName()

    (1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。

    (2)为了产生Class引用,调用forName()会立即就进行初始化。

    Object-getClass()

    获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。

    getName()

    取全限定的类名(包括包名),即类的完整名字。

    getSimpleName()

    获取类名(不包括包名)

    getCanonicalName()

    获取全限定的类名(包括包名),大多数情况下和getName一样

    isInterface()

    判断Class对象是否是表示一个接口

    getInterfaces()

    返回Class对象数组,表示Class对象所引用的类所实现的所有接口。

    getSupercalss()

    返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。

    newInstance()

    返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。

    getFields()

    获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。

    getDeclaredFields

    获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。

    首先调用了 java.lang.Class 的静态方法forName()获取反射获取类信息,调用到new instance,

    newInstance() 主要做了三件事:

      1.    权限检测,如果不通过直接抛出异常;
      2.    查找无参构造器,并将其缓存起来;
      3.    调用具体方法的无参构造方法,生成实例并返回;

      然后是获取构造器的过程:

        1. 先获取所有的constructors, 然后通过进行参数类型比较;
        2. 找到匹配后,通过 ReflectionFactory copy一份constructor返回;
        3. 否则抛出 NoSuchMethodException;

        3.2 Constructor类及其用法

        Constructor 类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象。

        方法返回值

        方法名称

        方法说明

        static Class

        forName(String className)

        返回与带有给定字符串名的类或接口相关联的 Class 对象。

        Constructor

        getConstructor(Class... parameterTypes)

        返回指定参数类型、具有public访问权限的构造函数对象

        Constructor[]

        getConstructors()

        返回所有具有public访问权限的构造函数的Constructor对象数组

        Constructor

        getDeclaredConstructor(Class... parameterTypes)

        返回指定参数类型、所有声明的(包括private)构造函数对象

        Constructor[]

        getDeclaredConstructors()

        返回所有声明的(包括private)构造函数对象

        T

        newInstance()

        调用无参构造器创建此 Class 对象所表示的类的一个新实例。

        方法返回值

        方法名称

        方法说明

        Class

        getDeclaringClass()

        返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数)

        Type[]

        getGenericParameterTypes()

        按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型。

        String

        getName()

        以字符串形式返回此构造方法的名称。

        Class[]

        getParameterTypes()

        按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型

        T

        newInstance(Object... initargs)

        使用此 Constructor对象表示的构造函数来创建新实例

        String

        toGenericString()

        返回描述此 Constructor 的字符串,其中包括类型参数。

        3.3 Field类及其用法

        Field 表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)。

        Field 类提供有关类或接口的单个字段的信息,以及对它的动态访问权限。

        通过Class类的提供的方法来获取代表字段信息的Field对象

        方法返回值

        方法名称

        方法说明

        Field

        getDeclaredField(String name)

        获取指定name名称的(包含private修饰的)字段,不包括继承的字段

        Field[]

        getDeclaredFields()

        获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段

        Field

        getField(String name)

        获取指定name名称、具有public修饰的字段,包含继承字段

        Field[]

        getFields()

        获取修饰符为public的字段,包含继承字段

        方法返回值

        方法名称

        方法说明

        void

        set(Object obj, Object value)

        将指定对象变量上此 Field 对象表示的字段设置为指定的新值。

        Object

        get(Object obj)

        返回指定对象上此 Field 表示的字段的值

        Class

        getType()

        返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。

        boolean

        isEnumConstant()

        如果此字段表示枚举类型的元素则返回 true;否则返回 false

        String

        toGenericString()

        返回一个描述此 Field(包括其一般类型)的字符串

        String

        getName()

        返回此 Field 对象表示的字段的名称

        Class

        getDeclaringClass()

        返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段

        void

        setAccessible(boolean flag)

        将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性

        3.4 Method类及其用法

        Method 表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private)。

        Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。

        方法返回值

        方法名称

        方法说明

        Method

        getDeclaredMethod(String name, Class... parameterTypes)

        返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。

        Method[]

        getDeclaredMethods()

        返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。

        Method

        getMethod(String name, Class... parameterTypes)

        返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。

        Method[]

        getMethods()

        返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。

        方法返回值

        方法名称

        方法说明

        Object

        invoke(Object obj, Object... args)

        对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。

        Class

        getReturnType()

        返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法的返回类型

        Type

        getGenericReturnType()

        返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象,也是方法的返回类型。

        Class[]

        getParameterTypes()

        按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。即返回方法的参数类型组成的数组

        Type[]

        getGenericParameterTypes()

        按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的,也是返回方法的参数类型

        String

        getName()

        以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称

        boolean

        isVarArgs()

        判断方法是否带可变参数,如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false。

        String

        toGenericString()

        返回描述此 Method 的字符串,包括类型参数。

        获取方法也一样,

           1. 获取所有方法列表;

           2. 根据方法名称和方法列表,选出符合要求的方法;

           3. 如果没有找到相应方法,抛出异常,否则返回对应方法;

        后面调取invoke时,是通过 MethodAccessor 进行调用的,而 MethodAccessor 是个接口,在第一次时调用 acquireMethodAccessor() 进行新创建。

        进行 ma.invoke(obj, args); 调用时,调用 DelegatingMethodAccessorImpl.invoke();

        最后被委托到 NativeMethodAccessorImpl.invoke()

        四、常用使用

        我们用代码来举例

        package com.test.reflect;
        public class User {
            private String userName = "无名氏";
            public int age;
            public String tag = "善";
            private ThingsAttribute attribute;
            public User() {
                attribute = new ThingsAttribute();
            }
            public User(String userName) {
                this.userName = userName;
            }
            public User(int age) {
                this.age = age;
                attribute = new ThingsAttribute();
            }
            public User(int age, String userName) {
                super();
                this.userName = userName;
                this.age = age;
                attribute = new ThingsAttribute();
            }
            public String getUserName() {
                return userName;
            }
            public void setUserName(String userName) {
                this.userName = userName;
            }
            public int getAge() {
                return age;
            }
            public void setAge(int age) {
                this.age = age;
            }
            private String getTag() {
                return tag;
            }
            public void setTag(String tag) {
                this.tag = tag;
            }
            public void draw(int count, String name) {
                System.out.println("draw " + name + ",count=" + count);
            }
            @Override
            public String toString() {
                return "User{" +
                        "userName='" + userName + '\'' +
                        ", age=" + age +
                        ", tag='" + tag + '\'' +
                        ", attribute=" + attribute +
                        '}';
            }
        }

        image.gif

        4.1 获取需要反射的class对象

        Class clz = String.class;
        //类的全路径名
        Class clz = Class.forName("java.lang.String");
        //使用类对象的 getClass() 方法
        String str = new String("Hello");
        Class clz = str.getClass();

        image.gif

        4.2 通过反射创建对象

        /**
                 * 2、通过反射创建类对象
                 *  无参数构造方法
                 */
                //第一种方法,通过 Class 对象的 newInstance() 方法,
                //  Class 对象则只能使用默认的无参数构造方法
                User user = (User) clazz.newInstance();
                user.setAge(20);
                user.setUserName("盐蠢,30岁");
                System.out.println(user);
                //第二种:通过 Constructor 对象的 newInstance() 方法
                Constructor cs2 = clazz.getConstructor();
                user = (User) cs2.newInstance();
                user.setAge(30);
                System.out.println(user);
                /**
                 * 2、通过反射创建类对象
                 *    带参数构造方法
                 *    通过 Constructor
                 */
                // 获取带String参数的public构造函数
                Constructor cs1 = clazz.getConstructor(String.class);
                //创建User
                user= (User) cs1.newInstance("盐蠢");
                user.setAge(22);
                System.out.println("user1:"+user);

        image.gif

        4.3 获取构造方法

        /**
                 * 3、获取构造方法
                 */
                // 获取带String参数的public构造函数
                cs1 =clazz.getConstructor(String.class);
                // 取得指定带int和String参数构造函数,该方法是私有构造private
                cs1=clazz.getDeclaredConstructor(int.class, String.class);
                // 获取所有构造包含private
                Constructor<?> cons[] = clazz.getDeclaredConstructors();
                // 获取所有public构造
                cons = clazz.getConstructors();

        image.gif

        4.4 获取方法 及 方法执行

        /**
                 * 4、获取方法 及 方法执行
                 */
                //根据参数获取public的Method,包含继承自父类的方法
                Method method = clazz.getMethod("draw", int.class, String.class);
                //获取所有public的方法:
                Method[] methods =clazz.getMethods();
                //获取当前类的方法包含private,
                Method method1 = clazz.getDeclaredMethod("getTag");
                //获取当前类的所有方法包含private,不 包含父类的方法
                Method[] methods1=clazz.getDeclaredMethods();
                /*
                 * 通过Method对象的invoke(Object obj,Object... args)方法调用
                 */
                method.invoke(user, 1, "x");
                //修改私有方法的访问标识
                method1.setAccessible(true);
                String result = (String) method1.invoke(user);
                System.out.println(result);

        image.gif

        4.5 获取属性 及 替换

        /**
                 * 5、获取属性
                 */
                //获取public 修饰的 指定字段名称的Field类,包含父类字段
                Field field = clazz.getField("age");
                //获取指定字段名称的Field类,(包括private),注意 不 包含父类的字段
                Field field2 = clazz.getDeclaredField("userName");
                //获取所有修饰符为public的字段,包含父类字段
                Field fields[] = clazz.getFields();
                //获取当前类所字段(包含private字段),注意 不 包含父类的字段
                Field fields2[] = clazz.getDeclaredFields();
                /**
                 * 6、修改属性值,或者说给内部属性赋值(替换)
                 * set(Object obj, Object value),用于设置字段的值,
                 * get(Object obj)则是获取字段的值
                 * 被final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。
                 */
                field.set(user, 34);
                // 因为是私有属性,必须设置其可访问性
                field2.setAccessible(true);
                field2.set(user, "盐蠢");
                //获取public 修饰的 指定字段名称的Field类,包含父类字段
                Field field3 = clazz.getField("tag");
                field3.set(user, "小白眼、gameover");
                //获取public 修饰的 指定字段名称的Field类,包含父类字段
                Field field4 = clazz.getDeclaredField("attribute");
                ThingsAttribute attribute = new ThingsAttribute();
                attribute.addAttribute("变坏了");
                attribute.addAttribute("因果轮回");
                /**
                 * 替换内部变量
                 */
                // 因为是私有属性,必须设置其可访问性
                field4.setAccessible(true);
                field4.set(user, attribute);
                System.out.println("after setField: "+user);

        image.gif

        Field 中有个get()方法,返回指定对象上此 Field 表示的字段的值

        如下图

        image.gif编辑

        4.6 实际使用举例

        案例一:Toast WindowManager$BadTokenException

        tips:主要针对于Android7.x。具体分析过程不再这里讨论,大家可自行搜索学习。

        直接上代码

        private static void hook(Toast toast) {
                if (Build.VERSION.SDK_INT != Build.VERSION_CODES.N_MR1) {
                    return;
                }
                try {
                    // 1、获取Class对象的引用
                    Class<?> cls = Class.forName("android.widget.Toast");
                    // 2、获取全局变量属性
                    Field sField_N = cls.getDeclaredField("mTN");
                    sField_N.setAccessible(true);
                    // 3、获取mTN全局变量内部属性
                    Field sField_TN_Handler = sField_N.getType().getDeclaredField("mHandler");
                    sField_TN_Handler.setAccessible(true);
                    // 4、返回指定对象上此 Field 表示的字段的值, 即 tn
                    Object tn = sField_N.get(toast);
                    // 5、返回指定对象上此 Field 表示的字段的值, 即 tn内部handler
                    Handler handler = (Handler) sField_TN_Handler.get(tn);
                    // 6、给handler赋一个新的值,用来处理 handler报错
                    sField_TN_Handler.set(tn, new ReplaceHandler(handler));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            private static class ReplaceHandler extends Handler {
                private Handler tnHandler;
                public ReplaceHandler(Handler handler) {
                    this.tnHandler = handler;
                }
                @Override
                public void handleMessage(Message msg) {
                    try{
                        tnHandler.handleMessage(msg);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }

        image.gif

        这里简单做个说明,第六步,需要将原来的handler传进去,目的是为了原先handler的消息能继续执行,不然,没有办法处理原先handler的异常,我们看下源码

        // 6、给handler赋一个新的值,用来处理 handler报错

        sField_TN_Handler.set(tn, new ReplaceHandler(handler));

        当原先的handler在执行 dispatchMessage时,会判断callback是否为空,不为空,则执行callback的handleMessage方法,也即我们新定义的callback,在这个callback内部,在继续原来的handler消息执行。
            /**
             * Handle system messages here.
             */
            public void dispatchMessage(@NonNull Message msg) {
                if (msg.callback != null) {
                    handleCallback(msg);
                } else {
                    if (mCallback != null) {
                        if (mCallback.handleMessage(msg)) {
                            return;
                        }
                    }
                    handleMessage(msg);
                }
            }

        image.gif

        案例二:hook activity 报错

        tips:具体就不分析了,一搜索一大堆,直接上代码,原理同上

        // class
                        Class<?> aClass = Class.forName("android.app.ActivityThread");
                        // 获取currentActivityThread 方法
                        Method declaredMethod = aClass.getDeclaredMethod("currentActivityThread");
                        declaredMethod.setAccessible(true);
                        // 获取ActivityThread对象
                        Object activityThread = declaredMethod.invoke(null);
                        // 获取 currentActivityThread 对象中的mH 属性,即 Handler
                        Field mH = aClass.getDeclaredField("mH");
                        mH.setAccessible(true);
                        // 原始ActivityThread mH对象
                        Handler mHandler = (Handler) (mH.get(activityThread));
                        // 获取原始callBack字段
                        Field callbackField = Handler.class.getDeclaredField("mCallback");
                        callbackField.setAccessible(true);
                        // 由于 activity 生命周期都是通过主线程的 handler进行消息处理,
                        // 所以,我们可以通过反射替换掉主线程 handler中的callback,(ActivityThread.mH.mCallback)
                        // 然后,在callback的回调中 进行try catch, 同时调用原来handler的 handlemessage方法继续执行。
                        callbackField.set(mHandler, new Callback() {
                            @Override
                            public boolean handleMessage(@NonNull Message msg) {
                                try {
                                    // 捕获 Exception
                                    mHandler.handleMessage(msg);
                                } catch (Exception e) {
                                }
                                return true;
                            }
                        });

        image.gif

        最后,再贴一张网络图

        image.gif编辑


        相关文章
        |
        26天前
        |
        存储 Java 开发者
        Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
        【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
        57 2
        |
        2月前
        |
        Java Linux
        java基础(3)安装好JDK后使用javac.exe编译java文件、java.exe运行编译好的类
        本文介绍了如何在安装JDK后使用`javac.exe`编译Java文件,以及使用`java.exe`运行编译好的类文件。涵盖了JDK的安装、环境变量配置、编写Java程序、使用命令行编译和运行程序的步骤,并提供了解决中文乱码的方法。
        60 2
        |
        1月前
        |
        存储 消息中间件 安全
        JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
        【10月更文挑战第9天】本文介绍了如何利用JUC组件实现Java服务与硬件通过MQTT的同步通信(RRPC)。通过模拟MQTT通信流程,使用`LinkedBlockingQueue`作为消息队列,详细讲解了消息发送、接收及响应的同步处理机制,包括任务超时处理和内存泄漏的预防措施。文中还提供了具体的类设计和方法实现,帮助理解同步通信的内部工作原理。
        JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
        |
        11天前
        |
        Java 大数据 API
        14天Java基础学习——第1天:Java入门和环境搭建
        本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
        |
        23天前
        |
        存储 Java
        [Java]反射
        本文详细介绍了Java反射机制的基本概念、使用方法及其注意事项。首先解释了反射的定义和类加载过程,接着通过具体示例展示了如何使用反射获取和操作类的构造方法、方法和变量。文章还讨论了反射在类加载、内部类、父类成员访问等方面的特殊行为,并提供了通过反射跳过泛型检查的示例。最后,简要介绍了字面量和符号引用的概念。全文旨在帮助读者深入理解反射机制及其应用场景。
        15 0
        [Java]反射
        |
        28天前
        |
        开发框架 Java 程序员
        揭开Java反射的神秘面纱:从原理到实战应用!
        本文介绍了Java反射的基本概念、原理及应用场景。反射允许程序在运行时动态获取类的信息并操作其属性和方法,广泛应用于开发框架、动态代理和自定义注解等领域。通过反射,可以实现更灵活的代码设计,但也需注意其性能开销。
        45 1
        |
        1月前
        |
        安全 Java 测试技术
        🌟Java零基础-反射:从入门到精通
        【10月更文挑战第4天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
        25 2
        |
        1月前
        |
        前端开发 小程序 Java
        java基础:map遍历使用;java使用 Patten 和Matches 进行正则匹配;后端传到前端展示图片三种情况,并保存到手机
        这篇文章介绍了Java中Map的遍历方法、使用Pattern和matches进行正则表达式匹配,以及后端向前端传输图片并保存到手机的三种情况。
        19 1
        |
        1月前
        |
        Oracle Java 关系型数据库
        |
        1月前
        |
        IDE Java 编译器
        java的反射与注解
        java的反射与注解
        16 0