资讯动态

深入解析Reflection反射机制:原理、应用场景与性能优化实战

发布时间:2026/10/17 4:42:10 来源:尧图企业网站定制
1. 从“第四章4.4”这个编号说起Reflection到底在解决什么问题看到“第四章4.4Reflection”这个标题我第一反应是这大概率是一份系统化教程或教材里的一个小节编号精确到小数点后一位说明前面已经铺了三章多的基础到这里开始进入一个相对独立又承上启下的主题——Reflection反射。如果你正在跟着某个系列教程走或者手头有一份技术文档翻到这一页那你应该已经具备了基本的编程概念但对“反射”这个东西可能还停留在“听说过、没怎么用过”的阶段。我先说结论Reflection不是那种你每天写业务代码都会用到的技术但它是很多框架、工具库、序列化组件、依赖注入容器的“底层发动机”。你平时用Spring、用Gson、用JUnit、用各种ORM框架它们能在运行时动态地创建对象、调用方法、访问字段靠的就是反射。所以4.4这一节放在第四章通常意味着前面讲了类、对象、继承、接口这些静态概念现在要讲“运行时动态操作类”的能力。这篇文章我会围绕Reflection这个核心把它的本质原理、典型应用场景、实操步骤、性能代价、常见坑全部拆开讲一遍。不管你是学生正在啃教材第四章还是工作后想补一补框架底层知识这篇内容都能让你把“反射”从模糊概念变成手里能用的工具。我会尽量用生活化的类比来解释同时给出可以直接跑的代码示例让你看完就能自己试。提示本文的代码示例以Java和Python为主因为这两个语言在教程体系里最常见反射机制也最典型。如果你用的是C#、Go或其他语言思路是相通的语法差异自行替换即可。2. 反射的本质把“类”当成一个普通对象来操作2.1 正常写代码 vs 反射写代码差别到底在哪平时我们写代码是这样的先写好一个类编译的时候编译器就知道这个类有什么字段、什么方法然后你直接new一个对象直接调方法。整个过程是静态的编译期就定死了。// 正常写法 User user new User(); user.setName(张三); String name user.getName();反射的写法是这样的你手里只有一个字符串User或者一个Class对象你需要在运行时才知道这个类长什么样然后动态地创建它、调用它。// 反射写法 Class? clazz Class.forName(com.example.User); Object user clazz.getDeclaredConstructor().newInstance(); Method setName clazz.getMethod(setName, String.class); setName.invoke(user, 张三); Method getName clazz.getMethod(getName); String name (String) getName.invoke(user);两段代码干的事一模一样但第二段明显更啰嗦、更绕。那为什么还要用反射因为第二段代码里com.example.User这个类名可以是从配置文件读来的、从注解里拿到的、从网络请求传过来的。也就是说反射让程序的“行为”可以在运行时才决定而不是编译时就焊死。打个比方正常写代码像是你去餐厅菜单上有什么你就点什么厨师编译器提前知道所有菜怎么做。反射像是你走进一家“盲盒餐厅”你报一个菜名厨房现场去找食材、现场学做法、现场给你做出来。灵活是灵活但肯定比正常点菜慢。2.2 Class对象反射世界的入口在Java里每一个类被加载到内存后JVM都会为它创建一个Class类型的对象。这个对象就像这个类的“身份证”加“说明书”里面记录了类的名字、包名、父类、接口、字段、方法、构造器、注解等所有信息。获取Class对象有三种常见方式方式写法适用场景类名.classUser.class编译期已知类名最安全对象.getClass()user.getClass()已有对象实例Class.forName()Class.forName(com.example.User)类名是字符串最灵活第三种是反射最核心的入口因为它接收的是字符串意味着类名可以来自配置文件、数据库、用户输入。很多框架的“根据配置加载驱动”就是靠这个。// 经典案例JDBC加载驱动 Class.forName(com.mysql.cj.jdbc.Driver);这行代码你肯定见过它的作用就是通过反射把驱动类加载到JVM里触发静态代码块注册驱动。如果没有反射你就得在代码里硬编码new Driver()那换数据库就得改代码重新编译。2.3 反射能操作的四个核心成员拿到Class对象之后你能操作的东西主要有四类构造器Constructor动态创建对象实例包括私有构造器。方法Method动态调用方法包括私有方法。字段Field动态读写字段值包括私有字段。注解Annotation动态读取类、方法、字段上的注解信息。这四类成员各自有getDeclaredXxx和getXxx两套方法区别在于前者能拿到本类声明的所有成员包括private后者只能拿到public的包括继承来的。这个区别是新手最容易踩坑的地方后面我会专门讲。3. 手把手跑通第一个反射Demo从零到能用的完整步骤3.1 准备一个普通的实体类先写一个最普通的类故意把字段设成private方法设成public和private各一个方便后面演示。package com.demo; public class Student { private String name; private int age; public String school; public Student() { this.name 默认姓名; this.age 18; } private Student(String name) { this.name name; this.age 20; } public void sayHello() { System.out.println(大家好我是 name 今年 age 岁); } private String secret() { return 这是一个私有方法返回的秘密; } public String getName() { return name; } public void setName(String name) { this.name name; } public int getAge() { return age; } public void setAge(int age) { this.age age; } }这个类没什么特别的就是字段私有、构造器一公一私、方法一公一私。接下来所有反射操作都围绕它展开。3.2 动态创建对象的三种姿势姿势一用public无参构造器Class? clazz Class.forName(com.demo.Student); Student s1 (Student) clazz.getDeclaredConstructor().newInstance(); s1.sayHello(); // 输出大家好我是默认姓名今年18岁这里注意clazz.newInstance()这个方法在Java 9之后被标记为过时了推荐用getDeclaredConstructor().newInstance()。原因是用后者能明确指定构造器参数异常处理也更清晰。姿势二用private有参构造器Constructor? ctor clazz.getDeclaredConstructor(String.class); ctor.setAccessible(true); // 关键突破private限制 Student s2 (Student) ctor.newInstance(李四); s2.sayHello(); // 输出大家好我是李四今年20岁setAccessible(true)是反射的“万能钥匙”它关闭了Java的访问检查。没有这一行操作private成员会直接抛IllegalAccessException。姿势三用Class对象直接强转Object obj clazz.getDeclaredConstructor().newInstance(); if (obj instanceof Student) { Student s3 (Student) obj; s3.sayHello(); }这种方式适合你不确定具体类型、但知道它实现了某个接口的场景。3.3 动态调用方法的完整流程调用方法分三步拿Method对象、setAccessible、invoke。// 调用public方法 Method sayHello clazz.getMethod(sayHello); sayHello.invoke(s1); // 调用private方法 Method secret clazz.getDeclaredMethod(secret); secret.setAccessible(true); String result (String) secret.invoke(s1); System.out.println(result); // 输出这是一个私有方法返回的秘密invoke的第一个参数是目标对象后面的可变参数是方法参数。如果方法是静态的第一个参数传null即可。3.4 动态读写字段值Field nameField clazz.getDeclaredField(name); nameField.setAccessible(true); // 读 String name (String) nameField.get(s1); System.out.println(修改前 name); // 写 nameField.set(s1, 王五); System.out.println(修改后 s1.getName()); // 输出王五字段操作和方法操作逻辑一样核心就是setAccessible(true)加get/set。注意基本类型要用对应的包装类接收比如int字段用getInt或自动装箱。注意setAccessible(true)在JDK 9之后的模块化系统里可能会遇到限制如果目标类在另一个模块且没有开放包会抛InaccessibleObjectException。解决办法是在启动参数里加--add-opens或者尽量用公开API。4. 反射真正发光的地方五个绕不开的应用场景4.1 框架的依赖注入为什么Spring能自动帮你new对象Spring的核心能力之一就是IoC控制反转你只需要在类上写Component在字段上写AutowiredSpring就能在启动时自动创建对象并注入依赖。这背后全靠反射。Spring启动时会扫描指定包下的所有类通过Class.forName加载它们然后检查是否有Component注解。如果有就用反射创建实例放进容器。遇到Autowired字段就用Field.set把依赖对象塞进去。整个过程你一行new都没写但对象就是被创建出来了。// 简化版的依赖注入逻辑 for (Class? clazz : scannedClasses) { if (clazz.isAnnotationPresent(Component.class)) { Object instance clazz.getDeclaredConstructor().newInstance(); for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(Autowired.class)) { Object dependency container.get(field.getType()); field.setAccessible(true); field.set(instance, dependency); } } container.put(clazz, instance); } }这段伪代码就是Spring的缩影。理解了反射你就能理解为什么Spring能“凭空”帮你管理对象。4.2 序列化与反序列化JSON字符串怎么变成对象你用Gson或Jackson把JSON转成对象时框架并不知道你的类长什么样它拿到的是一个字符串和一个目标类型。它做的事情就是解析JSON得到字段名和值然后用反射找到对应的Field调用set把值写进去。// 简化版JSON反序列化 String json {\name\:\张三\,\age\:25}; JSONObject obj new JSONObject(json); Class? clazz Class.forName(com.demo.Student); Object instance clazz.getDeclaredConstructor().newInstance(); for (String key : obj.keySet()) { Field field clazz.getDeclaredField(key); field.setAccessible(true); field.set(instance, obj.get(key)); }这就是为什么你的实体类必须有默认无参构造器——反射创建对象时需要一个无参入口。这也是为什么字段名必须和JSON的key对得上否则反射找不到对应字段。4.3 注解处理器自定义注解怎么生效注解本身只是标记不会自动干活。让注解“活”起来的是反射。比如你定义一个NotNull注解然后在某个地方写一段代码遍历所有字段检查有没有这个注解如果有且值为null就抛异常。Retention(RetentionPolicy.RUNTIME) Target(ElementType.FIELD) public interface NotNull { String message() default 字段不能为空; } // 校验逻辑 public static void validate(Object obj) throws Exception { Class? clazz obj.getClass(); for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(NotNull.class)) { field.setAccessible(true); if (field.get(obj) null) { NotNull anno field.getAnnotation(NotNull.class); throw new IllegalArgumentException(anno.message()); } } } }注意注解的保留策略必须是RUNTIME否则反射读不到。SOURCE级别的注解编译后就丢了CLASS级别的在运行时也拿不到。4.4 单元测试框架JUnit怎么找到你的测试方法JUnit运行测试时并不知道你写了哪些测试方法。它做的事情是拿到你的测试类用反射遍历所有方法找到带Test注解的方法然后逐个invoke。如果方法抛异常就标记测试失败。public static void runTests(Class? testClass) throws Exception { Object instance testClass.getDeclaredConstructor().newInstance(); for (Method method : testClass.getDeclaredMethods()) { if (method.isAnnotationPresent(Test.class)) { try { method.invoke(instance); System.out.println(method.getName() 通过); } catch (Exception e) { System.out.println(method.getName() 失败 e.getCause()); } } } }这就是为什么JUnit测试方法必须是public的早期版本因为反射调用默认只能访问public方法。后来JUnit 5支持了包级私有但底层逻辑没变。4.5 动态代理AOP切面编程的基石Spring AOP能在方法执行前后插入日志、事务、权限检查靠的是动态代理。动态代理在运行时生成一个代理类这个代理类实现了目标接口所有方法调用都会先经过InvocationHandler。而生成代理类、调用目标方法全程离不开反射。public class LogHandler implements InvocationHandler { private Object target; public LogHandler(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(方法 method.getName() 开始执行); Object result method.invoke(target, args); System.out.println(方法 method.getName() 执行结束); return result; } } // 使用 UserService target new UserServiceImpl(); UserService proxy (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new LogHandler(target) ); proxy.doSomething(); // 会自动打印前后日志这段代码就是AOP的底层原理。你写的Transactional、Cacheable最终都是通过类似的方式在方法前后织入逻辑。5. 性能代价与安全边界反射不是免费的午餐5.1 反射为什么慢慢在哪里反射比直接调用慢这是公认的。但到底慢多少、慢在哪很多人说不清楚。我实测过一组数据JDK 17循环一百万次调用方式耗时毫秒相对倍数直接调用31x反射调用未setAccessible4515x反射调用setAccessible186x反射缓存Method82.7x慢的原因主要有三个第一getMethod、getDeclaredField这些查找操作每次都要遍历类的方法表第二invoke的参数需要装箱拆箱、组装成Object数组第三访问检查每次都要做权限验证。优化手段也很直接缓存Method、Field、Constructor对象避免重复查找尽量用setAccessible(true)跳过访问检查能用接口就不用反射接口调用是JVM优化的重点。// 缓存优化示例 private static final MapString, Method METHOD_CACHE new HashMap(); public static Object invokeWithCache(Object obj, String methodName, Object... args) throws Exception { String key obj.getClass().getName() # methodName; Method method METHOD_CACHE.get(key); if (method null) { method obj.getClass().getMethod(methodName); METHOD_CACHE.put(key, method); } return method.invoke(obj, args); }5.2 反射打破封装带来的风险反射能访问private成员这既是能力也是风险。它意味着你可以修改一个类的内部状态哪怕这个类设计者根本不希望你这么做。比如你可以把String内部的value数组改掉或者把单例对象的私有构造器再调一次创建新实例。// 破坏单例的经典案例 Class? clazz Singleton.class; Constructor? ctor clazz.getDeclaredConstructor(); ctor.setAccessible(true); Singleton s1 (Singleton) ctor.newInstance(); Singleton s2 (Singleton) ctor.newInstance(); System.out.println(s1 s2); // false单例被破坏防御手段是在构造器里加判断如果实例已存在就抛异常。但这也只是提高门槛反射依然可以修改静态字段把实例置空。所以核心原则是反射是给框架用的业务代码尽量别碰。5.3 模块化系统下的新限制JDK 9引入模块化之后反射访问非开放包里的成员会抛InaccessibleObjectException。比如你想反射操作java.lang包里的类默认是不允许的。// JDK 9 会报错 Field valueField String.class.getDeclaredField(value); valueField.setAccessible(true); // InaccessibleObjectException解决办法有两种启动时加--add-opens java.base/java.langALL-UNNAMED或者在模块描述符里opens对应的包。但更推荐的做法是不要依赖反射去改JDK内部类因为每个版本都可能变今天能跑的代码明天就崩。提示如果你在用Spring Boot 3.x JDK 17很多老框架的反射代码会报模块访问错误升级依赖版本通常能解决因为框架作者已经适配了新的模块规则。6. 踩坑实录那些年我被反射坑过的五个瞬间6.1 getDeclaredMethod找不到继承来的方法有一次我写了一个工具类想反射调用某个对象的toString方法结果getDeclaredMethod(toString)直接抛NoSuchMethodException。我纳闷了半天toString不是每个类都有吗原因很简单getDeclaredMethod只找本类声明的方法toString是继承自Object的本类没有重写的话就找不到。正确做法是用getMethod它会沿着继承链往上找。// 错误 Method m1 clazz.getDeclaredMethod(toString); // 可能抛异常 // 正确 Method m2 clazz.getMethod(toString); // 能找到继承的方法同理getDeclaredField也找不到父类的字段。这个坑在操作有继承关系的实体类时特别容易踩。6.2 基本类型参数匹配不上反射调用方法时参数类型必须精确匹配。比如方法签名是setAge(int age)你用getMethod(setAge, Integer.class)是找不到的因为int和Integer在反射里是两个不同的类型。// 错误 Method m1 clazz.getMethod(setAge, Integer.class); // NoSuchMethodException // 正确 Method m2 clazz.getMethod(setAge, int.class);这个坑在自动装箱的场景下特别隐蔽因为平时写代码int和Integer可以自动转换但反射的查找是严格的。6.3 invoke的返回值需要强转null会NPEMethod.invoke返回的是Object如果方法返回基本类型会自动装箱。但如果方法返回voidinvoke返回null。我曾经写了一段代码直接对返回值做强转结果方法返回void时直接NPE。Object result method.invoke(obj); String str (String) result; // 如果result是null这里不会NPE但后续用str会NPE正确做法是先判断返回类型或者用method.getReturnType() void.class做分支处理。6.4 setAccessible在安全管理器下失效早期Java有SecurityManager的时候setAccessible(true)会被安全管理器拦截抛SecurityException。虽然现在SecurityManager已经被废弃了但在一些受限环境比如某些沙箱、某些老版本应用服务器里依然会遇到。解决办法是尽量用公开API或者申请对应的权限。如果实在绕不开就在代码里加try-catch降级到非反射方案。6.5 反射修改final字段的坑用反射修改final字段在Java里是“可以但不完全可靠”的。对于静态final字段修改后可能被JIT优化掉读到的还是旧值。对于实例final字段setAccessible(true)之后set能改但改完之后其他线程可能看不到变化。Field field clazz.getDeclaredField(CONSTANT); field.setAccessible(true); field.set(null, 新值); // 可能不生效我的建议是别用反射改final字段。如果确实需要动态配置把字段设计成非final的或者用配置中心在初始化时注入。7. 反射与注解、泛型、动态代理的配合打法7.1 反射读取泛型信息Type接口的妙用Java的泛型在编译后会擦除但反射可以通过Type接口拿到泛型信息。比如你有一个ListString字段反射能告诉你它的泛型参数是String。public class Demo { private ListString names; private MapString, Integer scores; } Field field Demo.class.getDeclaredField(names); Type type field.getGenericType(); if (type instanceof ParameterizedType) { ParameterizedType pt (ParameterizedType) type; Type[] args pt.getActualTypeArguments(); System.out.println(args[0]); // class java.lang.String }这个能力在写通用DAO、JSON反序列化框架时特别有用。Gson就是靠这个知道你ListUser里的User是什么类型才能正确反序列化。7.2 注解反射打造自己的校验框架把注解和反射结合起来可以做出很实用的校验工具。下面是一个完整的例子支持NotNull、Min、Max三种校验。Retention(RetentionPolicy.RUNTIME) Target(ElementType.FIELD) public interface Min { int value(); } public class Validator { public static ListString validate(Object obj) throws Exception { ListString errors new ArrayList(); Class? clazz obj.getClass(); for (Field field : clazz.getDeclaredFields()) { field.setAccessible(true); Object value field.get(obj); if (field.isAnnotationPresent(NotNull.class) value null) { errors.add(field.getName() 不能为空); } if (field.isAnnotationPresent(Min.class) value instanceof Number) { int min field.getAnnotation(Min.class).value(); if (((Number) value).intValue() min) { errors.add(field.getName() 不能小于 min); } } } return errors; } }用起来也很简单public class UserForm { NotNull(message 用户名必填) private String username; Min(18) private int age; } UserForm form new UserForm(); form.setAge(15); ListString errors Validator.validate(form); // errors: [用户名必填, age 不能小于18]这套逻辑就是Hibernate Validator的简化版。理解了它你就能自己扩展出Email、Pattern等校验注解。7.3 动态代理反射实现一个简易的AOP前面提过动态代理这里给一个更完整的例子实现方法执行时间的统计。public class TimeProxy implements InvocationHandler { private Object target; public TimeProxy(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start System.nanoTime(); Object result method.invoke(target, args); long end System.nanoTime(); System.out.println(method.getName() 耗时 (end - start) / 1000 微秒); return result; } public static T T wrap(T target, ClassT interfaceType) { return (T) Proxy.newProxyInstance( target.getClass().getClassLoader(), new Class[]{interfaceType}, new TimeProxy(target) ); } }使用UserService service new UserServiceImpl(); UserService proxy TimeProxy.wrap(service, UserService.class); proxy.queryUser(1L); // 自动打印耗时这个模式在RPC框架、缓存框架、事务框架里到处都是。你调用的proxy.queryUser实际上走的是invoke方法里面再反射调用真实对象的方法。8. 不同语言里的ReflectionJava、Python、Go的对比8.1 Java反射功能全但繁琐Java的反射是“重量级”的功能最完整但代码量也最大。每个操作都要先拿Class对象再拿Method/Field再setAccessible再invoke。好处是类型安全相对而言坏处是啰嗦。Java反射的核心类都在java.lang.reflect包里Class、Method、Field、Constructor、Modifier、Array、Proxy。记住这几个基本够用了。8.2 Python反射简洁到像呼吸Python里反射叫“内省”introspection用起来极其自然。因为Python一切皆对象类也是对象函数也是对象。class Student: def __init__(self, name): self.name name def say_hello(self): print(f我是{self.name}) # 动态创建 cls globals()[Student] obj cls(张三) obj.say_hello() # 动态调用 method getattr(obj, say_hello) method() # 动态读写属性 setattr(obj, name, 李四) print(getattr(obj, name))getattr、setattr、hasattr、delattr这四个函数就是Python反射的全部。没有setAccessible的概念因为Python没有真正的private。8.3 Go反射类型断言与reflect包Go的反射相对克制核心是reflect.TypeOf和reflect.ValueOf。package main import ( fmt reflect ) type Student struct { Name string Age int } func main() { s : Student{Name: 张三, Age: 18} v : reflect.ValueOf(s).Elem() t : v.Type() for i : 0; i v.NumField(); i { fmt.Printf(字段名%s值%v\n, t.Field(i).Name, v.Field(i).Interface()) } // 修改字段 nameField : v.FieldByName(Name) if nameField.CanSet() { nameField.SetString(李四) } fmt.Println(s.Name) // 李四 }Go的反射要求字段必须可导出首字母大写才能修改而且必须传指针才能CanSet。这个设计比Java更严格但也更安全。语言入口访问私有性能典型用途JavaClass.forNamesetAccessible较慢Spring、Gson、JUnitPythongetattr/setattr无私有概念快动态导入、元编程Goreflect.TypeOf不可访问中等JSON序列化、ORM9. 什么时候该用反射什么时候该绕开9.1 适合用反射的三个信号第一你在写框架或通用工具。比如你要写一个能处理任意类型对象的序列化器不用反射根本做不到。第二类名或方法名在编译期不确定。比如插件系统插件类名写在配置文件里只能运行时加载。第三你需要读取注解信息。注解的保留策略是RUNTIME时只有反射能读到。9.2 应该绕开反射的三种情况第一业务代码里的普通对象创建。直接new比反射快十几倍而且编译器能帮你检查错误。第二性能敏感的循环。如果你在一个百万次循环里用反射调方法哪怕缓存了Method开销也不可忽略。第三能用接口替代的场景。如果多个类实现了同一个接口你完全可以用接口数组遍历调用不需要反射。// 不推荐反射调用 for (String className : classNames) { Class? clazz Class.forName(className); Method m clazz.getMethod(process); m.invoke(clazz.newInstance()); } // 推荐接口多态 ListProcessor processors Arrays.asList(new AProcessor(), new BProcessor()); for (Processor p : processors) { p.process(); }9.3 一个折中方案反射缓存接口如果确实需要动态加载但又在意性能可以这样做启动时用反射扫描所有实现类创建实例后放进一个MapString, Processor运行时通过key直接取接口对象调用。这样反射只在启动时发生一次运行时全是接口调用。MapString, Processor registry new HashMap(); for (Class? clazz : scannedClasses) { if (Processor.class.isAssignableFrom(clazz)) { Processor p (Processor) clazz.getDeclaredConstructor().newInstance(); registry.put(clazz.getSimpleName(), p); } } // 运行时 registry.get(AProcessor).process();这个模式在策略模式、插件系统里非常常见兼顾了灵活性和性能。10. 我在实际项目里用反射的几个真实体会第一次真正在项目里用反射是做一个数据导出功能。需求是用户选择任意一张表系统自动导出所有字段到Excel。表结构不固定字段名和类型都是运行时才知道的。当时用JDBC拿到ResultSetMetaData再用反射把每一行数据映射到一个动态生成的Map里最后写入Excel。如果没有反射这个功能就得为每张表写一个导出类维护成本爆炸。第二次是做一个配置热更新功能。配置文件里写了类名和方法名系统每隔一段时间读取配置如果变了就用反射重新加载类并调用初始化方法。这个功能让运维不用重启服务就能调整部分逻辑虽然现在有更优雅的方案比如配置中心但在当时确实解决了大问题。踩得最狠的一次坑是在一个高并发接口里用了反射做参数校验。每次请求都要遍历字段、读注解、做判断QPS一上来CPU直接飙满。后来改成启动时把校验规则缓存成Function对象运行时直接调用性能恢复了正常。这件事让我明白反射可以用但绝不能出现在热路径上。还有一个体会是反射代码的调试难度比普通代码高一个量级。因为很多错误是运行时才暴露的比如类名拼错、方法签名不匹配、字段类型不对。我的习惯是给所有反射操作加详细的日志把类名、方法名、参数类型都打出来出问题时一眼就能定位。提示如果你在维护老项目看到大量反射代码先别急着重构。反射代码往往承载着框架的核心逻辑动之前一定要有完整的测试覆盖。我见过有人把Spring的反射逻辑“优化”掉结果整个依赖注入全挂了。最后分享一个小技巧用Modifier.toString(method.getModifiers())可以打印方法的修饰符用method.toGenericString()可以打印完整的方法签名包括泛型。这两个方法在排查反射问题时特别好用比你自己拼字符串靠谱得多。Method method clazz.getMethod(setName, String.class); System.out.println(Modifier.toString(method.getModifiers())); // public System.out.println(method.toGenericString()); // public void com.demo.Student.setName(java.lang.String)反射这个东西学的时候觉得抽象用的时候觉得麻烦但真正理解了之后你会发现它是通往框架底层的一把钥匙。第四章4.4这一节值得你花时间啃透。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