早上好,中午好,晚上好,地球Onli|

园龄:粉丝:关注:

2023-09-27 21:22阅读: 8评论: 0推荐: 0

反射

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"));
    }
}

注解

注解配合反射,在框架中很常见。

注解本质上是接口,内部可以定义成员变量,当然,定义的方式很像方法。框架可以通过反射获取这些值来进行一些操作。

注解有元注解和普通注解,前者用于注解普通注解。元注解四种:

  1. @Target:指定注解可以应用的目标元素类型。目标元素类型包括类、接口、方法、字段等。常用取值有 ElementType.TYPE(类、接口)、ElementType.METHOD(方法)、ElementType.FIELD(字段)等。
  2. @Retention:指定注解的保留策略,即注解在何时有效。常用取值有 RetentionPolicy.SOURCE(源码阶段有效)、RetentionPolicy.CLASS(编译阶段有效)、RetentionPolicy.RUNTIME(运行时有效)。
  3. @Documented:指定注解是否出现在 Java 文档中。
  4. @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类的方法,有如下常用方法

  1. getMethods():返回类中公共方法的数组。
  2. getField(String name):返回指定公共字段的对象。
  3. getConstructors():返回类中公共构造方法的数组。
  4. getDeclaredConstructors():返回类中所有构造方法的数组,包括私有构造方法。
  5. getConstructor(Class<?>... parameterTypes):返回指定公共构造方法的对象。
  6. newInstance():通过默认构造方法创建类的实例(需要处理异常)。
  7. newInstance(Object... initargs):通过指定构造方法和参数创建类的实例(需要处理异常)。
  8. getSuperclass():返回类的父类。
  9. 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 中国大陆许可协议进行许可。

posted @   _nullptr0xf  阅读(8)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起