作者:DeppWang原文地址

造轮子:实现一个简易的 Spring IoC 容器一文中提到 Spring 在创建 Bean 实例和依赖注入时使用了反射,本文来具体分析一下 Spring 中的反射以及反射的原理。


一、Spring 中的反射

1.1、创建 Bean 实例时的反射

  1. // 通过类加载器,根据 class 路径,得到其类对象
  2. Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass("org.deppwang.litespring.v1.service.PetStoreService");
  3. // 根据类对象生成 Bean 实例
  4. return clz.newInstance();

反射体现在 clz.newInstance(); 中,核心代码可分为两部分:

1、利用反射获取当前类 PetStoreService 的所有构造方法信息(Constructor 对象)

  1. // java.lang.Class.java
  2. // 调用 native 方法,此时 publicOnly 为 false
  3. res = getDeclaredConstructors0(publicOnly);
  4. // native 方法,从 jvm 中的 class 文件中获取构造方法信息,再转换为 Constructor 对象
  5. private native Constructor<T>[] getDeclaredConstructors0(boolean publicOnly);

2、利用反射通过默认构造方法生成实例

  1. // sun.reflect.NativeConstructorAccessorImpl.java
  2. // 调用 native 方法,var1 代表构造方法的参数,此时为 null
  3. return newInstance0(this.c, var1);
  4. // native 方法,真正生成实例的方法,执行 class 文件的构造方法 <init>
  5. private static native Object newInstance0(Constructor<?> var0, Object[] var1);

1.2、构造方法依赖注入时的反射

  1. // 通过反射获取当前类所有的构造方法信息(Constructor 对象)
  2. Constructor<?>[] candidates = beanClass.getDeclaredConstructors();
  3. // 设置构造方法参数实例
  4. Object[] argsToUse = new Object[parameterTypes.length];
  5. argsToUse[i] = getBean(beanNames.get(i));
  6. // 使用带有参数的 Constructor 对象实现实例化 Bean。此时使用反射跟上面一样(newInstance0),只是多了参数
  7. return constructorToUse.newInstance(argsToUse);

1.3、setter() 方法依赖注入时的反射

  1. // 通过反射获取当前类所有的方法信息(Method 对象)
  2. Method[] methods = bean.getClass().getDeclaredMethods();
  3. // 获得方法参数实例
  4. Object propertyBean = getBean(propertyName);
  5. // 通过反射执行调用 setter() 方法。invoke:调用方法,propertyBean 作为方法的参数
  6. method.invoke(bean, propertyBean);

bean.getClass().getDeclaredMethods(); 中的核心代码:

  1. // java.lang.Class.java
  2. // 调用 native 方法,publicOnly 为 false
  3. getDeclaredMethods0(publicOnly);
  4. // native 方法,从 jvm 中的 class 文件中获取方法信息,再转换为 Method
  5. private native Method[] getDeclaredMethods0(boolean publicOnly);

method.invoke(bean, propertyBean); 中的核心代码:

  1. // sun.reflect.NativeMethodAccessorImpl.java
  2. // 调用 native 方法,var1: bean、var2: propertyBean
  3. return invoke0(this.method, var1, var2);
  4. // native 方法,运行 class 文件中的字节码指令
  5. private static native Object invoke0(Method var0, Object var1, Object[] var2);

1.4、@Autowired 依赖注入时的反射

  1. // 通过反射得到当前类所有的字段信息(Field 对象)
  2. Field[] fields = bean.getClass().getDeclaredFields();
  3. // 判断字段是否有 @Autowired 注解
  4. Annotation ann = field.getAnnotation(Autowired.class);
  5. // 设置字段可连接,相当于将非 public(private、default、protect)更改为 public
  6. field.setAccessible(true);
  7. // 通过反射设置字段的值
  8. field.set(bean, getBean(field.getName()));

bean.getClass().getDeclaredFields(); 中的核心代码:

  1. // java.lang.Class.java
  2. // 调用 native 方法,此时 publicOnly 为 false
  3. getDeclaredFields0(publicOnly);
  4. // native 方法,从 jvm 中获取 class 文件的字段信息,再转换为 Field
  5. private native Field[] getDeclaredFields0(boolean publicOnly);

