Bootstrap

Java核心基础——反射

一、前言

在Java语言中,反射是一个非常核心的基础特性,在Java语言本身,以及以Java为基础的整个Java技术体系中,都承担着非常重要的角色。如工作当中通常会用到的Spring、Mybatis等框架中,都利用了反射这一重要特性。可以说,在Java的整个技术栈世界里,反射无处不在,理解Java反射这一重要特性,是能够自由翱翔于其中的基石。在各技术社区中,讲Java反射的文章多如牛毛,但笔者依然想简单说一下,因为它实在太重要了。

二、什么是反射

通俗地说,Java反射机制,是指在Java程序运行过程中,对任意一个类,都能够获取该类所有的属性和方法,并能够创建对象;对任意一个对象,都能够调用该对象中的属性和方法。即,能够动态地获取类的信息,动态地调用对象的方法和属性。

接下来,通过一个例子进行反射使用的对比演示,让大家对反射有个初步的直观感受。

假设我们有如下的一个类,有几个基础属性,有1个业务方法:

package com.xiaojiang.demo.reflect;

/**
 * @Description : 用户信息对象
 * @Version : 0.0.1
 * @Author : xiaojiang
 * @Date : Created in 2020-10-31 21:17
 */
public class User {
    /**
     * 主键ID
     */
    private Long id;
    /**
     * 用户姓名
     */
    private String name;
    /**
     * 用户年龄
     */
    private Integer age;

    /**
     * 模拟 业务方法
     * @param name
     * @return
     */
    public String doSomethingByName(String name){
        //TODO 相关业务逻辑
        return "我是模拟业务方法返回-->" + name;
    }

    // ... 此处省略 get/set 方法
}

那么,我们创建对象、调用对象中的属性与方法,通过关键字方式反射方式代码实现对比如下:

package com.xiaojiang.demo.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Description : 主函数入口
 * @Version : 0.0.1
 * @Author : xiaojiang
 * @Date : Created in 2020-10-31 21:21
 */
