注解 + 反射是各种框架实现的基石魔法
什么是注解
- 注解(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 - 注解的保留策略(所保留的阶段,
SOURCE、CLASS、RUNTIME) - @Target - 注解的作用目标(范围),指定注解用于修饰哪些程序元素
TYPEFIELDMETHODPARAMETERCONSTRUCTORLOCAL_VARIABLEANNOTATION_TYPEPACKAGETYPE_PARAMETERTYPE_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);
}