2022.4.20 注解
注解 :JDK1.5 之后的新特性,说明程序的,给计算机看的。
注释:用文字描述程序的,给程序员看的。
作用分类
-
编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
-
编写文档:通过代码里标识的注解生成文档【生成文档doc文档】,API文档是通过抽取代码中的文档注释生成的。
-
代码分析:通过代码里标识的注解对代码进行分析【使用反射】
内置注解
-
@Override : 检测被该注解标注的方法是否继承自父类(接口)的
-
@Deprecated :表示该注解标注的内容已过时
-
@SuppressWarnings :压制警告,一般传递参数all
-
@SuppressWarnings("all") 压制所有警告
-
自定义注解
格式:public @interface 注解名称{}
注解的本质
注解本质上就是一个接口,该接口默认继承Annotation接口
将以下注解编译过后进行反编译,得到结果:
编译前:
1 //自定义注解 @MyAnno 2 public @interface MyAnno{ 3 4 }
反编译后:
1 public interface MyAnno extends java.lang.annotation.Annotation{ 2 3 }
注解中的属性
可以理解为接口中可以定义的抽象方法
要求:
-
属性(抽象方法)的返回值类型只能为以下几种
-
基本数据类型
-
String
-
枚举
-
注解
-
以上类型的数组
-
-
定义的属性(本质上是抽象方法),在使用时需要进行赋值,抽象方法的名称就是属性的名称,值的类型是抽象方法返回值类型
-
如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
-
如果只有一个属性需要赋值,并且这个属性的名称是value,那么value可以省略,直接赋值即可。
-
数组赋值时,值使用大括号包裹。如果数组中只有一个值,那么{}可以省略
-
MyAnno
1 package com.xing.annotation; 2 3 4 //自定义注解 @MyAnno 5 public @interface MyAnno { 6 7 // 抽象方法就是注解的一个属性 返回值有要求 8 int age(); 9 String name(); 10 11 // 可以设置默认值 这样在使用这个注解时就不用给这个属性赋值了 12 String str() default ""; 13 }
AnnotationTest
1 package com.xing.annotation; 2 3 // 这里应该是实现了age与name方法把值retuen回来了,使用注解相当于把方法调用了,str这个属性有默认值 可以不用赋值 4 @MyAnno(age = 12,name = "使用时给属性(抽象方法)赋值") 5 public class AnnotationTest { 6 }
测试2
MyAnno
1 package com.xing.annotation; 2 3 4 //自定义注解 @MyAnno 5 public @interface MyAnno { 6 String value(); 7 }
AnnotationTest
1 package com.xing.annotation; 2 3 @MyAnno("小明") 4 public class AnnotationTest { 5 }
测试3
MyAnno
1 package com.xing.annotation; 2 3 4 //自定义注解 @MyAnno 5 public @interface MyAnno { 6 String[] names(); 7 }
AnnotationTest
1 package com.xing.annotation; 2 3 @MyAnno(names = {"小明","小红"}) 4 public class AnnotationTest { 5 }
元注解
元注解:用于描述注解的注解。Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明.
-
@Target():用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
1 //源码 2 @Documented 3 @Retention(RetentionPolicy.RUNTIME) 4 @Target(ElementType.ANNOTATION_TYPE) 5 public @interface Target { 6 /** 7 * Returns an array of the kinds of elements an annotation type 8 * can be applied to. 9 * @return an array of the kinds of elements an annotation type 10 * can be applied to 11 */ 12 ElementType[] value(); 13 } 14 15 public enum ElementType { 16 TYPE, 17 FIELD, 18 METHOD, 19 PARAMETER, 20 CONSTRUCTOR, 21 LOCAL_VARIABLE, 22 ANNOTATION_TYPE, 23 PACKAGE, 24 TYPE_PARAMETER, 25 TYPE_USE 26 }
MyAnno
1 @Target(ElementType.TYPE) //表示该MyAnno注解只能作用于类上 2 public @interface MyAnno { 3 }
AnnotationTest
1 package com.xing.annotation; 2 3 @MyAnno 4 public class AnnotationTest { 5 6 // @MyAnno() 报错不能作用在方法上 7 // public void test() { 8 // } 9 }
其中value中ElementType取值常用的几种情况:
-
TYPE:可以作用在类上
-
METHOD:可以作用在方法上
-
FIELD:可以作用于成员变量上
1 @Target(value = {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}) 2 //表示该MyAnno注解可以同时作用于类上,方法上和成员变量上 3 public @interface MyAnno { 4 }
-
-
@Retention():表示需要在什么级别保存该注释信息,用于描述注解的生命周期
1 //源码 2 @Documented 3 @Retention(RetentionPolicy.RUNTIME) 4 @Target(ElementType.ANNOTATION_TYPE) 5 public @interface Retention { 6 /** 7 * Returns the retention policy. 8 * @return the retention policy 9 */ 10 RetentionPolicy value(); 11 } 12 13 14 public enum RetentionPolicy { 15 SOURCE, 16 CLASS, 17 RUNTIME 18 }
-
java代码三个阶段:源代码阶段 (SOURCE 注解会保留到java源文件阶段,class文件中不会保留) -> 类加载阶段 (CLASS 注解会保留到java源文件阶段,编译生成的class文件中也会保留,但是反射机制读取不到) -> 运行时阶段 (RUNTIME 注解会保留到java源文件阶段,编译生成的class文件中也会保留,反射机制会读取到)
-
@Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到,一般自己定义的注解都加RUNTIME
-
-
@Documented:表示将我们的注解生成在javadoc中
-
@Inherited:描述注解会被子类继承
MyAnno
1 package com.xing.annotation; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Inherited; 5 import java.lang.annotation.Target; 6 7 @Target(ElementType.TYPE) 8 @Inherited 9 public @interface MyAnno { 10 }
AnnotationTest
1 package com.xing.annotation; 2 3 @MyAnno 4 public class AnnotationTest { 5 6 }
Test
1 package com.xing.annotation; 2 3 4 //会继承@MyAnno这个注解 因为父类的注解@MyAnno被@Inherited描述了 5 public class Test extends AnnotationTest{ 6 }
在程序中使用注解
注解在程序中经常和反射一起使用,注解大多数来说都是用来替换配置文件的,拿之前反射的程序来举例:
原配置文件测试
Student
1 package com.xing.annotation; 2 3 4 import lombok.AllArgsConstructor; 5 import lombok.Data; 6 import lombok.NoArgsConstructor; 7 8 @Data 9 @AllArgsConstructor 10 @NoArgsConstructor 11 public class Student { 12 private String name; 13 private int age; 14 15 public void study(){ 16 System.out.println("学生在学习!"); 17 } 18 19 }
pro.properties
1 className=com.xing.annotation.Student 2 methodName=study
MyReflectDemo
1 package com.xing.annotation; 2 3 import java.io.InputStream; 4 import java.lang.reflect.Constructor; 5 import java.lang.reflect.Method; 6 import java.util.Properties; 7 8 /* 9 反射可以跟配置文件结合的方式,动态的创建对象,并调用方法 10 */ 11 public class MyReflectDemo { 12 public static void main(String[] args) throws Exception{ 13 14 //1.读取配置文件中的信息 15 Properties prop = new Properties(); 16 System.out.println(System.getProperty("user.dir"));//用户的当前路径 17 18 ClassLoader classLoader = MyReflectDemo.class.getClassLoader(); 19 InputStream fis = classLoader.getResourceAsStream("pro.properties"); 20 prop.load(fis); 21 fis.close(); 22 System.out.println(prop);//{methodName=study, className=com.xing.annotation.Student} 23 24 //2.获取全类名和方法名 properties 中等号两边是键值对的形式 25 String className = (String) prop.get("className"); 26 String methodName = (String) prop.get("methodName"); 27 28 System.out.println(className);//com.xing.annotation.Student 29 System.out.println(methodName);//study 30 31 //3.利用反射创建对象并运行方法 32 Class<?> clazz = Class.forName(className); 33 34 //获取构造方法 35 Constructor<?> con = clazz.getDeclaredConstructor(); 36 //通过构造方法创建这个对象 37 Object o = con.newInstance(); 38 System.out.println(o);//Student(name=null, age=0) 39 40 //获取成员方法并运行 41 Method method = clazz.getDeclaredMethod(methodName); 42 method.setAccessible(true); 43 //o这个对象去调用method代表的方法 44 method.invoke(o);//学生在学习! 45 46 47 } 48 }
用注解代替配置文件
Student
1 package com.xing.annotation; 2 3 4 import lombok.AllArgsConstructor; 5 import lombok.Data; 6 import lombok.NoArgsConstructor; 7 8 @Data 9 @AllArgsConstructor 10 @NoArgsConstructor 11 public class Student { 12 private String name; 13 private int age; 14 15 public void study(){ 16 System.out.println("学生在学习!"); 17 } 18 19 }
MyAnno
1 package com.xing.annotation; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Retention; 5 import java.lang.annotation.RetentionPolicy; 6 import java.lang.annotation.Target; 7 8 @Target(ElementType.TYPE) 9 @Retention(RetentionPolicy.RUNTIME) 10 public @interface MyAnno { 11 String className(); 12 13 String methodName(); 14 }
AnnotationTest
1 package com.xing.annotation; 2 3 4 import java.lang.reflect.Method; 5 6 @MyAnno(className = "com.xing.annotation.Student",methodName = "study") 7 public class AnnotationTest { 8 9 public static void main(String[] args) throws Exception { 10 11 //1. 解析注解 12 //1.1 获取该类的字节码文件对象 13 Class<AnnotationTest> rac = AnnotationTest.class; 14 //判断类上有没有该注解 15 if(rac.isAnnotationPresent(MyAnno.class)){ 16 //1.2 获取上面的注解对象,其实就是在内存中生成了一个该注解接口的子类实现对象 参数为注解名称 17 MyAnno an = rac.getAnnotation(MyAnno.class); 18 /* 19 相当于 20 public class MyAnno implements AnnoReflect{ 21 public String className(){ 22 return "com.xing.annotation.Student"; 23 } 24 public String methodName(){ 25 return "study"; 26 } 27 } 28 */ 29 } 30 31 //2. 调用注解对象中定义的抽象方法,获取返回值 32 String className = an.className(); 33 String methodName = an.methodName(); 34 System.out.println(className);//com.xing.annotation.Student 35 System.out.println(methodName);//study 36 37 //3.加载该类进内存 38 Class cls = Class.forName(className); 39 //4.创建对象 40 Object obj = cls.newInstance(); 41 //5.获取方法对象 42 Method method = cls.getMethod(methodName); 43 //6.执行方法 44 method.invoke(obj);//学生在学习! 45 } 46 }
使用总结:
在程序中使用注解:获取注解中定义的属性值
-
获取注解定义的位置的对象 (Class, Method, Field)
-
获取指定的注解:getAnnotation(Class)
-
调用注解中的抽象方法获取配置的属性值
简单的测试框架
需求:设计一个框架,检测一个类中的方法是否有异常,并进行统计。
calculator
1 package com.xing.annotation; 2 3 4 public class calculator { 5 6 public void add(){ 7 System.out.println("1+0="+(1+0)); 8 } 9 10 public void sub(){ 11 System.out.println("1-0="+(1-0)); 12 } 13 14 public void mul(){ 15 System.out.println("1*0="+(1*0)); 16 } 17 18 public void div(){ 19 System.out.println("1/0="+(1/0)); 20 } 21 22 public void show(){ 23 System.out.println("今天天气真不错!"); 24 } 25 }
Check
1 package com.xing.annotation; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Retention; 5 import java.lang.annotation.RetentionPolicy; 6 import java.lang.annotation.Target; 7 8 @Retention(RetentionPolicy.RUNTIME) // 运行时 9 @Target(ElementType.METHOD) // 加在方法前面 10 public @interface Check { 11 }
TestCheck
1 package com.xing.annotation; 2 3 import java.io.BufferedWriter; 4 import java.io.FileWriter; 5 import java.io.IOException; 6 import java.lang.reflect.Method; 7 8 /** 简单的测试框架 9 * 当主方法执行后,会自动自行检测所有方法(加了check注解的方法),判断方法是否有异常并记录 10 */ 11 public class TestCheck { 12 public static void main(String[] args) throws IOException { 13 //1. 创建计算机对象 14 calculator c = new calculator(); 15 //2. 获取字节码文件对象 16 Class<? extends calculator> cls = c.getClass(); 17 //3. 获取所有方法 18 Method[] methods = cls.getMethods(); 19 20 int num = 0; //记录出现异常的次数 21 BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt")); 22 23 for(Method method:methods){ 24 //4. 判断方法上是否有Check注解 25 if(method.isAnnotationPresent(Check.class)){ 26 //5. 有注解就执行,捕获异常 27 try { 28 method.invoke(c); 29 } catch (Exception e) { 30 e.printStackTrace(); 31 //6.将异常记录在文件中 32 num++; 33 bw.write(method.getName()+"方法出异常了"); 34 bw.newLine(); 35 bw.write("异常的名称是:"+e.getCause().getClass().getSimpleName()); 36 bw.newLine(); 37 bw.write("异常原因:"+e.getCause().getMessage()); 38 bw.newLine(); 39 bw.write("====================="); 40 bw.newLine(); 41 } 42 } 43 } 44 bw.write("本次测试一共出现"+num+"次异常"); 45 bw.flush(); 46 bw.close(); 47 48 } 49 50 }
在待测试的类中每个需要测试的方法前面都加上@Check
1 package cn.other.annotation.demo; 2 3 /** 计算器类 4 */ 5 public class calculator { 6 @Check 7 public void add(){ 8 System.out.println("1+0="+(1+0)); 9 } 10 @Check 11 public void sub(){ 12 System.out.println("1-0="+(1-0)); 13 } 14 @Check 15 public void mul(){ 16 System.out.println("1*0="+(1*0)); 17 } 18 @Check 19 public void div(){ 20 System.out.println("1/0="+(1/0)); 21 } 22 23 public void show(){ 24 System.out.println("今天天气真不错!"); 25 } 26 }
运行TestCheck类中的主方法,就会自动检查所有注解@Check的方法是否异常:
小结 :
-
大多数时候,我们会使用注解而不是自定义注解
-
注解给编译器和解析程序用
-
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)