Skip to content

反射

一般我们在使用某个类之前就确定了是什么类,直接可以new来进行实例化来进行操作

反射就是一开始不知道要初始化的类,不能使用new来实例化

它允许在运行时检查和操作类、接口、字段和方法

框架的灵魂

通过反射你可以获取任意一个类的所有属性和方法,还可以调用这些方法

在spring springboot mybatis中都使用了

优点

  • 代码更加灵活,为框架提供了便利通过配置文件来加载不同的对象,调用不同的方法
  • 在运行时有了分析操作类的能力

缺点

  • 破坏了封装,允许访问private method and param
  • 增加了安全问题,无视泛型参数的检查
  • 性能相对差一点

每一个类的实例都会对应一个Class对象,这个对象由jvm生成来获取整个类的结构信息

Class

Class:代表一个类或接口,包含了类的结构信息(如名称、构造函数、方法、字段等)。通过 Class 对象,可以获取类的元数据并操作类的实例

java
package com.jasper.reflection;

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

public class Demo1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> clazz = Class.forName("com.jasper.reflection.Person");
        Constructor<?> constructor = clazz.getConstructor();
        Object o = constructor.newInstance();
        Method setName = clazz.getMethod("setName", String.class);
        setName.invoke(o,"jasper");
        Method getName = clazz.getMethod("getName");
        System.out.println(getName.invoke(o));
    }
}
java
package com.jasper.reflection;

public class Person {
    private String name;
    private String age;

    public Person() {
    }

     private Person(String name, String age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public void print(){
        System.out.println("print method");
    }

    private void print(String name){
        System.out.println(name);
    }
}

获取Class对象

  • Class.forName() @param className the fully qualified name of the desired class.
    java
    @CallerSensitive
        public static Class<?> forName(String className)
                    throws ClassNotFoundException {
            Class<?> caller = Reflection.getCallerClass();
            return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
        }
  • 类名.class 适合在编译前就知道操作的Class
  • 对象.getClass()
java
Class<?> clazz = Class.forName("com.jasper.reflection.Person");
        Class<Person> clazz1 = Person.class;
        Person person = new Person();
        Class<? extends Person> clazz2 = person.getClass();
        System.out.println(clazz == clazz1);
        System.out.println(clazz1 == clazz2);
  • 类加载器(系统类加载器 应用类加载器)
java
Class<?> clazz1 = ClassLoader.getSystemClassLoader().loadClass("com.jasper.reflection.Person");

创建反射类的对象

  • Class对象的newInstance方法
  • Constructor对象的newInstance method
java
Object o = clazz.newInstance();
        Object o1 = clazz.getConstructor().newInstance();
        System.out.println(o);
        System.out.println(o1);

获取构造方法

java
//        Constructor<?> constructor = clazz.getConstructor(String.class, String.class);
//        System.out.println(constructor);
//        no param 无参constructor
        System.out.println(clazz.getConstructor());

//        include private and protected method
        Constructor<?> declaredConstructor = clazz.getDeclaredConstructor();
        System.out.println("declaredConstructor = " + declaredConstructor);
        Constructor<?> declaredConstructor1 = clazz.getDeclaredConstructor(String.class, String.class);
        System.out.println("declaredConstructor1 = " + declaredConstructor1);
//        get all public constructor
        Constructor<?>[] constructors = clazz.getConstructors();
        System.out.println("constructors = " + constructors);
//        get all constructor include private
        Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
        System.out.println("declaredConstructors = " + declaredConstructors);

获取字段

java
Method setNameMethod = clazz.getMethod("setName", String.class);
Method getNameMethod = clazz.getMethod("getName");

获取方法

java
Method print = clazz.getMethod("print");
        System.out.println("print = " + print);
        Method print1 = clazz.getDeclaredMethod("print", String.class);
        System.out.println("print1 = " + print1);
        print1.invoke(o,"jasper");

output

java
print = public void com.jasper.reflection.Person.print()
print1 = private void com.jasper.reflection.Person.print(java.lang.String)
Exception in thread "main" java.lang.IllegalAccessException: Class com.jasper.reflection.Demo2 can not access a member of class com.jasper.reflection.Person with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
	at java.lang.reflect.Method.invoke(Method.java:491)
	at com.jasper.reflection.Demo2.main(Demo2.java:42)

如果想反射访问私有字段和(构造)方法时,需要使用Constructor/field/method.setAccessible(true)

java
Method print1 = clazz.getDeclaredMethod("print", String.class);
        System.out.println("print1 = " + print1);
        print1.setAccessible(true);
        print1.invoke(o,"jasper");

output

java
print1 = private void com.jasper.reflection.Person.print(java.lang.String)
jasper

invoke method

image.png

root 属性通常用于实现方法调用的优化。它用于维护 Method 对象之间的关联关系,特别是在涉及方法访问器(MethodAccessor)的情况下

Method

java
@CallerSensitive
    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
//是否进行权限检查
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
//获取方法访问器
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
//获取方法访问器
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }
java

private MethodAccessor acquireMethodAccessor() {
        // First check to see if one has been created yet, and take it
        // if so
        MethodAccessor tmp = null;
        if (root != null) tmp = root.getMethodAccessor();
        if (tmp != null) {
            methodAccessor = tmp;
        } else {
            // Otherwise fabricate(制造) one and propagate it up to the root
            tmp = reflectionFactory.newMethodAccessor(this);
            setMethodAccessor(tmp);
        }

        return tmp;
    }

ReflectionFactory

java
public MethodAccessor newMethodAccessor(Method var1) {
        checkInitted();
        if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
            return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
        } else {
            NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
            DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
            var2.setParent(var3);
            return var3;
        }
    }

