zoukankan      html  css  js  c++  java
  • Spring中的反射与反射的原理,案例详解

     

    image source: https://coderius.biz.ua/blog/article/vvedenie-v-php-reflection-api

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

    一、Spring 中的反射

    1.1、创建 Bean 实例时的反射

    // 通过类加载器,根据 class 路径,得到其类对象
    Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass("org.deppwang.litespring.v1.service.PetStoreService");
    // 根据类对象生成 Bean 实例
    return clz.newInstance();
    这里也要注意光理论是不够的,记住:Java架构项目经验永远是核心,如果你没有最新JAVA架构实战教程及大厂30k+面试宝典,可以去小编的Java架构学习.裙 :七吧伞吧零而衣零伞 (数字的谐音)转换下可以找到了,里面很多新JAVA架构项目教程,还可以跟老司机交流讨教! 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // sun.reflect.UnsafeObjectFieldAccessorImpl.java
    // 调用 native 方法,将目标对象 var1 指定偏移量 fieldOffset 处的字段值设置(修改)为 var2。var1 为 bean, var2 为参数实例
    unsafe.putObject(var1, this.fieldOffset, var2);
    
    // sun.misc.Unsafe.java
    // native 方法,直接修改堆中对象字段的数据
    public native void putObject(Object var1, long var2, Object var4);
    复制代码

    二、class 文件与类对象

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

    当类加载器将 class 文件加载进虚拟机元数据区(方法区,jdk1.7)时,虚拟机创建一个与之对应的类对象(Class 实例)。并将 class 文件由存放在磁盘的静态结构转换为存放在内存的运行时结构。

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

    package java.lang;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.lang.reflect.Constructor;
    
    public final class Class<T> {
        private native Field[]       getDeclaredFields0(boolean publicOnly);
        private native Method[]      getDeclaredMethods0(boolean publicOnly);
        private native Constructor<T>[] getDeclaredConstructors0(boolean publicOnly);
    
        // ReflectionData 缓存反射对象
        private static class ReflectionData<T> {
            volatile Field[] declaredFields;
            volatile Field[] publicFields;
            volatile Method[] declaredMethods;
            volatile Method[] publicMethods;
            volatile Constructor<T>[] declaredConstructors;
            volatile Constructor<T>[] publicConstructors;
            ...
        }
    }
    复制代码

    2.1、获得类对象的方式

    // 1、通过对象
    Class cls = object.getClass();
    // Object.java
    public final native Class<?> getClass();
    
    // 2、通过类加载器
    Class cls = Thread.currentThread().getContextClassLoader().loadClass("org.deppwang.litespring.v1.service.PetStoreService");
    
    // 3、通过 Class 类,本质上也是通过类加载器
    Class cls = Class.forName("org.deppwang.litespring.v1.service.PetStoreService");
    // Class.java
    private static native Class<?> forName0(String name, boolean initialize,
                                                ClassLoader loader,
                                                Class<?> caller)
    复制代码

    三、反射方法

    以下是常用的反射方法。

    3.1、Feild 相关

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

    field.get(Object) 核心代码:

    // 调用 native 方法,获取字段对应的值
    return unsafe.getObject(var1, this.fieldOffset);
    
    // native 方法,从堆中获取对象指定位置的对象
    public native Object getObject(Object var1, long var2);
    复制代码

    3.2、Method 相关

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

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

    String result = method.invoke().toString();
    复制代码

    3.3、Constructor 相关

    Constructor<?>[] constructors = cls.getConstructors(); // 获取所有公共的 Constructor(包括父类)
    Constructor<?>[] constructors = cls.getDeclaredConstructors(); // 获取当前类的所有Constructor(不包括父类),包括公共和非公共
    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
    // Main.java
    public class Main {
        public native int intMethod(int i);
        static {
            // 启动时载入 libMain.dylib
            System.loadLibrary("Main");
        }
        public static void main(String[] args) {
            System.out.println(new Main().intMethod(2));
        }
    }
    复制代码
    // Main.c:
    // 将 Main.h 引入
    #include "Main.h"
    
    // 相当于继承 "Main.h" 的 Java_Main_intMethod
    JNIEXPORT jint JNICALL Java_Main_intMethod(
        JNIEnv *env, jobject obj, jint i)
    {
        return i * i;
    }
    复制代码

    编译与运行:

    // 同时生成 Main.class 和 Main.h
    javac Main.java -h .
    // 根据 Main.c 生成 libMain.dylib
    gcc -dynamiclib -O3 
        -I/usr/include 
        -I$JAVA_HOME/include 
        -I$JAVA_HOME/include/darwin 
        Main.c -o libMain.dylib
    // 指定 library 的路径为当前路径
    java -cp . -Djava.library.path=$(pwd) Main
    复制代码

    输出:

    4
    复制代码
    /* Main.h .h 作为头文件*/
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class Main */
    
    #ifndef _Included_Main
    #define _Included_Main
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     Main
     * Method:    intMethod
     * Signature: (I)I
     */
    JNIEXPORT jint JNICALL Java_Main_intMethod
      (JNIEnv *, jobject, jint);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    复制代码
    javac Main.java -h .
    // 可拆分为两个命令
    javac Main.java
    javah -jni Main
    复制代码

    4.2、原理

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

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

    4.3、参考

    五、总结

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

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

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

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

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

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

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

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

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

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

    • 最后注意光理论是不够的,记住:Java架构项目经验永远是核心,如果你没有最新JAVA架构实战教程及大厂30k+面试宝典,可以去小编的Java架构学习.裙 :七吧伞吧零而衣零伞 (数字的谐音)转换下可以找到了,里面很多新JAVA架构项目教程,还可以跟老司机交流讨教! 

      本文的文字及图片来源于网络加上自己的想法,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理

  • 相关阅读:
    谈一下ACM的入门书籍及方法
    acm总结帖_By AekdyCoin
    楼天城楼教主的acm心路历程
    弱校ACM奋斗史
    【转】编程的浅学习与深学习
    HDOJ 1047 Integer Inquiry (大数)
    【链性栈】表达式求值
    【链性栈】基本链性栈的实现
    Beta冲刺博客
    Alpha项目测试
  • 原文地址:https://www.cnblogs.com/chengxuyuanaa/p/12837947.html
Copyright © 2011-2022 走看看