field.set(bean, getBean(field.getName())); 中的核心代码:

  1. // sun.reflect.UnsafeObjectFieldAccessorImpl.java
  2. // 调用 native 方法,将目标对象 var1 指定偏移量 fieldOffset 处的字段值设置(修改)为 var2。var1 为 bean, var2 为参数实例
  3. unsafe.putObject(var1, this.fieldOffset, var2);
  4. // sun.misc.Unsafe.java

  5. // native 方法,直接修改堆中对象字段的数据

  6. public native void putObject(Object var1, long var2, Object var4);

二、class 文件与类对象

class 文件由 java 文件编译而来,class 文件包含字段表、方法表、<init> 方法(构造方法)等。

当类加载器将 class 文件加载进虚拟机元空间(Meta-space,jdk 1.8)时,虚拟机在元空间中创建一个与之对应的类对象(Class 实例)。并将 class 文件由存放在磁盘的静态结构转换为存放在内存的运行时结构。

我们可以认为一个类(class 文件)对应一个类对象,当前类的所有对象共用一个类对象。类对象作为访问存放在 jvm 的 class 文件的入口。

  1. package java.lang;
  2. import java.lang.reflect.Field;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Constructor;
  5. public final class Class<T> {

  6. private native Field[] getDeclaredFields0(boolean publicOnly);

  7. private native Method[] getDeclaredMethods0(boolean publicOnly);

  8. private native Constructor<T>[] getDeclaredConstructors0(boolean publicOnly);
  9. // ReflectionData 缓存反射对象
  10. private static class ReflectionData&lt;T&gt; {
  11.     volatile Field[] declaredFields;
  12.     volatile Field[] publicFields;
  13.     volatile Method[] declaredMethods;
  14.     volatile Method[] publicMethods;
  15.     volatile Constructor&lt;T&gt;[] declaredConstructors;
  16.     volatile Constructor&lt;T&gt;[] publicConstructors;
  17.     ...
  18. }
  19. }

2.1、获得类对象的方式

  1. // 1、通过对象
  2. Class cls = object.getClass();
  3. // Object.java
  4. public final native Class<?> getClass();
  5. // 2、通过类加载器

  6. Class cls = Thread.currentThread().getContextClassLoader().loadClass("org.deppwang.litespring.v1.service.PetStoreService");
  7. // 3、通过 Class 类,本质上也是通过类加载器

  8. Class cls = Class.forName("org.deppwang.litespring.v1.service.PetStoreService");

  9. // Class.java

  10. private static native Class<?> forName0(String name, boolean initialize,

  11. ClassLoader loader,

  12. Class<?> caller)

三、反射方法

以下是常用的反射方法。

3.1、Feild 相关

  1. Field[] fields = cls.getFields(); // 获取所有公共的 Field(包括父类)
  2. Field[] fields = cls.getDeclaredFields(); // 获取当前类的所有 Field(不包括父类),包括公共和非公共
  3. Field field = cls.getDeclaredField("fieldName"); // 指定获取当前类某个 Field
  4. field.set(Object, Object); // 设置(修改)字段值
  5. field.get(Object); // 获取字段值

field.get(Object) 核心代码:

  1. // 调用 native 方法,获取字段对应的值
  2. return unsafe.getObject(var1, this.fieldOffset);
  3. // native 方法,从堆中获取对象指定位置的对象

  4. public native Object getObject(Object var1, long var2);

3.2、Method 相关

  1. Method[] methods = cls.getMethods(); // 获取所有公共的 Method(包括父类)
  2. Method[] methods = cls.getDeclaredMethods(); // 获取当前类的所有 Method(不包括父类),包括公共和非公共
  3. method.invoke(Object instance, Object... parameters); // 运行方法

运行方法使用场景:要么是修改对象的数据,如 void setter() 方法;要么是获得执行方法的返回结果。

  1. String result = method.invoke().toString();

3.3、Constructor 相关

  1. Constructor<?>[] constructors = cls.getConstructors(); // 获取所有公共的 Constructor(包括父类)
  2. Constructor<?>[] constructors = cls.getDeclaredConstructors(); // 获取当前类的所有Constructor(不包括父类),包括公共和非公共
  3. constructor.newInstance(Object... parameters); // 运行构造方法

当没有明确编写构造方法,Java 编译器将为该类构建一个默认构造函数 <init>

四、native 方法

Java 1.1 新增「Java 本地接口」(Java Native Interface,JNI),JNI 是一种包容极广的编程接口,允许我们从 Java 应用程序里调用 native 方法,native 方法由其它语言(C 、C++ 或汇编语言等)编写。native 方法用于实现 Java 无法处理的功能。

