34. 什么是反射
34. 什么是反射
反射就是运行状态中,对于任意一个类都可以知道该类的属性和方法;对于任意的对象,都可以调用它的属性和方法。可以动态的获取类信息和对象的信息,这种功能称为反射机制。
反射的优缺点
优点:可以动态获取类的实例,提高灵活性,与动态编译结合,动态代理。
缺点:性能开销大,需要解析字节码。有解决方案:setAccessible(true)关闭JDK的安全检查来提升反射性能,多次创建一个类的实例时,有缓存会快很多。ReflectASM工具类,通过字节码生成的方式加快反射速度。
如何获取反射中的Class对象
-
Class.forName(“类的路径”) 类的全路径名
Class<?> clazz = Class.forName("全类名");
说明
- 全类名:包括类的 包名 + 类名(例如
java.lang.String
)。 - 触发类的初始化:调用
Class.forName()
会触发类的 加载、链接和初始化(包括执行静态代码块)。 - 适用场景:
- 类名在运行时动态确定(例如从配置文件中读取类名)。
- 需要动态加载类并执行其静态代码块。
示例
// 动态加载类(假设 Student 类在包 huang6 中) Class<?> clazz = Class.forName("huang6.Student"); System.out.println(clazz); // 输出 class huang6.Student
- 全类名:包括类的 包名 + 类名(例如
-
类名.Class方法
语法
Class<Student> clazz = Student.class;
说明
-
编译期检查:在编译时就能检查类是否存在,更安全。
-
不触发类的初始化:仅获取类的
Class
对象,不会执行静态代码块。 -
适用场景
:
- 类名在编译时已知。
- 需要传递
Class
对象作为参数(例如泛型方法)。
示例
// 直接通过类名获取 Class 对象
Class<Student> clazz = Student.class;
System.out.println(clazz); // 输出 class huang6.Student
-
对象.getClass()
语法
Class<? extends Student> clazz = student.getClass();
说明
- 通过实例对象获取:需要先创建类的实例对象。
- 不触发类的初始化:类已经加载,因此不会执行静态代码块。
- 适用场景:
- 已有类的实例对象时(例如从集合或数据库中获取对象后)。
示例
// 通过实例对象获取 Class 对象 Student student = new Student(); Class<? extends Student> clazz = student.getClass(); System.out.println(clazz); // 输出 class huang6.Student
Java反射API有几类
-
Class类
-
作用:核心类,表示类或接口的类型信息
-
功能:getName(),getSuperclass(),getInterfaces(),getConstructors(),getFields(),getMethods(),newInstance()或getDeclaredConstructor.newInstance()
-
示例:
Class<?> clazz = String.class;
-
-
Field类
-
作用:类的字段信息,动态访问和修改
-
功能:getName(),getType(),setAccessible(true)访问私有字段
Field field = clazz.getDeclaredField("fieldName"); field.setAccessible(true); // 突破访问权限限制 field.set(instance, "newValue");
-
-
Method类
-
作用:表示类的方法
-
功能:getName(),getParameterTypes(),invoke()调用方法
Method method = clazz.getMethod("methodName", paramTypes); method.invoke(instance, args);
-
-
Constructor类
Constructor<?> constructor = clazz.getDeclaredConstructor(paramTypes); Object instance = constructor.newInstance(args);
-
Modifier类
-
Array类
-
Proxy 和 InvocationHandler
-
作用:实现动态代理
-
创建代理对象 Proxy.newProxyInstance()
-
拦截方法调用 InvocationHandler的invoke方法
InvocationHandler handler = (proxy, method, args) -> {System.out.println("方法调用前");return method.invoke(target, args); }; Object proxy = Proxy.newProxyInstance(...);
-
-
-
其他一些辅助类
注意事项
- 性能开销:反射操作比直接调用方法慢,避免在高性能场景频繁使用。
- 安全性:通过
setAccessible(true)
可以突破访问权限限制,但可能破坏封装性。 - 适用场景:常用于框架开发(如 Spring 依赖注入)、插件系统、通用工具类等。
反射使用的步骤:
-
获取类的Class对象
- Class.forName()
- .Class
- getClass()
-
创建类的实例
- 无参构造
- Object instrance = clazz.getDeclaredConstructor().newInstance();
- 有参构造
-
- Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
-
- Object instrance = constructor.newInstrance(“John”, 30);
- newInstrance() 在Java9 标记为过时,推荐用Constructor.newInstrance()
-
- 无参构造
-
访问和修改字段
-
获取字段
-
Field field = clazz.getDeclaredField("字段名称"); //获取指定字段(包括私有) field.setAccessible(true) // 突破访问权限限制(针对私有字段)
-
-
读取字段值
-
Object value = field.get(instrance);
-
-
修改字段值
-
field.set(instrance, "newvalue");
-
-
-
调用方法
-
获取方法
-
Method method = clazz.getDeclaredMethod("方法名", 参数.class); method.setAccessible(true);
-
-
调用方法
-
Object result = method.invoke(instance,参数值)
-
-
示例:
// 调用 Person 类的 sayHello 方法 Method sayHelloMethod = Person.class.getMethod("sayHello"); sayHelloMethod.invoke(person);// 调用带参数的方法 Method greetMethod = Person.class.getMethod("greet", String.class); greetMethod.invoke(person, "World");
-
-
获取和调用构造方法
-
获取类的元数据
-
处理泛型和注解
为什么引入反射概念?反射机制的应用有哪些
引入反射最主要的是增强程序的动态性和灵活性。能够在代码运行时动态获取类的属性,方法,调用他们。核心目的
- 动态性需求:
- 一般情况下,都是在编译时就已经确定了程序的行为;但在某些场景下,需要动态获取类的属性或者方法。比如:配置文件的加载
- 框架需求:
- 像spring,可以利用反射机制来实现依赖注入,动态代理,注解处理
- 兼容性与灵活性:
- 反射可以绕过编译时的类型限制,可以处理未知的类和方法动态调用
- 调试与测试:
- 反射可以访问私有属性和方法,便于测试如Junit
-
动态加载类和调用方法
-
Class<?> clazz = Class.forName("com.example.MyClass"); Object obj = clazz.getDeclaredConstructor().newInstance(); Method method = clazz.getMethod("myMethod", String.class); method.invoke(obj, "参数");
-
-
注解处理
-
如Junit的@Test,Spring的@Autowired
-
Class<?> clazz = Class.forName("com.example.MyClass"); Object obj = clazz.getDeclaredConstructor().newInstance(); Method method = clazz.getMethod("myMethod", String.class); method.invoke(obj, "参数");
-
-
JDBC数据库连接
- 通过Class.forName()加载驱动
-
动态代理和AOP编程
-
日志,事务,权限校验等
-
InvocationHandler handler = (proxy, method, args) -> {System.out.println("Before method: " + method.getName());return method.invoke(target, args); }; Object proxy = Proxy.newProxyInstance(...);
-
反射机制的原理是什么
Class actionClass = Class.forName("MyClass");
Object action = actionClass.newInstance();
Method method = actionClass.getMethod("MyMethod", null);
method.invoke(action, 参数);
-
反射会先获取类的实例,不会去交给Java去实现,而是交给了JVM去加载,获取ClassLoader,然后调用native方法获取信息,加载类就是回调Java.long.ClassLoader。最后,JVM又会回调ClassLoader进行类加载
-
newInstance() 主要做了三件事
- 权限检测,如果不通过就会抛出异常
- 查询无参构造,并将其缓存起来
- 调用具体方法的无参构造方法,生成实例并返回
-
获取Method对象
-
-
上面的Class对象是在加载类时由JVM构造的,JVM为每个类管理一个独一无二的Class对象,这份Class对象里维护着该类的所有Method,Field,Constructor的cache,这份cache也可以被称作根对象。每次getMethod获取到的Method对象都持有对根对象的引用,因为一些重量级的Method的成员变量(主要是MethodAccessor),我们不希望每次创建Method对象都要重新初始化,于是所有代表同一个方法的Method对象都共享着根对象的MethodAccessor,每一次创建都会调用根对象的copy方法复制一份
-
Method copy() {Method res = new Method(clazz, name, parameterTypes, returnType,exceptionTypes, modifiers, slot, signature,annotations, parameterAnnotations,annotationDefault);res.root = this;res.methodAccessor = methodAccessor;return res;}
-
-
调用invoke()方法
调用Method.invoke之后,会直接去调MethodAccessor.invoke。MethodAccessor就是上面提到的所有同名method共享的一个实例,由ReflectionFactory创建。