public class App {
    public static void main(String[] args) {
        /**
         * 1、通常方式
         */
        // 创建对象,调用方法
        User user = new User();
        user.doSomethingByName("小江1号");
        // 给对象属性赋值、取值
        user.setName("我是小江1号");
        user.getName();

        /**
         * 2、反射方式
         */
        try {
            // 创建对象,调用方法
            Class clazz = Class.forName("com.xiaojiang.demo.reflect.User");
            Object object = clazz.newInstance();
            Method method = clazz.getDeclaredMethod("doSomethingByName", String.class);
            method.invoke(object, "小江2号");
            // 给对象属性赋值、取值
            Field field = clazz.getDeclaredField("name");
            field.setAccessible(true);
            field.set(object,"我是小江2号");
            field.get(object);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

通过以上的例子,相信你对反射会有一个相对直观的感受,但是,同时可能又会有很多的疑惑,如示例当中,反射方式用到的 到底是什么意思, 又是什么意思? 下一节,笔者将对反射的基本原理与相关核心类进行简单说明。

三、反射的基本原理

一说到基本原理,有些同学可能就会觉得有些头痛,坦率地说,如果真要从源头说起的话,可能需要从JVM的类加载机制对象的创建过程说起,但这两点稍微展开一说,都需要很长的篇幅,而这不是本文的重点。所以,此处大家只需要知道有这样一个过程就好了,JVM类加载、对象创建大概过程简化如下:(实际过程复杂的多,此处简化细节便于理解反射,以User对象为例)

Tips:

1、将源码编译成二进制字节码文件;

  • 2、JVM将二进制字节码文件加载到JVM内存中,并生成User对应的唯一的对象; (任意实际对象,JVM类加载器加载时都会创建其对应的唯一的1个Class对象)

  • 3、当我们通过 这种方式创建一个User对象时,JVM就会通过User对象对应的对象来实例化一个User对象;(JVM通过这个关键字,为我们“屏蔽”了类的加载检测、加载、初始化等过程,从而使我们实例化一个对象变得非常容易)

  • 4、反射,则是JDK为我们提供了几个基础核心类,让我们可以通过API的方式直接去加载JVM内存中相关类的Class对象,从而可以获取类的构造函数、方法、属性等,并实例化对象。(如果JVM内存中已经存在了实例化的对象,通过反射的方式,则可以修改该对象的属性和方法内容等)

  • JDK中,关于反射相关操作的类、方法众多,笔者此处对其中核心的类、方法简单说明:

    1、

    表示类的实体,在运行中的Java应用程序中表示类和接口。获取一个类的Class对象,通常有如下3种方式:

    // 1、通过已存在的对象的getClass()方法获取
    User user = new User();
    Class clazz1 = user.getClass();
    // 2、通过类的静态属性 class 获取
    Class clazz2 = User.class;
    // 3、通过java.lang.Class类的静态方法 Class.forName(“全限定名”)获取 (最常用,即上文笔者用的方式)
    Class clazz3 = Class.forName("com.xiaojiang.demo.reflect.User");

    当获取到 对象后,就可以通过相应API获取对象的构造函数、方法、属性、访问修饰符等信息,根据这些信息就可以实例化一个对象,并对其进行相关操作。

    2、

    表示类的构造函数(构造方法)。

    例:

    3、

    表示类的方法。

    例:

    4、

    表示类的属性。

    例:

    5、

    用于判断类、方法、属性的访问修饰符,如 、 。

    例:

    说到此处,相信你对上文中笔者通过反射方式创建对象、调用对象的方法、属性有了进一步的理解,此处笔者简单说明如下:

    当然,JDK操作反射相关的类、方法远不止这几个,反射的真正使用也比笔者的示例要复杂地多,毕竟真正的类的结构情况、方法的参数情况、属性的访问作用域情况等,都是需要我们考虑的。不过,也不必太过于纠结这些操作反射的类与方法,因为只要理解了笔者以上的这个示例,基本上其它的相关操作方法,也就是API的相关使用了,其主要流程是没有本质区别的。

    因此,笔者此处不再对反射操作相关API进行一一列举,不过,需要特别指出的是,需要特别注意这个方法,因为通常反射的目的是为了调用对象实例的某个方法;而在各种通用框架源码中(如Spring、Mybatis),也经常会看到类似写法的代码,其本质上很多都是利用了Java的反射机制,从而实现特定的目标。所以,理解反射调用方法的思想,有利于我们读懂源码,有利于我们理解各框架的设计思想。

    四、反射的应用场景

    说到此处,你可能会有一些疑惑,就是这个反射到底有啥用? 甚至可能有一种感觉,就是自己日常工作当中,很少用到这东西,真的是这样吗?

    诚然,如果说日常工作中,只是在现有项目框架之上,针对具体业务需求写CRUD相关代码的话,确实几乎不会需要我们写反射相关代码,这是因为框架层面帮我们封装了许多操作,让我们可以更便利地实现业务需求。

    不过,细想一下,Mybatis是如何实现配置文件与实体的映射的?Spring的IOC又是如何实现的?其实,这些技术的背后,几乎都利用了Java的反射机制。

    以下列出几项利用了Java反射机制的应用场景:

    ...

    很多很多,可以说,在我们日常工作中用到的Java技术,其背后几乎都有反射的存在。

    五、小结

    1、Java反射特性的存在,是Java语言被认为是半动态语言的重要原因之一。因为反射机制的存在,我们可以灵活地实现很多功能。

    2、JDK提供了一个功能丰富的反射库工具集(包下),通过该工具集,我们可以方便地操作反射相关的功能。

    3、反射的真正运用,远比本文示例中的复杂,如Modifier类,本文仅从基础的角度帮助理解大概流程。

    4、如果需要深入理解反射机制,需要知道JVM类加载机制以及Java类的创建过程,有兴趣的可以去了解相关知识。

    5、日常工作中,如果不是写底层框架相关代码的话,直接写反射代码的情况不会很多,但是几乎所有的Java技术栈相关的框架都有用到反射,所以,理解反射的基本原理,有利于我们理解整个Java技术栈相关技术的运行原理,可以说是刚需。

    6、需要注意Java中对象和实例的区别,如:User 是一个用户对象,但不是实例,因为其只是一个抽象的概念,不表示某一个具体存在的实体,User实例化的 xiaojiang 这个用户才是一个具体的实体。