JavaSec - Java基础 - 反射(Reflection)

JavaSec - Java基础 - 反射(Reflection)

什么是反射?为什么要用使用反射?

“Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions. The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.” – Java Reflection API

根据Oracle的官方文档,我们可以知道反射Reflection的作用就是,在Java程序运行的过程中可以获取 加载的class对象 的相关信息,包括成员变量,方法以及构造器,同时可以利用反射来创建对象并且调用其属性(成员变量,构造器,方法)。

这听起来和new一个类的对象,然后操作其属性很像对吗,那么区别在哪里呢?我们为什么不直接new对象却需要来使用反射呢?

不要着急,我认为既然确定了反射是需要学习的目标,不妨我们先来学习一下反射是如何使用的,有了一定的了解之后,相信也会更为方便的去理解使用反射的优势与原因。

反射的原理

这一节中我们先来理解一下反射背后的原理,方便我们接下来学习具体的代码应用。

我们先来回顾一下程序员编写的Java代码在计算机中,从编写到被执行所经历的三个阶段:

  1. Source源码阶段 : - Java代码的最开始就是程序员在IDE中所编写的 .java 文件中的源代码

    • 进而通过 javac 编译之后成为 .class 文件
    • 这两种文件都存储于计算机的硬盘之中
  2. Class对象阶段 : - 接着使用 java 命令来执行 .class 文件,这里就启动了一个 JVM进程

    • JVM通过类加载器以双亲委派模型的方式来找到并加载 .class文件(或者其他字节码文件)
    • 最终的结果是将一个 Class 类的对象存储在内存中
    • 这个 Class 类对象包含三个重要属性
      1. 成员变量
      2. 成员方法
      3. 构造方法
    • 这三种属性都会以数组对象的形式存储
  3. 内存 : 创建已经加载的类的对象,并存储与内存中(如果没有加载,则在第二步中进行加载)

Figure 1: Java代码在计算机中的各个阶段

Figure 1: Java代码在计算机中的各个阶段

现在我们知道了一个类在加载之后会创建一个 Class 类的对象,其中会包含所有其属性的信息,并被保存在内存中,而反射Reflection就是利用这些 Class 类对象,来获取其中属性的信息。还是那句话,这里肯定有很多为什么要问,但是我还是建议先继续看具体的代码使用,或者可以直接看后面为什么要使用反射的原因以及使用的场景那一节,如果觉得不好理解再回头看看具体的使用。

反射的使用

获取Class对象

获取Class对象的有三种方式

  1. Object.getClass(), 获得对象之后调用顶层父类 Object 的方法 getClass()
  2. 所有数据类型(包括基本的数据类型)都有一个 静态 的class属性
  3. Class类的静态方法 forName(String className) 方法(最常用)
package ReflectionLearn;

import org.junit.jupiter.api.Test;

public class ReflectionTest {
    @Test
    public void reflectClass() throws Exception{
        // 第一种获取Class对象的方式
        ReflectedObject ro = new ReflectedObject();
        Class roClass1 = ro.getClass();
        System.out.println(roClass1.getName());

       // 第二种获取Class对象的方式
        Class roClass2 = ReflectedObject.class;
        System.out.println("Two class object are same: " + (roClass1 == roClass2));

        // 第三种获取Class对象的方式
        try {
            Class roClass3 = Class.forName("ReflectionLearn.ReflectedObject");
            System.out.println("Two class object are same: " + (roClass3 == roClass2));
        } catch (ClassNotFoundException e){
            e.printStackTrace();
        }
    }
}

判断是否为某个类的实例对象

我们使用Class对象中的成员方法 isInstance() 方法来判断输入的对象是否为Class对象所代表的类的一个实例。

public native boolean isInstance(Object obj);

这是一个native方法,意味着其方法的实现源码不是java。

@Test
    public void ifInstanceTest() throws Exception {
        ReflectedObject ro = new ReflectedObject();
        Class roClass = ro.getClass();
        System.out.println(roClass.isInstance(ro));
        System.out.println(ro instanceof ReflectedObject);

    }

获取实例对象

获取实例对象有两种方法:

  1. 调用Class对象的 newInstance 方法来创建Class对象对应的实例对象
  2. 先通过Class对象指定想要使用的一种Constructor构造器的对象,然后调用Constructor对象的newInstance()方法来创建对象
@Test
public void reflectObject() throws Exception {
    Class roClass = Class.forName("ReflectionLearn.ReflectedObject");
    Object reflectedObject1 = roClass.newInstance();

    // 获取指定的Class对象的一种构造器的对象
    Constructor constructor = roClass.getConstructor(null);
    Object reflectedObject2 = constructor.newInstance();
}

获取构造器

  1. 批量获取构造器
    • public Constructor<?>[] getConstructors(),获取所有 public 构造器
    • public Constructor<?>[] getDeclaredConstructors(),获取所有的构造器(public, private, protected, default)
  2. 获取单个构造器
    • public Constructor<T> getConstructor(Class<?>… parameterTypes),获取单个 public 构造器
    • public Constructor<T> getDeclaredConstructor(Class<?>… parameterTypes),获取任意的单个构造器
  3. 调用构造器
    • Constructor–>public T newInstance(Object … initargs)
    • 无参构造器使用null作为参数

