注解的定义与处理
定义注解#
使用 @Interface
定义注解
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
注解的参数类似无参方法,可以使用default
设置一个默认值,最常用的参数建议命名为value
元注解#
可以修饰其他注解的注解称为元注解,对于元注解以使用为主,通常不去编写
@Target
@Target
可以定义注解可以用于源码哪些位置
- 类或接口:ElementType.TYPE
- 字段:ElementType.FIELD
- 方法:ElementType.METHOD
- 构造方法:ElementType.CONSTRUCTOR
- 方法参数:ElementType.PARAMETER
例如想要让自定义的注解可作用于方法上,可以通过这样的方式定义:
@Target({ElementType.METHOD})
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
@Retention
该元注解主要定义了注解的生命周期
- 仅编译期:RetentionPolicy.SOURCE
- 仅 class 文件:RetentionPolicy.CLASS
- 运行期:RetentionPolicy.RUNTIME
像
@Override
就是属于第一种,因为其主要作用是让编译器检查该方法是否重写,并不会进入 class 文件
如果@Retention
不存在,则默认为 CLASS 。一般定义的注解都是 RUNTIME 的,所以需要加上@Retention(RetentionPolicy.RUNTIME)
@Repeatable
这个元注解主要是定义注解是否可以重复,用的不多
处理注解#
注解本身对代码逻辑没有影响,如何使用注解由工具决定。SOURCE 类型的注解主要由编译器使用,因此我们一般只使用,不编写。CLASS 类型的注解主要由底层工具库使用,涉及到 class 的加载,一般我们很少用到。只有 RUNTIME 类型的注解不但要使用,还经常需要编写
因为注解定义后也是一种 class ,所有注解都继承自java.lang.annotation.Annotation
,所以读取注解需要使用到反射
一个简单的例子#
注释类
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "";
int type() default 0;
}
实体类
@MyAnnotation(value = "Chinese",type = 1)
public class Person {
private String name;
private int age;
}
测试类
@SpringBootTest
public class AnnotationTest {
@Test
public void testMethod() {
// 方式1
// Class<Person> personClass = Person.class;
// if (personClass.isAnnotationPresent(MyAnnotation.class)) {
// MyAnnotation annotation = personClass.getAnnotation(MyAnnotation.class);
// String value = annotation.value();
// int type = annotation.type();
// System.out.println(value + "---" + type);
// }
//----------------------------------------------
// 方式2
MyAnnotation annotation = Person.class.getAnnotation(MyAnnotation.class);
if(annotation != null){
System.out.println(annotation.value());
System.out.println(annotation.type());
}
}
}
在上面的例子中,注解是作用在类上的,作用在方法、字段和构造方法的注解处理和它大同小异,但是作用在方法参数上的注解处理起来会比较麻烦一些:因为方法参数本身可以看成一个数组,而每个参数又可以定义多个注解,所以,一次获取方法参数的所有注解就必须用一个二维数组来表示
读取方法参数的注解#
方法参数注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MySpeak {
String value() default "";
String toWho() default "nobody";
boolean required() default true;
}
实体类
@MyAnnotation(value = "Chinese", type = 1)
public class Person {
private String name;
private int age;
public void speak(
@MySpeak(value = "hello", toWho = "tom", required = true)
@NotNull
String msg
) {
System.out.println(msg);
}
}
测试类
@SpringBootTest
public class AnnotationTest {
@Test
public void testFunction() throws Exception{
Class<Person> personClass = Person.class;
// 获取方法
Method speakMethod = personClass.getMethod("speak", String.class);
// 获取所有参数的注解
Annotation[][] anno = speakMethod.getParameterAnnotations();
// anno[0]代表第一个参数的注解,anno[0][0]代表第一个参数的第一个注解
for(Annotation a : anno[0]){
if(a instanceof MySpeak){
MySpeak mp = (MySpeak) a;
}else if(a instanceof NotNull){
NotNull nn = (NotNull) a;
}
}
}
}
使用注解#
注释类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
int min() default 0;
int max() default 255;
}
实体类
@MyAnnotation(value = "Chinese", type = 1)
@Data
@AllArgsConstructor
public class Person {
@Range(min = 3,max = 16)
private String name;
@Range(max = 24)
private String city;
private int age;
public void speak(
@MySpeak(value = "hello", toWho = "tom", required = true)
@NotNull
String msg
) {
System.out.println(msg);
}
}
测试类
@SpringBootTest
public class AnnotationTest {
@Test
public void testFunction() throws Exception {
Person person = new Person("tom",
"hebeishengshijiazhuangshixianhuaqu",
18
);
check(person);
}
//注释逻辑
public void check(Person person) throws Exception {
//获取所有字段
Field[] fields = Person.class.getDeclaredFields();
for(Field f : fields){
Range range = f.getAnnotation(Range.class);
//判断字段上是否使用注解 @range
if(range != null){
//因为是私有属性所以必须设置为true
f.setAccessible(true);
//获取每个字段的值
Object val = f.get(person);
if(val instanceof String){
String s = (String) val;
//判断长度
if(s.length() < range.min() || s.length() > range.max()){
throw new RuntimeException("数据长度不合法");
}else{
System.out.println("数据长度合法");
}
}
}
}
}
}
输出
数据长度合法
java.lang.RuntimeException: 数据长度不合法
通过这个例子可以明显的观察到,定义了注解对实际的业务逻辑完全没有影响,还是需要靠编写代码来完成注解的逻辑。比如,定义了@Range
注解,系统不会为我们自动生成检测字段长度是否合法的逻辑,而是需要自己通过反射机制来完成注解的逻辑
参考内容
注解
作者:colee51666
出处:https://www.cnblogs.com/colee51666/p/16470555.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
尊重每一个原创,从你我开始!
转载请注明原文链接,如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“👍”哦,博主在此感谢你的支持!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端