
1. 引言反射Reflection是 Java 语言中一个强大且颇具争议的特性。它允许程序在运行时动态地获取类的信息、创建对象、调用方法和访问字段而这一切在编译期并不需要知道目标类的具体类型。正是这种「动态性」让 Spring、MyBatis、Hibernate 等众多框架得以实现配置驱动、注解解析和对象映射等核心能力。本文将从反射的基本概念讲起逐步深入 Class 对象的获取、构造器操作、方法调用、字段访问并结合实战场景分析其性能问题与使用注意事项帮助你在实际开发中正确、高效地使用反射。2. 反射是什么在 Java 中通常的编程方式是编译期确定类型通过new关键字创建对象通过对象直接调用方法。这种方式称为静态编译代码简洁且运行高效。而反射则是反过来把原本在编译期完成的工作推迟到运行期。程序可以在运行时拿到任意一个类的Class对象进而获取类的全限定名、包名、修饰符、父类、接口等元信息获取类中声明的构造器、方法、字段动态创建对象即使构造器是私有的动态调用方法即使方法是私有的动态读写字段即使字段是私有的获取并解析类、方法、字段上的注解。用一句话概括反射是在运行时检查和操作类的能力。它把 Java 从「编译期类型安全」的牢笼中解放出来换取了极大的灵活性。3. 反射的核心Class 对象反射的一切都始于Class对象。JVM 在加载某个类时会为其在方法区Java 8 之后为元空间创建一个唯一的Class实例用来保存该类的类型信息。我们通常有三种方式获取它// 方式一通过类的全限定名最常用适合配置文件场景Class?clazz1Class.forName(com.example.User);// 方式二通过类字面量编译期已知类型性能最好ClassUserclazz2User.class;// 方式三通过对象实例UserusernewUser();Class?extendsUserclazz3user.getClass();三者获取到的是同一个Class对象System.out.println(clazz1clazz2);// trueSystem.out.println(clazz2clazz3);// true拿到Class对象后就可以读取各种元信息System.out.println(类名clazz.getName());System.out.println(简单类名clazz.getSimpleName());System.out.println(包名clazz.getPackageName());System.out.println(是否接口clazz.isInterface());System.out.println(是否注解clazz.isAnnotation());System.out.println(父类clazz.getSuperclass());System.out.println(实现的接口Arrays.toString(clazz.getInterfaces()));4. 通过反射创建对象4.1 使用 Class.newInstance()这是最古老的方式要求目标类提供无参构造器Class?clazzClass.forName(com.example.User);Useruser(User)clazz.newInstance();但由于它无法传递构造参数且抛出的异常类型不友好从 Java 9 开始已被标记为过时官方推荐使用Constructor。4.2 使用 Constructor 对象先通过getConstructor或getDeclaredConstructor获取构造器再调用newInstancepublicclassUser{privateStringname;privateintage;publicUser(){}publicUser(Stringname,intage){this.namename;this.ageage;}}// 获取带两个参数的公有构造器ConstructorUserconstructorUser.class.getConstructor(String.class,int.class);Useruserconstructor.newInstance(张三,20);System.out.println(user);如果需要访问私有构造器则使用getDeclaredConstructor并调用setAccessible(true)ConstructorUserprivateConstructorUser.class.getDeclaredConstructor(String.class);privateConstructor.setAccessible(true);// 突破访问权限UseruserprivateConstructor.newInstance(李四);需要注意的是从 Java 9 模块化之后setAccessible(true)对 JDK 内部类的访问受到了更严格的限制但对开发者自己编写的类依然有效。5. 通过反射调用方法方法与构造器类似先获取Method对象再调用invokepublicclassCalculator{publicintadd(inta,intb){returnab;}privateStringformat(intresult){return结果是result;}}CalculatorcalculatornewCalculator();// 调用公有方法MethodaddCalculator.class.getMethod(add,int.class,int.class);Objectresultadd.invoke(calculator,3,5);System.out.println(result);// 8// 调用私有方法MethodformatCalculator.class.getDeclaredMethod(format,int.class);format.setAccessible(true);Objectmessageformat.invoke(calculator,8);System.out.println(message);// 结果是8invoke的第一个参数是目标对象实例后面的参数是方法的实参。如果是静态方法第一个参数可以传null。6. 通过反射访问字段字段访问使用Field对象publicclassUser{privateStringname默认姓名;publicintage18;}UserusernewUser();// 获取公有字段FieldageFieldUser.class.getField(age);intageageField.getInt(user);System.out.println(修改前 ageage);// 18ageField.setInt(user,25);// 获取私有字段FieldnameFieldUser.class.getDeclaredField(name);nameField.setAccessible(true);Stringname(String)nameField.get(user);System.out.println(修改前 namename);// 默认姓名nameField.set(user,张三);System.out.println(修改后user);getField只能获取public字段包括父类继承的公有字段getDeclaredField可以获取本类声明的所有字段不含继承包括私有字段。7. 反射读取与解析注解反射是注解机制的基石。框架之所以能识别Component、Autowired、Table等注解靠的就是反射Retention(RetentionPolicy.RUNTIME)Target(ElementType.TYPE)publicinterfaceTableName{Stringvalue();}TableName(t_user)publicclassUser{}ClassUserclazzUser.class;if(clazz.isAnnotationPresent(TableName.class)){TableNametableNameclazz.getAnnotation(TableName.class);System.out.println(表名tableName.value());}方法、字段上的注解解析方式类似通过Method.getAnnotation或Field.getAnnotation即可。需要注意的是注解的Retention必须是RUNTIME否则运行时反射无法读取。8. 反射的应用场景反射在日常业务代码中可能不常见但它几乎渗透在每一个主流框架中框架与容器Spring 的 IoC 容器通过反射创建 Bean、注入依赖Spring MVC 通过反射调用 Controller 方法。ORM 映射MyBatis、Hibernate 通过反射把查询结果封装成实体对象。序列化与反序列化Jackson、Gson 通过反射把 JSON 转换成对象。动态代理JDK 动态代理本质上是基于接口 反射实现的。注解处理各种自定义注解的运行时解析都依赖反射。工厂模式与通用工具根据类名或配置动态加载不同实现实现插件化架构。一个经典的工厂示例publicclassAnimalFactory{publicstaticAnimalcreate(StringclassName){try{return(Animal)Class.forName(className).newInstance();}catch(Exceptione){thrownewRuntimeException(创建失败className,e);}}}9. 反射的性能问题与优化反射功能强大但代价是性能损耗。主要原因在于运行时进行类型检查、安全性检查Method.invoke需要做参数装箱拆箱和数组分配无法享受 JIT 编译器的部分优化。因此在高频调用场景如循环内、高并发接口中应尽量避免直接使用反射。常见优化手段缓存Class、Method、Field等对象获取元信息是较昂贵的操作拿到后应缓存复用。调用setAccessible(true)可以关闭一次安全检查减少一部分开销。尽量减少装箱拆箱必要时使用invokeExactMethodHandle 场景。优先使用直接调用或 Lambda现代 Java 推荐使用方法引用、MethodHandle或VarHandle作为反射的替代方案。实际上对于仅调用一次的反射开销通常可以接受需要警惕的是把它放在循环或热路径中未加优化。10. 反射的局限性与注意事项反射并非万能使用时需要注意破坏封装setAccessible(true)可以绕过访问控制滥用会破坏类的封装性。绕过泛型检查反射可以把不符合泛型约束的值塞进集合中导致隐藏的类型安全隐患。错误延迟到运行期编译期无法检查方法名、字段名的正确性拼写错误在运行时才暴露。异常类型复杂反射相关操作会抛出ClassNotFoundException、NoSuchMethodException、InvocationTargetException等多种受检异常。可读性下降反射代码绕来绕去维护成本高于直接的静态调用。因此绝大多数业务代码应优先使用直接调用反射更适合框架、工具类、基础设施等需要高度抽象的场景。11. 总结反射是 Java 提供的一把「万能钥匙」它让我们可以在运行时窥探和操作任何类从而支撑起框架化、配置化、插件化的现代软件开发范式。但正如钥匙能开锁也能破门反射带来灵活性的同时也牺牲了编译期安全、封装性和运行性能。理解反射的关键是理解Class对象以及Constructor、Method、Field这一套运行时元信息 API。掌握它们之后再去阅读 Spring、MyBatis 的源码你会发现自己看到的底层逻辑豁然开朗。建议你亲手完成以下练习巩固写一个通用的对象拷贝工具通过反射实现两个对象属性拷贝写一个简单的依赖注入容器通过反射完成对象创建与字段注入通过反射解析自定义注解实现一个轻量级 ORM 映射。