4.1、简单示例

一个在 Java 中使用 Java 本地接口(JNI)的简单示例。

  • 环境:jdk8、macOS 10.15
  1. // Main.java
  2. public class Main {
  3. public native int intMethod(int i);
  4. static {
  5. // 启动时载入 libMain.dylib
  6. System.loadLibrary("Main");
  7. }
  8. public static void main(String[] args) {
  9. System.out.println(new Main().intMethod(2));
  10. }
  11. }
  1. // Main.c:
  2. // 将 Main.h 引入
  3. #include "Main.h"
  4. // 相当于继承 "Main.h" 的 Java_Main_intMethod

  5. JNIEXPORT jint JNICALL Java_Main_intMethod(

  6. JNIEnv *env, jobject obj, jint i)

  7. {

  8. return i * i;

  9. }

编译与运行:

  1. // 同时生成 Main.class 和 Main.h
  2. javac Main.java -h .
  3. // 根据 Main.c 生成 libMain.dylib
  4. gcc -dynamiclib -O3 \
  5. -I/usr/include \
  6. -I$JAVA_HOME/include \
  7. -I$JAVA_HOME/include/darwin \
  8. Main.c -o libMain.dylib
  9. // 指定 library 的路径为当前路径
  10. java -cp . -Djava.library.path=$(pwd) Main

输出:

  1. 4
  1. /* Main.h .h 作为头文件*/
  2. /* DO NOT EDIT THIS FILE - it is machine generated */
  3. #include <jni.h>
  4. /* Header for class Main */
  5. ifndef _Included_Main

  6. define _Included_Main

  7. ifdef __cplusplus

  8. extern "C" {
  9. endif

  10. /*
    • Class: Main
    • Method: intMethod
    • Signature: (I)I

    • */

    • JNIEXPORT jint JNICALL Java_Main_intMethod

    • (JNIEnv *, jobject, jint);
  11. ifdef __cplusplus

  12. }
  13. endif

  14. endif

  1. javac Main.java -h .
  2. // 可拆分为两个命令
  3. javac Main.java
  4. javah -jni Main

4.2、原理

运行 Main.class 时,将 libMain.dylib 载入虚拟机,JVM 调用 libMain.dylib 的 Java_Main_intMethod,传入参数,libMain.dylib 由系统直接运行,返回结果。

  • *env 用于将 java 类型数据与本地(此处为 C 语言)类型数据之间的转换
  • jint 还是 Java 数据类型,Java 基本数据类型可以映射(使用),不用通过 *env 转换
  1. /*C code*/
  2. JNIEXPORT void JNICALL Java_ClassName_MethodName
  3. (JNIEnv *env, jobject obj, jstring javaString)
  4. {
  5. /*Get the native string from javaString*/
  6. const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);
  7. /*Do something with the nativeString*/
  8. /*DON'T FORGET THIS LINE!!!*/
  9. (*env)-&gt;ReleaseStringUTFChars(env, javaString, nativeString);
  10. }


4.3、参考

五、总结

反射反射,哪里体现反射字面意思?

可以这么理解,通过 native 方法得到反射对象,操作反射对象,像镜子一样,将反射到原对象上。

我们发现,反射和 native 方法的关系:

  • 获取字段、方法、构造方法对象,native() 方法实现
  • 获取字段值、设置修改字段值,native() 方法实现
  • 运行方法,native() 方法实现
  • 运行构造方法,native() 方法实现

我们可以得出结论,反射由 native 方法实现

我们说通过反射实现一个功能,我们也可以说:

  • 通过反射方法实现
  • 通过反射 API 实现
  • 通过 native 方法实现

反射是一种非常规(native 方法实现)方式获取 class 文件信息、运行 class 文件字节码指令和操作对象数据的能力。

一句话总结 :反射是一种运行时获取和修改对象数据的能力。

关于运行时:Java 是静态语言,先编译,后运行。编译时不执行代码,代码都是运行时执行。

六、延伸阅读