代理模式,将NativeMethodAccessorImpl给DelegatingMethodAccessorImpl代理

代理模式可以在本地实现和动态实现之间切换

java
package sun.reflect;

import java.lang.reflect.InvocationTargetException;

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;

    DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
        this.setDelegate(var1);
    }

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        return this.delegate.invoke(var1, var2);
    }

    void setDelegate(MethodAccessorImpl var1) {
        this.delegate = var1;
    }
}

将NativeMethodAccessorImpl给了DelegatingMethodAccessorImpl的delegate属性

newMethodAccessor最后返回了DelegatingMethodAccessorImpl

ma.invoke 进入了DelegatingMethodAccessorImpl的invoke方法

delegate是MethodAccessorImpl

image.png

这里的 delegate 其实是一个 NativeMethodAccessorImpl 对象,所以这里会进入 NativeMethodAccessorImpl 的 invoke 方法。

java
class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private final Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method var1) {
        this.method = var1;
    }

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
            MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
            this.parent.setDelegate(var3);
        }

        return invoke0(this.method, var1, var2);
    }

    void setParent(DelegatingMethodAccessorImpl var1) {
        this.parent = var1;
    }

    private static native Object invoke0(Method var0, Object var1, Object[] var2);
}

在 NativeMethodAccessorImpl 的 invoke 方法里,其会判断调用次数是否超过阀值(numInvocations)。如果超过该阀值15,那么就会生成另一个MethodAccessor 对象,并将原来 DelegatingMethodAccessorImpl 对象中的 delegate 属性指向最新的 MethodAccessor 对象

Native 版本一开始启动快,但是随着运行时间边长,速度变慢。Java 版本一开始加载慢,但是随着运行时间边长,速度变快。正是因为两种存在这些问题,所以第一次加载的时候我们会发现使用的是 NativeMethodAccessorImpl 的实现,而当反射调用次数超过 15 次之后,则使用 MethodAccessorGenerator 生成的 MethodAccessorImpl 对象去实现反射

方法

在Java反射API中,Class类提供了两种方法来获取类中的方法信息:getDeclaredMethods()getMethods()。这两个方法的主要区别在于它们返回的方法集合不同。

getDeclaredMethods()

  • 返回值getDeclaredMethods()方法返回Method对象的数组,这些对象反映了由该Class对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法
  • 使用场景:当你想要获取一个类中声明的所有方法,包括私有方法,而不关心其父类或接口的方法时,使用getDeclaredMethods()

getMethods()

  • 返回值getMethods()方法返回一个Method对象数组,这些对象反映了由该Class对象表示的类或接口的所有公共方法,这包括它自身声明的和从它的所有父类继承的公共方法。
  • 使用场景:当你只对获取一个类的所有公共接口,包括其父类的公共方法感兴趣时,使用getMethods()

示例

假设有以下类结构:

java
public class SuperClass {
    public void publicMethod() {}
    protected void protectedMethod() {}
    private void privateMethod() {}
}

public class SubClass extends SuperClass {
    public void publicSubMethod() {}
    private void privateSubMethod() {}
}
  • 使用SubClass.class.getDeclaredMethods()将只返回SubClass中声明的方法,即publicSubMethod()privateSubMethod(),不论它们的访问权限如何。
  • 使用SubClass.class.getMethods()将返回SubClass以及其超类SuperClass中的所有公共方法,即publicMethod()publicSubMethod()

总结

  • 使用getDeclaredMethods()获取一个类中定义的所有方法,不包括父类方法。
  • 使用getMethods()获取类的所有公共方法,包括其继承的方法。

选择哪一个方法取决于你的具体需求,是否需要访问私有方法或者是只关心公共接口。

class.getResourceAsStream

在 Java 中,getResourceAsStream(String name) 方法用于从 classpath(类路径) 中加载资源文件,并返回一个 InputStream,方便读取文件内容(如配置文件、图片、模板等)。


✅ 常见用法:

java
InputStream is = MyClass.class.getResourceAsStream("/config.properties");

📘 参数说明:

  • 路径写法(重点)
    • /xxx从 classpath 根目录开始查找(绝对路径)
    • xxx相对于当前类所在包路径查找(相对路径)

示例讲解:

假设资源路径为:

src/main/resources/config/app.properties

1. 绝对路径(推荐):

java
InputStream is = MyClass.class.getResourceAsStream("/config/app.properties");

从 classpath 根目录查找,最常用

2. 相对路径(不推荐):

假如 MyClasscom.example.util 包中:

java
InputStream is = MyClass.class.getResourceAsStream("app.properties");

这会去找:com/example/util/app.properties


❗注意事项:

  • getClass().getResourceAsStream(...)ClassLoader.getResourceAsStream(...) 不一样
    • getClass().getResourceAsStream("/xxx"):从 classpath 根路径查找
    • ClassLoader.getResourceAsStream("xxx"):默认就是从根路径查找,不需要 /

示例

rocketmq dashboard 源码

java
    public AbstractFileStore(RMQConfigure configure, String fileName) {
        filePath = configure.getRocketMqDashboardDataPath() + File.separator + fileName;
        // 文件不存在就去读取类路径下的配置文件 
        if (!new File(filePath).exists()) {
            // Use the default path
            InputStream inputStream = getClass().getResourceAsStream("/" + fileName);
            if (inputStream == null) {
                log.error(String.format("Can not found the file %s in Spring Boot jar", fileName));
                System.exit(1);
            } else {
                try {
                    load(inputStream);
                } catch (Exception e) {
                    log.error("fail to load file {}", filePath, e);
                } finally {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        log.error("inputStream close exception", e);
                    }
                }
            }
        } else {
            log.info(String.format("configure file is %s", filePath));
            load();
            watch();
        }
    }