背景
凡事我们都要知其然,知其所以然,我们既然要研究反射,就要知道
反射的原理是什么:在Java中,反射(Reflection)是指在运行时获取和操作类的信息的能力。它允许程序在运行时动态地检查类、获取类的属性和方法,并且可以调用类的方法或创建类的实例。
但是他到底是怎么做到运行时获取到信息的呢?我们一起来看一看吧,归根结底,还是匹配字符串,但是是如何匹配的我们还需要考究一下,下面我就手写自定义注解的例子讲解如何使用反射的,并把他手写出来。
过程
注解概念
当应用5W2H方法分析注解时,我们可以按照以下方式来描述注解的作用和含义:
What(什么):注解是一种特殊的标记,用于提供元数据。它可以应用于类、方法、字段等程序元素上,以附加额外的信息。
Why(为什么):注解的目的是为了丰富和增强代码的表达能力和功能。它可以提供额外的元数据,用于编译时检查、运行时处理和自动生成代码等。
Where(在哪里):注解可以应用于各种程序元素,包括类、方法、字段等。具体可以根据注解的定义和目的来决定它可以应用的位置。
When(何时):注解在编码过程中可以随时使用。根据需求,可以在需要注解的代码上使用它们,例如标记过时的方法、指定配置信息等。
Who(谁):注解可以由开发人员或框架进行定义和使用。开发人员可以为自己的代码添加注解,而框架可以使用注解来实现特定的行为或功能。
How(如何):注解通过使用@符号作为前缀,并提供注解名称和可选的参数来声明和使用。开发人员可以自定义注解,并通过反射机制来解析注解并采取相应的操作。
How much(多少):注解的数量和复杂性取决于具体的应用场景和需求。Java提供了一些内置的注解,同时也可以根据需要自定义注解。
明确自定义注解的边界
在Java中,注解(Annotation)是一种用于提供元数据的特殊标记。它可以应用于类、方法、字段等程序元素上,以提供关于这些元素的额外信息。注解本身不会直接影响程序的执行,但可以被编译器、工具和框架等处理,以实现特定的行为或功能。
1、定义注解:使用 @interface 关键字定义一个新的注解。注解可以包含元素,用于指定注解的属性。
// 定义一个自定义注解 public @interface MyAnnotation { String value(); int count() default 0; }
2、使用注解:在适当的地方使用自定义注解。可以将注解应用于类、方法、字段等。
// 使用自定义注解 @MyAnnotation(value = "Hello", count = 5) public class MyClass { @MyAnnotation("World") private String message; @MyAnnotation(value = "Method") public void myMethod() { // 方法体 } }
3、解析注解:在程序中通过反射机制解析和处理注解。可以获取注解的属性值,并根据注解的定义执行相应的操作。
// 解析注解 Class<?> clazz = MyClass.class; MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class); String value = annotation.value(); int count = annotation.count();
手写反射实现自定义注解
我们主要是实现自定义注解步骤的第三步,上述的步骤是使用getAnnotation
方法获取的注解的相关信息,我们现在不使用这个API,自己手写一个实现文本匹配从而实现getAnnotation的效果:
public class Client { public static void main(String[] args) throws IOException, NoSuchFieldException, ClassNotFoundException { MapImpl map = new MapImpl(); map.readFileInfo(); System.out.println(map.min()); System.out.println(map.max()); System.out.println(map.errorMsg()); } }
@Target({ElementType.FIELD}) //表示自定义注解可保留到运行时 @Retention(RetentionPolicy.RUNTIME) //自定义注解,注解名字是Length public @interface Length { // 允许的字符串长度最小值 int min(); // 的允许字符串长度最大值 int max(); // 自定义错误提示 String errorMsg(); }
在这个类里边写识别字符串的操作。
public class MapImpl implements Length { @Override public int min() { // String min = this.readFile("min"); // System.out.println("最小值为:"+min); // return Integer.valueOf(min) ; String minValue = this.getMethedValue(Thread.currentThread().getStackTrace()[1].getMethodName()); return Integer.valueOf(minValue); } @Override public int max() { // String max = this.readFile("max"); // System.out.println("最大值为:"+max); // return Integer.valueOf(max) ; String maxValue = this.getMethedValue(Thread.currentThread().getStackTrace()[1].getMethodName()); return Integer.valueOf(maxValue); } @Override public String errorMsg() { // String errorMessage = this.readFile("errorMsg"); // System.out.println("错误信息:"+ errorMessage); // return errorMessage; String errorMsgValue = this.getMethedValue(Thread.currentThread().getStackTrace()[1].getMethodName()); return errorMsgValue; } @Override public Class<? extends Annotation> annotationType() { return null; } boolean state=true; private String getMethedValue(String name){ if (state==true){ state=false; methedValue= this.readFileInfo(); } if(name.equals("errorMsg")){ System.out.println("错误信息:"+ methedValue.get(name)); return methedValue.get(name); }else if(name.equals("min")){ return methedValue.get(name); }else if(name.equals("max")){ System.out.println("最大值为:"+methedValue.get(name)); return methedValue.get(name); } return null; } Map<String,String> methedValue = new HashMap<>(); public Map<String, String> readFileInfo() { try{ BufferedReader br=new BufferedReader(new InputStreamReader(System.in)); System.out.println("请输入要读取文件的路径"); //E:\zy\TGB-zgy-2022\老师设计模式课相关资料必须留着\米老师设计模式课小例子\JAVAtest\customAnnotations\target\classes\LengthImpl1\Person.class String fileName=br.readLine(); DataInputStream csanner = new DataInputStream(new FileInputStream(new File(fileName))); ClassFile classFile = new ClassFile(csanner); List<FieldInfo> fieldsList = classFile.getFields(); for (FieldInfo field:fieldsList){ for (AttributeInfo attributeInfo : field.getAttributes()) { System.out.println(attributeInfo); String annotationValue = attributeInfo.toString().replace("(", ","); String annotationValue1 = annotationValue.replace(")", ","); String[] fields = annotationValue1.split(","); for (int i = 1; i < fields.length; i++) { String[] fieldSplit = fields[i].split("="); methedValue.put(fieldSplit[0].trim(),fieldSplit[1].trim()); System.out.println(methedValue); } } } }catch (Exception e){ System.out.println("获取文件失败"+e); } return methedValue; } }
实现效果:
了解元注解(注解使用的地方,时间范围,是否生成文档,是否被继承)
@Retention:
指定注解的保留策略。它决定注解在编译后是否被保留在class文件中以及在运行时可见性。常用的保留策略包括:
RetentionPolicy.SOURCE:注解仅在源代码级别保留,不包含在编译后的class文件中。
RetentionPolicy.CLASS:注解在编译时被保留,并包含在class文件中,但在运行时不可见。
RetentionPolicy.RUNTIME:注解在编译时被保留,并包含在class文件中,可以在运行时通过反射机制获取到。
@Target:
指定注解可以应用的目标元素类型。它规定了注解可以用于哪些程序元素,如类、方法、字段等。常用的目标元素类型包括:
ElementType.TYPE:注解应用于类、接口、枚举等类型。
ElementType.METHOD:注解应用于方法。
ElementType.FIELD:注解应用于字段(属性)。
ElementType.PARAMETER:注解应用于方法的参数。
@Documented:
指定注解是否包含在生成的API文档中。如果一个注解被标记为@Documented,那么当使用javadoc工具生成API文档时,注解会包含在文档中。
@Inherited:
指定注解是否可以被继承。如果一个注解被标记为@Inherited,那么它可以被继承,即当一个类继承自有该注解的父类时,子类也会继承该注解。
结果
注解的好处:
代码文档化:注解可以用于生成代码文档,通过添加注解来标记类、方法或字段的说明,使得开发者和其他人员能够更好地理解代码的意图和用法。
编译时检查:注解可以用于在编译时对代码进行检查,以确保代码的正确性或进行静态分析。例如,通过自定义注解来标记特定的代码结构,编译器可以根据注解执行额外的检查或生成警告信息。
运行时处理:注解可以在运行时被解析和处理,用于执行特定的逻辑或配置应用程序。例如,通过注解可以配置依赖注入框架,实现对象的自动注入;或者使用注解来定义路由映射,简化Web应用程序的开发。
测试和调试:注解可以用于标记测试用例或调试相关的信息,以便测试框架或调试器能够自动化执行相应的操作。例如,通过注解标记测试方法,测试框架可以识别并执行这些方法作为测试套件的一部分。