注解与反射

160 阅读6分钟

注解 + 反射是各种框架实现的基石魔法

什么是注解

  • 注解(Annotation)是Java中的一种特殊的语法元素,JDK1.5引入
  • 提供了一种在程序中添加元数据(Metadata)的方法
  • 注解可以用于代码编译、运行和解析时期,为程序的运行提供一些额外的信息

元数据Metadata

  • 描述数据的数据,或者说是数据的附加信息
  • 在软件开发中,元数据通常用于描述程序的配置信息、注解信息、文档信息
  • 元数据可以分成三个层次:
    • 描述数据的属性和结构,比如数据类型、字段名、长度、精度等
    • 描述数据的来源和格式,比如数据源、数据格式、数据版本等
    • 描述数据的用途和访问权限,比如数据的许可证、数据的保密性、数据的可用性等
  • 举例:String str
    • str 是我们的数据
    • 然而规定 str 多长就是元数据

注解本质与分类

  • 用法:@注解名
  • 本质:特殊Java接口 @interface
  • 分类:
    • 运行机制:源码注解、编译时注解、运行时注解
    • 代码来源:JDK内置注解、第三方注解、自定义注解
    • 特殊的注解:元注解(对注解进行注解)

JDK 内置注解

  • @Override - 用于标注重写了父类的方法
  • @Deprecated - 表示类、方法、接口等已废弃
@Deprecated
public class DeprecatedClass {
    @Deprecated
    public void doSomething(String s) {
        System.out.println("String: " + s);
    }

    public void doSomethingV2(String s) {
        System.out.println("String: " + s);
    }
}
  • @SuppressWarnings - 抑制编译器的警告
// @SuppressWarnings({"deprecation", "unused", "rawtypes"})
@SuppressWarnings("deprecation")
public class SuppressWarningAnnotationDemo {
    public void SuppressRawTypeWarning() {
        @SuppressWarnings("rawtypes")
        List list = new ArrayList<>();
    }

    @SuppressWarnings("unused")
    public void SuppressUnusedWarning() {
        int a = 5;
        int b = 10;
    }

    public void SuppressDeprecatedWarning() {
        DeprecatedClass deprecatedClass = new DeprecatedClass();
        deprecatedClass.doSomething("Hello World");
    }
}
  • @FunctionalInterface - 函数式接口
@FunctionalInterface
public interface CustomizeFunctionalInterface {
    void doSomething();

    default void doAnotherThing(){};
}
  • @SafeVarargs - 标注一个方法或构造函数的参数为安全的可变参数(Varargs),表示该方法或构造函数不会对这个可变参数产生堆污染(Heap pollution)