使用范例

public ReflectedObject(){
    System.out.println("public 无参构造器");
}

// 单参构造器
public ReflectedObject(String field){
    System.out.println("public 单参构造器:" + field);
}

// 多参构造器
public ReflectedObject(String field, int num) {
    System.out.println("public 多参构造器:" + field + ";" + num);
}
// protected 构造器
protected ReflectedObject(boolean b) {
    System.out.println("protected 单参构造器 b =" + b);
}

// private 构造器
private ReflectedObject(int i) {
    System.out.println("private 单参构造器 i = " + i);
}

// default 构造器
ReflectedObject(char c){
    System.out.println("default 单参构造器 c = " + c);
}

测试实例

@Test
public void reflectConstructor() throws Exception {
    Class roClass = Class.forName("ReflectionLearn.ReflectedObject");

    // getConstructors()
    System.out.println("getConstructors():");
    Constructor[] constructors = roClass.getConstructors();
    for (Constructor c : constructors) {
        System.out.println(c);
    }
    System.out.println();
    // getDeclaredConstructors()
    System.out.println("getDeclaredConstructors():");
    Constructor[] declaredConstructors = roClass.getDeclaredConstructors();
    for (Constructor c : declaredConstructors) {
        System.out.println(c);
    }

    // 获取公有的无参构造器
    System.out.println("reflect public non-parameter constructor");
    Constructor con = roClass.getConstructor(null);
    System.out.println("con = " + con);

    // 调用构造器
    Object obj = con.newInstance();

    // 获取私有构造器并调用
    System.out.println("reflect and call private constructor");
    con = roClass.getDeclaredConstructor(int.class);
    System.out.println(con);
    // 暴力反射,忽略访问修饰符
    con.setAccessible(true);
    obj = con.newInstance(2);
}

获取成员变量

  1. 批量获取成员变量
    • Field[] getFields(), public成员变量;
    • Field[] getDeclaredFields(), 获取所有成员变量(pubic, protected, private, default);
  2. 获取单个成员变量
    • public Field getField(String fieldName), 获取单个public成员变量;
    • public Field getDeclaredField(String fieldName), 获取任意成员变量;
    • 最后以field.get(obj)的形式获取变量具体的值;
  3. 设置成员变量的值 Field –> public void set(Object obj, Object value):
    1. obj, 设置成员变量所在的对象;
    2. value, 设置的值;

实例

// fields
    public String str;
    protected int i;
    char c;
    private Date d;

测试实例

@Test
public void reflectedFields() throws Exception {
    Class roClass = Class.forName("ReflectionLearn.ReflectedObject");

    // getFields()
    System.out.println("getFields():");
    Field[] fields = roClass.getFields();
    for(Field f : fields){
        System.out.println(f);
    }

    // getDeclaredFields()
    System.out.println("\ngetDeclaredFields():");
    fields = roClass.getDeclaredFields();
    for(Field f : fields){
        System.out.println(f);
    }

    // 获取public成员变量
    System.out.println("\npublic field");
    Field f = roClass.getField("str");

    // 设置public成员变量的值
    System.out.println("\nset public field's value");
    Object obj = roClass.getConstructor().newInstance();
    f.set(obj, "A new string");
    System.out.println("public fields is set to :" + ((ReflectedObject)obj).str);

    // 获取private成员变量
    System.out.println("\nprivate field");
    f = roClass.getDeclaredField("d");

    // 设置private成员变量的值
    f.setAccessible(true); // 暴力反射,解除私有限定
    System.out.println("previous private field is set to: " + f.get(obj));
    f.set(obj, new Date());
    System.out.println("private field is set to :" + (ReflectedObject)obj);
}

获取成员方法

  1. 批量获取成员方法
    • Field[] getMethods(), public成员方法
    • Field[] getDeclaredMethods(), 获取所有成员方法(pubic, protected, private, default)
  2. 获取单个成员方法
    • public Method getMethod(String name, Class<?>… parameterTypes), 获取单个public成员方法
    • public Method getDeclaredMethod(String name, Class<?>… parameterTypes), 获取任意成员方法
  3. 设置成员变量的值 Method –> public Object invoke(Object obj, Object… args)
    1. obj, 设置成员变量所在的对象(staic方法使用null);
    2. args, 调用方法时所传递的参数

实例:

// methods

    // public 成员方法
    public void publicMethod(String str){
        System.out.println("public 成员方法,设置str = " + str);
    }

    // protected 成员方法
    protected void protectedMethod(){
        System.out.println("protected 成员方法");
    }


    // default 成员方法
    void defaultMethod(){
        System.out.println("default 成员方法");
    }

    // private 成员方法
    private String privateMethod(int i) {
        System.out.println("private 成员方法,设置 i = " + i);
        return "returnValue";
    }

    // main 方法

    public static void main(String[] args) {
        System.out.println("main方法");
    }

