反射是Java语言的一项强大特性,它允许程序在运行时检查和修改自身的行为。通过反射,代码可以动态地加载类、创建对象、调用方法、访问字段,甚至改变私有成员的可见性。动态代理则建立在反射之上,提供了在运行时创建接口实现的能力。这两个特性是Spring、Hibernate、MyBatis等框架的基石,也是实现AOP(面向切面编程)的核心技术。
参考:https://xgmoi.cn/category/yinshi.html
反射的核心API位于java.lang.reflect包中。Class类是反射的入口点,可以通过对象.getClass()、类名.class、或Class.forName("全限定名")获取。Class提供了获取构造函数(getConstructor、getDeclaredConstructor)、方法(getMethod、getDeclaredMethod)、字段(getField、getDeclaredField)以及注解的方法。Constructor、Method、Field类分别代表构造函数、方法和字段,提供了newInstance、invoke、get、set等操作。
反射的代价是性能。调用Method.invoke比直接方法调用慢几倍到几十倍,因为涉及参数包装、类型检查、访问控制、以及JIT优化的限制。字段访问也有类似的性能损失。反射的另一个代价是安全性——反射可以访问私有成员,打破了封装性,可能被恶意代码利用。此外,反射代码难以阅读、调试和维护。
反射的优化:现代JVM对反射进行了改进。MethodHandle(Java 7)提供了比反射更快的动态调用机制,但API更复杂。LambdaMetafactory(Java 8)可以动态生成函数式接口的实现,性能接近直接调用。缓存Method对象避免重复查找。使用setAccessible(true)可以禁用访问控制检查,提高反射调用速度,但破坏了封装性。
反射的典型应用:框架的依赖注入(Spring IoC容器使用反射创建Bean并注入依赖)、对象关系映射(Hibernate使用反射将数据库记录映射到Java对象)、JSON序列化(Jackson、Gson使用反射读取对象的字段)、单元测试(JUnit使用反射发现和执行测试方法)、以及代码分析工具(IDE的代码补全和重构依赖反射)。
动态代理是java.lang.reflect.Proxy类提供的功能。它允许在运行时创建实现指定接口的代理类。动态代理的核心是InvocationHandler——代理对象的方法调用都会被转发到InvocationHandler.invoke方法。开发者可以在invoke中添加横切逻辑(如日志、事务、权限检查),然后调用目标对象的方法。
动态代理的限制是只能代理接口,不能代理类。这是因为生成的代理类继承自Proxy,而Java不支持多继承。要代理具体类,需要使用字节码增强技术(如CGLIB、ByteBuddy)。CGLIB通过生成目标类的子类来实现代理,可以代理任何非final类和非final方法。
动态代理的应用:Spring AOP默认使用JDK动态代理(当目标类实现接口时)或CGLIB(否则)。声明式事务通过动态代理在方法调用前后开启和提交事务。缓存抽象在方法调用前检查缓存,命中则直接返回。远程调用(RPC)通过动态代理屏蔽网络通信细节。
方法句柄MethodHandle是Java 7引入的低级动态调用机制,是invokedynamic指令的基础。MethodHandle类似于函数指针,可以指向任何方法、构造函数或字段访问器。与反射相比,MethodHandle没有访问控制检查(但要求调用者具有访问权限),且优化潜力更大。MethodHandle的API比反射更复杂,通常用于框架开发而非应用代码。
参考:https://xgmoi.cn/category/zhongyi.html
invokedynamic指令是Java 7为JVM添加的新字节码指令,支持在调用点动态解析方法。invokedynamic是Java 8 Lambda表达式的底层实现机制——Lambda表达式被编译为invokedynamic调用,引导方法返回一个CallSite,指向Lambda的实际实现。invokedynamic也是非Java语言(如Groovy、Scala)实现动态特性的关键。
Lambda与反射:Java 8的Lambda表达式在大多数情况下不依赖反射。Lambda实例通过invokedynamic生成,在首次调用时创建,后续调用直接调用生成的方法,性能接近匿名内部类。这与反射有本质区别。
反射与模块化:JPMS(JDK 9)对反射进行了限制。一个模块默认不能反射访问其他模块的非公共成员。要允许反射访问,目标模块必须opens其包,或者调用者使用--add-opens命令行参数。这提高了平台的安全性,但也给框架开发者带来了挑战。
反射的安全问题:反射可以调用私有方法、读取私有字段,这可能导致安全漏洞。Java安全管理器可以限制反射的使用,但现代应用很少启用安全管理器。框架通常需要广泛的反射访问权限,因此用户必须信任所使用的框架。
反射的替代方案:在性能敏感的代码中,应避免使用反射。代码生成(如APT、注解处理器)在编译期生成代码,没有运行时开销。MethodHandle提供了比反射更好的性能。Lambda和方法引用提供了类型安全的动态调用。
反射的最佳实践:缓存反射对象;避免在热点路径上使用反射;优先使用接口和工厂模式而不是反射;使用MethodHandle替代反射进行频繁调用;在需要动态代理时,优先考虑JDK动态代理(比CGLIB更轻量)。最重要的是,只在真正需要动态性的地方使用反射——大多数业务代码不需要反射。
参考:https://xgmoi.cn