写点什么

Java 核心基础——反射

用户头像
老农小江
关注
发布于: 2020 年 11 月 07 日

一、前言

在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 方法
}

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

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();
}
}
}

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

三、反射的基本原理

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



Tips:

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

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

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

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

  • 

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

    1、java.lang.Class

    表示类的实体,在运行中的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");

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

    2、 java.lang.reflect.Constructor

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

    例: Constructor constructor = clazz.getConstructor();

    3、java.lang.reflect.Method

    表示类的方法。

    例: Method method = clazz.getDeclaredMethod("doSomethingByName",String.class);

    4、java.lang.reflect.Field

    表示类的属性。

    例:Field field = clazz.getDeclaredField("name");

    5、java.lang.reflect.Modifier

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

    例: Modifier.isPublic(method.getModifiers());

    

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

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

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

    四、反射的应用场景

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

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

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

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

    1. 动态代理实现;

    2. 自定义注解;

    3. 序列化与反序列化;

    4. JDBC实现;

    5. Spring的IOC、AOP;

    ...

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

    五、小结

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

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

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

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

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

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

    

    发布于: 2020 年 11 月 07 日阅读数: 44
    用户头像

    老农小江

    关注

    好好学习,天天向上 2020.03.26 加入

    还未添加个人简介

    评论

    发布
    暂无评论
    Java核心基础——反射