JDK 元注解(Meta Annotation)

  • @Retention - 注解的保留策略(所保留的阶段,SOURCECLASSRUNTIME
  • @Target - 注解的作用目标(范围),指定注解用于修饰哪些程序元素
    • TYPE
    • FIELD
    • METHOD
    • PARAMETER
    • CONSTRUCTOR
    • LOCAL_VARIABLE
    • ANNOTATION_TYPE
    • PACKAGE
    • TYPE_PARAMETER
    • TYPE_USE
  • @Inherited - 指定注解具有继承性
  • @Documented - 注解将包含在 Javadoc 中
  • @Repeatable - 可重复注解
  • (不常用)@Native - 修饰成员变量,可被本地代码引用

如何自定义注解

  • 用@interface定义注解接口,并给出注解的名称以及元注解
  • 在注解接口中定义注解参数。注解参数的定义方式和接口方法类似,但是没有方法体,而且可以指定默认值
  • 在程序中使用注解。可以在类、方法、字段等元素上使用注解,以便进行元数据描述和处理
  • 不带参数:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MarkAnnotation {
}
  • 带单个参数:
@Target({ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface SingleParameterAnnotation {
    String value() default "unknown";
}
  • 带多个参数
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonInfo {
    String name() default "ivan";
    int age() default 10;
    String id();
}


// 引用
// 默认的可以不写
// @PersonInfo(id = "456")
@PersonInfo(name = "near", age = 55, id = "jlsjf1323")
public class Person {}

从两个需求看为啥要设计反射

  • 需求一:当要调用一个类中的方法,并不知道类具体的实现,只有个类名路径,该怎么办?
  • 需求二:在没有私有属性提供的getter/setter方法时,我们非要修改private修饰的属性字段怎么办?

反射是什么?

  • Java反射机制:允许我们在运行时获取类的信息,并动态地创建对象、访问修改对象属性和调用方法这种灵活性使得Java程序可以更加动态和可拓展
  • 反射机制主要作用
    • 动态创建对象 - 可以动态地加载类,并创建它的对象。这对于创建灵活的框架和库非常有用
    • 动态获取类的信息 - 例如类的名称、父类、接口、字段和方法等。可以在运行时获取类的信息,并根据这些信息来执行操作
    • 动态调用方法 - Java反射机制可以动态地调用类的方法,包括私有方法。这对于编写通用的代码和调试非常有用
    • 动态修改属性 - Java反射机制可以动态地修改对象的属性,包括私有属性。这使得我们可以在运行时修改对象的状态
  • 反射的重要应用:动态代理 (Spring AOP)

Java反射机制的核心类:Class类

  • 其他类在java.lang.reflect包下基本通过看名字就知道做什么
  • 类信息对象 → 各种信息描述的对象

如何获取Class对象

  • 使用对象的getClass()方法获取Class对象
Animal animal = new Animal();
Class<? extends String> stringClazz2 = s.getClass();
Class animalClazz2 = animal.getClass();
System.out.println("String clazz2: " + stringClazz2);
System.out.println("Animal clazz2: " + animalClazz2);
  • 使用.class获取Class对象
Class<String> stringClazz1 = String.class;
Class<Animal> animalClazz1 = Animal.class;
System.out.println("String clazz1: " + stringClazz1);
System.out.println("Animal clazz1: " + animalClazz1);
  • 使用Class.forName()方法获取Class对象
try {
    Class<?> stringClazz3 = Class.forName("java.lang.String");
    Class animalClazz3 = Class.forName("reflection.Animal");
    System.out.println("String clazz3: " + stringClazz3);
    System.out.println("Animal clazz3: " + animalClazz3);
} catch (ClassNotFoundException ex) {
    throw new RuntimeException("Class not found.", ex);
}
  • (了解)使用类加载器ClassLoader获取Class对象
ClassLoader classLoader = ObtainClassInstanceDemo.class.getClassLoader();
try {
    Class stringClazz4 = classLoader.loadClass("java.lang.String");
    Class animalClazz4 = classLoader.loadClass("reflection.Animal");
    System.out.println("String clazz4: " + stringClazz4);
    System.out.println("Animal clazz4: " + animalClazz4);
} catch (ClassNotFoundException e) {
    throw new RuntimeException(e);
}
  • 特殊用法:基本类型包装类的TYPE
Class<Integer> intClazz = Integer.TYPE;
Class<Boolean> boolClazz = Boolean.TYPE;
System.out.println("Integer clazz: " + intClazz);
System.out.println("Boolean clazz: " + boolClazz);
  • 注意:Class对象是在编译期间生成的,所以每个类在JVM中只有一个Class对象-如何验证?
String s1 = "Hello";
String s2 = "World";
Class s1Clazz = s1.getClass();
Class s2Clazz = s2.getClass();
System.out.println("== : " + (s1Clazz == s2Clazz));
System.out.println("equals : " + (s1Clazz.equals(s2Clazz)));

实战如何动态创建对象(最简方式)

Class<Animal> clazz = Animal.class;
try {
    // 1. 要有无参构造器
    // 2. 无参构造器要有访问权限
    Animal animal = clazz.newInstance();
    System.out.println(animal);
} catch (InstantiationException | IllegalAccessException e) {
    throw new RuntimeException(e);
}

如何获取类的相关信息

  • 要知道能获取哪些信息
    • 类名
    // 获取类名
    Class<Bird> birdClass = Bird.class;
    // getName: the name of the class or interface represented by this object.
    System.out.println("Class name for bird: " + birdClass.getName());
    // getSimpleName: the simple name of the underlying class.
    System.out.println("Class simple name for bird: " + birdClass.getSimpleName());
    
    // 获取包名
    Package birdClassPackage = birdClass.getPackage();
    System.out.println("Package name for birdClass: " + birdClassPackage);
    
    • 构造器
    // 获取当前类所有的public构造器
    Constructor<?>[] constructors = birdClass.getConstructors();
    for (Constructor c: constructors) {
        System.out.println("Public Constructor: " + c);
    }
    
    
    // 获取当前类所有的构造器
    Constructor<?>[] allConstructors = birdClass.getDeclaredConstructors();
    for (Constructor c: allConstructors) {
        System.out.println("Constructor: " + c);
    }
    
    • 属性
    Class<Bird> birdClass = Bird.class;
    
    // getFields: 获取所有的自身public属性以及父类的public属性
    Field[] fields = birdClass.getFields();
    
    for (Field field : fields) {
        System.out.println("Public field: " + field);
        // 变量名
        System.out.println("Field name: " + field.getName());
        // 访问修饰符
        System.out.println("Field Modifier: " + Modifier.toString(field.getModifiers()));
        // 数据类型
        System.out.println("Field type: " + field.getType());
    }
    
    System.out.println("==============");
    
    // getDeclaredFields: 获取所有的自身属性
    Field[] classFields = birdClass.getDeclaredFields();
    
    for (Field field : classFields) {
        System.out.println("Field: " + field);
        // 变量名
        System.out.println("Field name: " + field.getName());
        // 访问修饰符
        System.out.println("Field Modifier: " + Modifier.toString(field.getModifiers()));
        // 数据类型
        System.out.println("Field type: " + field.getType());
    }
    
    try {
        // getDeclaredField
        Field ageField = birdClass.getDeclaredField("age");
        System.out.println("Age field: " + ageField);
        // getField
        Field canEatField = birdClass.getField("canEat");
        System.out.println("Can eat field: " + canEatField);
    } catch (NoSuchFieldException e) {
        throw new RuntimeException(e);
    }
    
    • 方法
    Class<Bird> birdClass = Bird.class;
    
    // 获取该类以及其父类(superclasses)的所有public方法
    Method[] methods = birdClass.getMethods();
    for (Method m: methods) {
        System.out.println("Public method: " + m);
    
        // 注解
        Annotation[] annotations = m.getAnnotations();
        for (Annotation a: annotations) {
            System.out.println("Public method annotation: " + a);
        }
    
        // 方法信息
        System.out.println("Public method name: " + m.getName());
        System.out.println("Public method return type: " + m.getReturnType());
    
        Parameter[] parameters = m.getParameters();
        for (Parameter p: parameters) {
            System.out.println("Public method parameter: " + p);
        }
    
        Class<?>[] exceptions = m.getExceptionTypes();
        for (Class<?> ex: exceptions) {
            System.out.println("Public ex type: " + ex);
        }
    
        System.out.println("Public method modifier: " + Modifier.toString(m.getModifiers()));
    }
    
    // 获取该类的所有方法
    Method[] classMethods = birdClass.getDeclaredMethods();
    for (Method m: classMethods) {
        System.out.println("Method: " + m);
    
        // 注解
        Annotation[] annotations = m.getAnnotations();
        for (Annotation a: annotations) {
            System.out.println("Method annotation: " + a);
        }
    
        // 方法信息
        System.out.println("Method name: " + m.getName());
        System.out.println("Method return type: " + m.getReturnType());
    
        Parameter[] parameters = m.getParameters();
        for (Parameter p: parameters) {
            System.out.println("Method parameter: " + p);
        }
    
        Class<?>[] exceptions = m.getExceptionTypes();
        for (Class<?> ex: exceptions) {
            System.out.println("Ex type: " + ex);
        }
    
        System.out.println("Method modifier: " + Modifier.toString(m.getModifiers()));
    }
    
    • 注解
    Class<Bird> birdClass = Bird.class;
    
    Markable markable = birdClass.getAnnotation(Markable.class);
    System.out.println("Get Annotation: " + markable);
    System.out.println("Annotation value: " + markable.value());
    
    // 属性上的注解
    try {
        Field field = birdClass.getDeclaredField("age");
        Markable fieldMarkable = field.getAnnotation(Markable.class);
        System.out.println("Get Annotation in age field: " + fieldMarkable);
        System.out.println("Annotation value in age field: " + fieldMarkable.value());
    } catch (NoSuchFieldException e) {
        throw new RuntimeException(e);
    }
    
    Class<Target> targetClass = Target.class;
    Target targetClassAnnotation = targetClass.getAnnotation(Target.class);
    System.out.println("Get Annotation: " + targetClassAnnotation);
    System.out.println("Annotation value: " + Arrays.toString(targetClassAnnotation.value()));
    
    Annotation[] annotations = targetClass.getAnnotations();
    for (Annotation a: annotations) {
        System.out.println("Target annotation: " + a);
        if (a instanceof Target) {
            Target target = (Target) a;
            System.out.println("Target value: " + Arrays.toString(target.value()));
        }
    }
    
    • 父类信息
    • 泛型信息
    • ...

如何动态调用方法

  • 获取目标类的Class对象
MyClass obj = new MyClass(10);
Class clazz = obj.getClass();
  • 获取目标方法的Method对象
    • 通过Class.getDeclaredMethod()或者Class.getMethod()方法获取目标方法的Method对象
  • 设置方法的可访问性(只有调private方法需要)
    • 如果目标方法的访问级别为private,需要将Method对象的accessible属性设置为true,才能够访问该方法
  • 调用目标方法
    • 通过Method对象的invoke()方法来调用目标方法,同时可以传入所需参数
// 1. 最简单:调用public无参数无返回值方法
// (1) 获取目标类的class对象
MyClass obj = new MyClass(10);
Class clazz = obj.getClass();

try {
    // (2) 获取目标方法的Method对象
    Method doNothingMethod = clazz.getMethod("doNothing");
    // (3) 调用Method中的invoke
    Object returnResult = doNothingMethod.invoke(obj);
    System.out.println("Do nothing result: " + returnResult);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
    throw new RuntimeException(e);
}

// 2. 调用public有参数有返回值的方法
try {
    // (2) 获取目标方法的Method对象,要加上参数类型
    Method sumMethod = clazz.getMethod("sum", int.class, String.class);
    // (3) 调用Method中的invoke,参数对于method对象
    Object returnResult = sumMethod.invoke(obj, 10, "10");
    System.out.println("Sum result: " + returnResult);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
    throw new RuntimeException(e);
}

// 3. 调用private有参数有返回值的方法
// (1) 获取目标类的class对象
MyClass myClass = new MyClass();

try {
    // (2) 获取目标方法的Method对象,要加上参数类型
    Method method = clazz.getDeclaredMethod("sum", String.class, String.class);
    // (3) 设置方法的可访问性(只有调private方法需要)
    method.setAccessible(true);
    // (4) 调用Method中的invoke,参数对于method对象
    Object sumResult = method.invoke(myClass,"15", "15");
    System.out.println("Private method sum Result: " + sumResult);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
    throw new RuntimeException(e);
}

// 4. 调用static方法
try {
    // (2) 获取目标方法的Method对象,要加上参数类型
    Method method = clazz.getDeclaredMethod("sum", String.class);
    // (3) 设置方法的可访问性(只有调private方法需要)
    method.setAccessible(true);
    // (4) 调用Method中的invoke,参数对于method对象,设置method对象为null
    Object sumResult = method.invoke(null,"15");
    System.out.println("Static private method sum Result: " + sumResult);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
    throw new RuntimeException(e);
}

如何动态修改属性/如何动态修改构造器

重点:

  • 对于private要用declared
  • 对于private要把accessible设为true
  • 动态修改属性
Person person = new Person();
System.out.println("Person: " + person);
try {
    // 获取Field
    Field ageField = person.getClass().getDeclaredField("age");
    Field nameField = person.getClass().getField("name");
    // 将访ageField的问权限设为true
    ageField.setAccessible(true);
    // set属性
    ageField.set(person, 60);
    nameField.set(person, "ivan");
    System.out.println("Person after reflection: " + person);
} catch (NoSuchFieldException | IllegalAccessException e) {
    throw new RuntimeException(e);
}
  • 动态调用构造器
Class<Person> personClass = Person.class;
try {
    // 获取对应要调用的构造器
    Constructor<Person> constructor = personClass.getDeclaredConstructor(String.class, int.class);
    // 设置可访问性为true
    constructor.setAccessible(true);
    // 利用newInstance初始化对象,注意参数需要和构造器传入参数对应
    Person person = constructor.newInstance("ZHANGSAN", 1000);
    System.out.println("Person: " + person);
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
    throw new RuntimeException(e);
}