Skip to main content

reflection

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

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

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

框架的灵魂

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

在spring springboot mybatis中都使用了

优点

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

缺点

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

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

Class

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

public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<?> clazz = Class.forName("com.jasper.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));
System.out.println(o);
}
}
//jasper
//Person(id=0, name=jasper, age=0)

获取Class对象

  • Class.forName() @param className the fully qualified name of the desired class.
  • 类名.class 适合在编译前就知道操作的Class
  • 对象.getClass()
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);

  • 类加载器(系统类加载器 应用类加载器)
Class<?> clazz1 = ClassLoader.getSystemClassLoader().loadClass("com.jasper.reflection.Person");

反射类的对象实例化

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

获取构造方法

// 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);

获取字段

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

获取方法

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

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)

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

output

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

invoke method

image.png

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

Method

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

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

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代理

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

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 方法。

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

示例

假设有以下类结构:

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,方便读取文件内容(如配置文件、图片、模板等)。


✅ 常见用法

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

📘 参数说明

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

示例讲解

假设资源路径为:

src/main/resources/config/app.properties

1. 绝对路径(推荐)

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

从 classpath 根目录查找,最常用

2. 相对路径(不推荐)

假如 MyClasscom.example.util 包中:

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

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


❗注意事项

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

示例1

rocketmq dashboard 源码

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