测试实例

@Test
public void reflectedMethods() throws Exception {
    Class roClass = Class.forName("ReflectionLearn.ReflectedObject");

    // getMethods()
    System.out.println("getMethods()");
    Method[] methods = roClass.getMethods();
    for (Method m : methods) {
        System.out.println(m);
    }

    // getDeclaredMethods()
    System.out.println("\ngetDeclaredMethods()");
    methods = roClass.getDeclaredMethods();
    for (Method m : methods) {
        System.out.println(m);
    }

    // getMethod()
    System.out.println("\ngetMethod()");
    Method m = roClass.getMethod("publicMethod", String.class);
    Object obj = roClass.getConstructor().newInstance();
    m.invoke(obj, "string value");


    // getDeclaredMethod()
    System.out.println("\ngetDeclaredMethod");
    m = roClass.getDeclaredMethod("privateMethod", int.class);
    m.setAccessible(true); // 暴力反射,解除私有限定
    m.invoke(obj, 2);

    // get and invoke main method
    /* main方法为static静态方法,因此在调用时可以使用null作为对象
    * invoke的第二个参数的要求为一个String数组,但是这里我们提供的是一个 Object[]
    * 这是因为在jdk1.4时要求为数组,就算在jdk1.5修改为可变参数之后,也要考虑向下兼容的问题
    * 单同时直接使用 new String[]{"a", "b", "c"}会被jdk1.5之后的版本拆成三个对象,因此需要需要强制转换成Object[]
    */
    System.out.println("\nget and invoke main method");
    m = roClass.getMethod("main", String[].class);
    m.invoke(null, (Object) new String[]{"a", "b", "c"});

}

反射的其他使用

  • 通过反射运行配置文件的内容

    className = ReflectionLearn.ReflectedObject
    methodName = publicMethod
    

    测试实例

    public static String getConfig(String key) throws IOException {
            Properties properties = new Properties();
            FileReader in = new FileReader("/Users/runfeng/IdeaProjects/JavaSec/src/ReflectionLearn/config.txt");
            properties.load(in);
            in.close();
            return properties.getProperty(key);
        }
        @Test
        public void loadConfig() throws Exception {
            Class roClass = Class.forName(getConfig("className"));
            Method m = roClass.getMethod(getConfig("methodName"), String.class);
            m.invoke(roClass.getConstructor().newInstance(), "String value");
        }
    

    使用配置文件的好处就在于,如果我们需要升级整个系统,不需要旧的类的时候,就可以重新写一个新的类,并且修改配置文件中的内容即可,而不需要修改源代码中的名称

  • 通过反射越过泛型检查

    我们可以通过反射来绕过泛型检查,从而向String类型的集合中添加一个Integer类型的值

    @Test
    public void bypassGeneric() throws Exception {
        ArrayList<String> strList = new ArrayList<>();
        strList.add("a");
        strList.add("b");
    
        Class listClass = strList.getClass();
        Method m = listClass.getMethod("add", Object.class);
        m.invoke(strList, 100);
    
        for(Object obj : strList){
            System.out.println(obj);
        }
    
    }
    

为什么要使用反射的三大原因

本小结旨在回答一个问题,为什么不使用new来创建对象,进而调用和操作一个对象的属性?

不得已而为之

当我们没办法使用new来实例化对象的时候,就只能使用反射来操作对象

  • 当调用来自网络的 .class 文件时,因为没有 .java 代码,因此无法通过new来进行实例化对象
  • 注解 (Annotation) - 注解本身仅仅是起到标记作用,它需要利用反射机制,根据注解标记去调用注解解释器,执行行为。如果没有反射机制,注解并不比注释更有用。

动态加载

  • 通过反射的方式加载类,可以减少JVM启动时的工作,进而减少JVM的启动时间,同时可以动态地加载所需要的对象(多态)
  • 提高协作效率,例如两个程序员共同开发的时候,不要等待别人写好.java源文件,而只需要使用反射即可实现对象的实例化以及操作

增加代码的灵活度,避免将代码写死

我们之前提到了使用配置文件的形式来选择加载的类以及操作对象中的属性就体现了这种好处。

通过使用配置文件,我们可以讲代码解耦,减少修改源码,进而减少重新编译的情况

许多通用的开发框架就用到了这样的思想

反射的缺点

  • 性能开销
    • 由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能要比非反射操作的性能要差,应该在性能敏感的应用程序中频繁调用的代码段中避免。
  • 破坏封装性
    • 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
  • 内部曝光
    • 由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,所以反射的使用可能会导致意想不到的副作用,这可能会导致代码功能失常并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为。

Reference

Java Reflection API

Java基础篇:反射机制详解

Java反射:用最直接的大白话来聊一聊Java中的反射机制

为什么需要Java反射?

java为何用反射,举例说明

java 反射机制,为什么要用反射?

Licensed under CC BY-NC-SA 4.0
Last updated on Oct 25, 2022 17:49 CST
comments powered by Disqus
Cogito, ergo sum
Built with Hugo
Theme Stack designed by Jimmy