反射
Java反射
反射用于在运行时获取类的信息,多用于框架开发。
简单原理
总体流程
Java将源代码编译成字节码.class
文件,类加载阶段,JVM将其加载进内存,在堆区实例化一个(仅有一个)该类的Class类对象。
后续可以调用API获取类的相关信息(例如方法、属性等)进行调用或修改。具体背后底层原理可能设计编译和JVM知识,暂时未知。
字节码里有什么
包含常量池,类名,父类名,字段,方法,接口,属性等很多信息,并且按照一定的格式排列。链接
字节码类库
主要作用:根据字节码的规则,在运行时生成新的类,或者和修改增强以及存在的类
应用举例:实现代理,AOP等底层框架开发
类库举例:ASM,CGlib。后者是对于前者的封装。
ASM创建新类例子,根据字节码的格式要求,填好相应的数据。GPT生成,不一定能跑,主要是说明用ASM很麻烦。
public class DynamicBeanCreation {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 创建 ClassWriter 对象,用于生成新类的字节码
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES + ClassWriter.COMPUTE_MAXS);
// 定义类的元信息,包括类名、父类、接口等
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "DynamicBean", null, "java/lang/Object", null);
// 添加属性字段,并设置为 public 访问修饰符
cw.visitField(Opcodes.ACC_PUBLIC, "name", "Ljava/lang/String;", null, null).visitEnd();
cw.visitField(Opcodes.ACC_PUBLIC, "age", "Ljava/lang/Integer;", null, null).visitEnd();
// 添加默认构造方法
MethodVisitor constructor = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
constructor.visitCode();
constructor.visitVarInsn(Opcodes.ALOAD, 0);
constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
constructor.visitInsn(Opcodes.RETURN);
constructor.visitMaxs(1, 1);
constructor.visitEnd();
// 完成类的定义
cw.visitEnd();
// 将字节码转换为字节数组
byte[] bytecode = cw.toByteArray();
// 使用自定义类加载器加载并实例化类
ClassLoader classLoader = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return defineClass(name, bytecode, 0, bytecode.length);
}
};
Class<?> dynamicBeanClass = classLoader.loadClass("DynamicBean");
Object obj = dynamicBeanClass.getDeclaredConstructor().newInstance();
// 直接访问字段进行读写操作
Field nameField = dynamicBeanClass.getField("name");
nameField.set(obj, "Alice");
Field ageField = dynamicBeanClass.getField("age");
ageField.set(obj, 20);
// 获取字段值
System.out.println(nameField.get(obj));
System.out.println(ageField.get(obj));
}
}
CGlib举例,简洁很多。
import net.sf.cglib.beans.BeanGenerator;
public class DynamicClassCreation {
public static void main(String[] args) {
// 创建 BeanGenerator 对象,并指定父类和属性类型
BeanGenerator beanGenerator = new BeanGenerator();
beanGenerator.setSuperclass(Object.class);
beanGenerator.addProperty("name", String.class);
beanGenerator.addProperty("age", Integer.class);
// 创建子类实例,并设置属性值
Object obj = beanGenerator.create();
PropertyUtils.setProperty(obj, "name", "Alice");
PropertyUtils.setProperty(obj, "age", 20);
// 打印属性值
System.out.println(PropertyUtils.getProperty(obj, "name"));
System.out.println(PropertyUtils.getProperty(obj, "age"));
}
}
注解
注解配合反射,在框架中很常见。
注解本质上是接口,内部可以定义成员变量,当然,定义的方式很像方法。框架可以通过反射获取这些值来进行一些操作。
注解有元注解和普通注解,前者用于注解普通注解。元注解四种:
@Target
:指定注解可以应用的目标元素类型。目标元素类型包括类、接口、方法、字段等。常用取值有ElementType.TYPE
(类、接口)、ElementType.METHOD
(方法)、ElementType.FIELD
(字段)等。@Retention
:指定注解的保留策略,即注解在何时有效。常用取值有RetentionPolicy.SOURCE
(源码阶段有效)、RetentionPolicy.CLASS
(编译阶段有效)、RetentionPolicy.RUNTIME
(运行时有效)。@Documented
:指定注解是否出现在 Java 文档中。@Inherited
:指定注解是否可以被继承。如果一个注解被@Inherited
标记,并且应用在一个类上,那么该注解会被自动继承到其子类。
注解例子
// 声明一个注解,包含两个成员变量
public @interface MyAnnotation {
String value(); // 字符串类型的成员变量
int count() default 0; // 整数类型的成员变量,默认值为0
}
// 使用
@MyAnnotation(value = "Hello", count = 5)
public class MyClass {
}
// 反射使用注解
MyClass obj = new MyClass();
// 获取 MyClass 类的 Class 对象
Class<?> clazz = obj.getClass();
// 获取类上的注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof MyAnnotation) {
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("Value: " + myAnnotation.value());
System.out.println("Count: " + myAnnotation.count());
}
}
反射API使用例子
反射主要使用Class
类的方法,有如下常用方法
getMethods()
:返回类中公共方法的数组。getField(String name)
:返回指定公共字段的对象。getConstructors()
:返回类中公共构造方法的数组。getDeclaredConstructors()
:返回类中所有构造方法的数组,包括私有构造方法。getConstructor(Class<?>... parameterTypes)
:返回指定公共构造方法的对象。newInstance()
:通过默认构造方法创建类的实例(需要处理异常)。newInstance(Object... initargs)
:通过指定构造方法和参数创建类的实例(需要处理异常)。getSuperclass()
:返回类的父类。getInterfaces()
:返回类实现的接口数组。
注意:反射可以获取private的成员。
例子:
Class clazz = Class.forName("org.Ikun.Student");
调用API没有什么难度,更深一步的类加载原理学习留到JVM部分,现在学习目的是看懂框架中应用部分。
Class,Class<T>, Class<?> 区别
暂时没有查到特别让人信服的解释。猜测:
Class相当于Class<Object>
Class<?>可以用于限制类型 Class<? extends String> c;
不写相当于Class
Class<T>和其他的泛型一起使用,单独看他没什么意义,可能和上面的没区别,要放在实际的方法,类这个环境中。
本文作者:DL
本文链接:https://www.cnblogs.com/BayMax0-0/p/17726430.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步