Spring 中的反射与反射的原理的更多相关文章

  1. 【进阶】Spring中的注解与反射

    [进阶]Spring中的注解与反射 目录 [进阶]Spring中的注解与反射 前言 一.内置(常用)注解 1.1@Overrode 1.2@RequestMapping 1.3@RequestBody ...

  2. Spring中FactoryBean的作用和实现原理

    BeanFactory与FactoryBean,相信很多刚翻看Spring源码的同学跟我一样很好奇这俩货怎么长得这么像,分别都是干啥用的.BeanFactory是Spring中Bean工厂的顶层接口, ...

  3. Spring中Mybatis的花样配置 及 原理

    摘自: https://www.jianshu.com/p/fc23c94fc439

  4. Spring中异步注解@Async的使用、原理及使用时可能导致的问题

    前言 其实最近都在研究事务相关的内容,之所以写这么一篇文章是因为前面写了一篇关于循环依赖的文章: <面试必杀技,讲一讲Spring中的循环依赖> 然后,很多同学碰到了下面这个问题,添加了S ...

  5. spring中反射机制和注入的使用

    http://www.cnblogs.com/andin/archive/2011/04/30/spring.html spring的一大核心概念是注入, 但是,这存在的一个前提就是类是由spring ...

  6. 深入理解Java中的反射机制和使用原理!详细解析invoke方法的执行和使用

    反射的概念 反射: Refelection,反射是Java的特征之一,允许运行中的Java程序获取自身信息,并可以操作类或者对象的内部属性 通过反射,可以在运行时获得程序或者程序中的每一个类型的成员活 ...

  7. 使用反射创建Bean、Spring中是如何根据类名配置创建Bean实例、Java提供了Class类获取类别的字段和方法,包括构造方法

    Java提供了Class类,可以通过编程方式获取类别的字段和方法,包括构造方法    获取Class类实例的方法:   类名.class   实例名.getClass()   Class.forNam ...

  8. 在Spring Bean实例过程中,如何使用反射和递归处理的Bean属性填充?

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! <Spring 手撸专栏>目录 [x] 第 1 章:开篇介绍,我要带你撸 Spri ...

  9. Java反射机制及IoC原理

    一. 反射机制概念 主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义.在java中,只要给定类的名字, 那么就可以通 ...

随机推荐

  1. 数据可视化之powerBI基础(六)Power BI的“问答”,你用过吗?

    https://zhuanlan.zhihu.com/p/64412190 ​本文来自星球嘉宾"海艳"的分享,关于Power BI的问答功能以及各项细节,海艳详细为大家逐一介绍,下 ...

  2. 数据可视化之powerBI基础(三)编辑交互,体验更灵活的PowerBI可视化

    https://zhuanlan.zhihu.com/p/64412190 PowerBI可视化与传统图表的一大区别,就是可视化分析是动态的,通过页面上筛选.钻取.突出显示等交互功能,可以快速进行访问 ...

  3. 数据可视化实例(九): 边缘箱形图(matplotlib,pandas)

    https://datawhalechina.github.io/pms50/#/chapter7/chapter7 边缘箱形图 (Marginal Boxplot) 边缘箱图与边缘直方图具有相似的用 ...

  4. 05-Python模块

    一.简介 模块是一个包含所有你定义的函数和变量的文件,其后缀名是.py.模块可以被其他程序导入来使用模块具有的功能.这也是使用python标准库的方式. import time start_time ...

  5. 【其他-小技巧-Uipath】VB语法操作DataTable分组并求和

    需要对DataTable分组求和的语法:VB.net 和C#中还有点不太一样.最后试了好多方法,要这么写 我的dataTabel数据: (From p In dataTabel.AsEnumerabl ...

  6. 用scratch编程大炮打幽灵

    首先来看看效果: 是不是很炫酷呢?想知道具体程序的话请关注微信公众号!

  7. 通过jmeter读取csv的case来实现接口自动化

    一.环境准备: 1.环境:jdk:1.8+,jmeter:4.2+, 2.csv: 按照以下这种方式编写case或者自定义其他的,准备好case 二.注意要点: 1.想实现数据参数化,可以直接csv中 ...

  8. Win7安装Python失败 提示Setup failed

    一.安装报错 如图所示,双击Python安装包后进行安装显示Setup failed 安装失败: 二.错误排除 1.首先查看自己的计算机是否已经安装了 Win7 Service Pack 1大补丁,没 ...

  9. 01 . Git常用命令及方法和分支管理

    原理 # Workspace:工作区 # Index / Stage:暂存区 # Repository:仓库区(或本地仓库) # Remote:远程仓库 本地分支关联远程 git branch --s ...

  10. 单例模式—Java实现

    饿汉法 顾名思义,饿汉法就是在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建.代码如下: 1 2 3 4 5 6 7 public class Singleton {       priv ...