自定义注解的理解及其应用
在项目中经常会用到自定义注解,下面讲解一下自定义注解的理解及其应用。
一、元注解
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。
Java5.0定义的元注解:
- @Target
- @Retention
- @Documented
- @Inherited
@Target
作用:描述该注解修饰的范围,可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。
取值(ElementType):
CONSTRUCTOR:用于描述构造器;
FIELD:用于描述域;
LOCAL_VARIABLE:用于描述局部变量;
METHOD:用于描述方法;
PACKAGE:用于描述包;
PARAMETER:用于描述参数;
TYPE:用于描述类、接口(包括注解类型) 或enum声明;
@Retention
作用:描述该注解的生命周期,表示在什么编译级别上保存该注解的信息。Annotation被保留的时间有长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)
取值(RetentionPoicy):
SOURCE:在源文件中有效(即源文件保留)
CLASS:在class文件中有效(即class保留)
RUNTIME:在运行时有效(即运行时保留)
@Documented
@Documented Annotation的作用是在生成javadoc文档的时候将该Annotation也写入到文档中
@Inherited
作用:@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
二、自定义注解
使用@interface自定义注解,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
定义注解格式:public @interface 注解名 {定义体}
注解参数的可支持数据类型:
- 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
- String类型
- Class类型
- enum类型
- Annotation类型
- 以上所有类型的数组
参数定义要点:
- 只能用public或默认(default)这两个访问权修饰;
- 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组;
- 如果只有一个参数成员,建议参数名称设为value();
- 注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或负数作为默认值是一种常用的做法。
简单的自定义注解实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | /** *自定义注解MyAnnotation */ @Target(ElementType.TYPE) //目标对象是类型 @Retention(RetentionPolicy.RUNTIME) //保存至运行时 @Documented //生成javadoc文档时,该注解内容一起生成文档 @Inherited //该注解被子类继承 public @ interface MyAnnotation { public String value() default "" ; //当只有一个元素时,建议元素名定义为value(),这样使用时赋值可以省略"value=" String name() default "devin" ; //String int age() default 18; //int boolean isStudent() default true ; //boolean String[] alias(); //数组 enum Color {GREEN, BLUE, RED,} //枚举类型 Color favoriteColor() default Color.GREEN; //枚举值 } @MyAnnotation( value = "info" , name = "myname" , age = 99, isStudent = false , alias = { "name1" , "name2" }, favoriteColor = MyAnnotation.Color.RED ) public class MyClass { //使用MyAnnotation注解,该类生成的javadoc文档包含注解信息如下: /* @MyAnnotation(value = "info", name = "myname", age = 99, isStudent = false, alias = {"name1","name2"}, favoriteColor = Color.RED) public class MyClass extends Object */ } public class MySubClass extends MyClass{ //子类MySubClass继承了父类MyClass的注解 } |
解析注解信息
Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。相应地,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素。
实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
AnnotatedElement接口是所有程序元素(Field、Method、Package、Class和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下七个方法来访问Annotation信息:
- <T extends Annotation> T getAnnotation(Class<T> annotationClass) :返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null;
- Annotation[] getDeclaredAnnotation(Class<T>):返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null;与此接口中的其他方法不同,该方法将忽略继承的注解;
- Annotation[] getAnnotations():返回该程序元素上存在的所有注解;
- Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注解;
- Annotation[] getAnnotationsByType(Class<T>):返回直接存在于此元素上指定注解类型的所有注解;
- Annotation[] getDeclaredAnnotationsByType(Class<T>):返回直接存在于此元素上指定注解类型的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注解;
- boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | /***********注解声明***************/ /** * 水果名称注解 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @ interface FruitName { String value() default " " ; } /** * 水果颜色注解 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @ interface FruitColor { /** * 颜色枚举 */ public enum Color{BLUE, RED, GREEN}; /** * 颜色属性 * @return */ Color fruitColor() default Color.GREEN; } /** * 水果供应商注解 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @ interface FruitProvider { /** * 供应商编号 * @return */ public int id() default -1; /** * 供应商名称 * @return */ public String name() default " " ; /** * 供应商地址 * @return */ public String address() default " " ; } /***********注解使用***************/ public class Apple { @FruitName( "Apple" ) private String appleName; @FruitColor(fruitColor = FruitColor.Color.RED) private String appleColor; @FruitProvider(id = 1, name = "陕西红富士集团" , address = "陕西红富士大厦" ) private String appleProvider; public String getAppleProvider() { return appleProvider; } public void setAppleProvider(String appleProvider) { this .appleProvider = appleProvider; } public String getAppleName() { return appleName; } public void setAppleName(String appleName) { this .appleName = appleName; } public String getAppleColor() { return appleColor; } public void setAppleColor(String appleColor) { this .appleColor = appleColor; } public void displayName(){ System. out .println(getAppleName()); } } /***********注解信息获取***************/ public class AnnotationParser { public static void main(String[] args) { Field[] fields = Apple. class .getDeclaredFields(); for (Field field : fields) { //System.out.println(field.getName().toString()); if (field.isAnnotationPresent(FruitName. class )){ FruitName fruitName = field.getAnnotation(FruitName. class ); System. out .println( "水果的名称:" + fruitName.value()); } else if (field.isAnnotationPresent(FruitColor. class )){ FruitColor fruitColor = field.getAnnotation(FruitColor. class ); System. out .println( "水果的颜色:" +fruitColor.fruitColor()); } else if (field.isAnnotationPresent(FruitProvider. class )){ FruitProvider fruitProvider = field.getAnnotation(FruitProvider. class ); System. out .println( "水果供应商编号:" + fruitProvider.id() + " 名称:" + fruitProvider.name() + " 地址:" + fruitProvider.address()); } } } } /***********输出结果***************/ 水果的名称:Apple 水果的颜色:RED 水果供应商编号:1 名称:陕西红富士集团 地址:陕西红富士大厦 |
JDK8注解新特性
JDK 8 主要有两点改进:类型注解和重复注解
- 类型注解:类型注解在@Target中增加了两个ElementType参数:
1、ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中;
2、ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中(例如声明语句、泛型和强制转换语句中的类型);
从而扩展了注解使用的范围,可以使用在创建类实例、类型映射、implements语句、throw exception声明中的类型前面。例如:
1、创建类实例: new @Interned MyObject();
2、类型映射:myString = (@NonNull String) str;
3、implements 语句中 :class UnmodifiableList<T> implements @Readonly List<@Readonly T> { ... }
4、throw exception声明:void monitorTemperature() throws @Critical TemperatureException { ... }
简单示例:
1 2 3 4 5 6 7 8 9 | @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) public @ interface Encrypted { } public class MyTypeAnnotation { @Encrypted String data; List<@Encrypted String> strings; } |
类型注解的作用:
首先,局域变量声明中的类型注解也可以保留在类文件中,完整泛型被保留,并且在运行期可以访问,从而有助于我们获取更多的代码信息;其次,类型注解可以支持在的程序中做强类型检查。配合第三方工具check framework,可以在编译的时候检测出runtime error,以提高代码质量;最后,代码中包含的注解清楚表明了编写者的意图,使代码更具有表达意义,有助于阅读者理解程序,毕竟代码才是“最根本”的文档、“最基本”的注释。
重复注解
重复注释就是运行在同一元素上多次使用同一注解,使用@Repeatable注解。之前也有重复使用注解的解决方案,但可读性不是很好,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public @ interface Authority { String role(); } public @ interface Authorities { Authority[] value(); } public class RepeatAnnotationUseOldVersion { @Authorities({@Authority(role= "Admin" ),@Authority(role= "Manager" )}) public void doSomeThing(){ } } |
而现在的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Repeatable(Authorities. class ) public @ interface Authority { String role(); } public @ interface Authorities { Authority[] value(); } public class RepeatAnnotationUseNewVersion { @Authority(role= "Admin" ) @Authority(role= "Manager" ) public void doSomeThing(){ } } |
不同的地方是,创建重复注解Authority时,加上@Repeatable,指向存储注解Authorities,在使用时候,直接可以重复使用Authority注解。从上面例子看出,java 8里面做法更适合常规的思维,可读性强一点。
参见:http://www.jianshu.com/p/4068da3c8d3d
三、自定义注解的应用
(1)利用自定义注解打印接口调用时长日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #创建ServiceLog类用来自定义注解 @Retention(RetentionPolicy.Runtime) @Target(ElementType.METHOD) public @ interface ServiceLog { } #定义ServiceLogAspect类用来定义日志打印信息 @Component @Aspect public class ServiceLogAspect { public ThreadLocal<Long> local = new ThreadLocal<Long>(); @Pointcut( "@annotation(com.test.XXX.ServiceLong)" ) public void pointCut() { } @Before( "pointCut()" ) public void before(JoinPoint point) { String methodName = point.getTarget().getClass().getName()+ "." +point.getSignature().getName(); local. set (System.currentTimeMillis()); } @After( "pointCut()" ) public void after(JoinPoint point) { long start = local. get (); String methodName = point.getTarget().getClass().getName()+ "." +point.getSignature().getName(); System. out .println(System.currentTimeMillis()-start)); } @AfterThrowing(pointcut= "pointCut()" ,throwing= "error" ) public void throwing(JoinPoint point,Throwable error) { System. out .println( "error" ); } } |
完成上述定义,如果需要记录方法调用时长时,可以直接使用@ServiceLog注解。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构