什么是Java注解
# 什么是Java注解
我们学习注解的第一步,首先就是先从最基本的开始,看看注解到底是什么?
注解和反射是Java中非常让人容易忽略的东西,但却很重要,在主流的Spring中更是充满了注解,注解和注释很像,两者其实本质就差不多,注释是给我们程序员看的,而注解呢其实就是给程序看的。
上面所说希望你着重注意以下两点:
1、注解和注释很像
2、注释是给我们程序员看的,而注解呢其实就是给程序看的
用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。所以,可以说注解就是源代码的元数据。比如,下面这段代码:
@Override
public String toString() {
return "This is String Representation of current object.";
}
2
3
4
上面的代码中,我重写了toString()方法并使用了@Override注解。但是,即使我不使用@Override注解标记代码,程序也能够正常执行。那么,该注解表示什么?这么写有什么好处吗?事实上,@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。如果我不小心拼写错误,例如将toString()写成了toStrring(){},而且我也没有使用@Override注解,那程序依然能编译运行。但运行结果会和我期望的大不相同。现在我们了解了什么是注解,并且使用注解有助于阅读程序。
Annotation是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。
注解(Annontation),Java5引入的新特性,位于java.lang.annotation包中。提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。是一种说明、配置、描述性的信息,与具体业务无关,也不会影响正常的业务逻辑。但我们可以用反射机制来进行校验、赋值等操作。
常见的注解:@Override,@author,@param,@Deprecated,@SuppressWarnings。
# 注解的常见用途:
- 生成文档的注解,如@author,@param。
- 跟踪代码依赖性,实现替代配置文件功能,如spring mvc的注解。
- 编译时进行格式检查,如@override。
- 编译时进行代码生成补全,如lombok插件的@Data。
# 注解的分类
Java内置注解
举例: @Override:这个注解代表重写,但实际并不需要手动在重写方法上添加,编译器在编译时会自动添加 @Deprecated:标明已经过时的方法或者类 @SuppressWarnnings:关闭一些对方法、类的警告,简单讲,“我知道代码有问题,但你不要说出来” 把JDK中主要三个注解单拎出来作为一类是想跟框架提供的注解分开,不容易搞混,不然有人以为注解是spring特有的就尴尬了。
第三方框架提供的注解
主流框架Spring中就定义了大量的注解来使得程序编写更加简捷方便,比如**@Bean代表需要把目标类注册到spring的IOC容器中,随用随拿,@Repository用来标记数据访问组件,@Controller标记控制层组件,@Before代表方法执行前执行,@After**代表方法执行后执行等等。 *这类也是算自定义注解,只不过它是框架定义的。
自定义注解
程序员在实际开发中为了满足业务中某些切面设计要求而设定的注解,只要符合自定义注解规范即可。
元注解
简单讲就是定义注解的注解,就跟描述类的类一样,其他类型的注解只需知道是什么作用,会用就行,但想彻底了解注解就得从元注解入手。要创建一个自定义注解,元注解是必不可少的,下面就简单说下元注解的定义规则,先不写例子,就看下jdk提供的注解**@Override**的源码:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
2
3
4
5
java.lang.annotation提供了四种元注解:
- @Documented – 注解是否将包含在JavaDoc中
- @Retention – 什么时候使用该注解
- @Target – 注解用于什么地方
- @Inherited – 是否允许子类继承该注解
- @Repeatable - 是否可重复注解,jdk1.8引入
# 注解的使用
1、定义注解
/**
* 防止重复提交注解
* @author zys
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Resubmit {
/**
* 延时时间 在延时多久后可以再次提交
*
* @return Time unit is one second
*/
int delaySeconds() default 20;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
首先有一个**@interface规定语法来跟编辑器说这是一个注解,接着还需加上@Target和@Retention**这两个元注解,各自的含义解释:
# @Target
同一个注解总不能什么地方都能用吧,方法、属性、构造函数还是有区别的,得分一下,target就是指定该注解能被使用的位置(挑一些常用的解释下):
- 类或接口:
ElementType.TYPE
- 字段:
ElementType.FIELD
- 方法:
ElementType.METHOD
- 构造方法:
ElementType.CONSTRUCTOR
- 方法参数:
ElementType.PARAMETER
- 注解:
ElementType.ANNOTATION_TYPE
@Target支持设置多个位置,比如想放在类或者方法上,那么可以这样写: @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
如果是单个,则允许省去{}符号
# @Retention
定义了注解的生命周期,默认是RetentionPolicy.CLASS
格式如下:
@Retention(RetentionPolicy.SOURCE)
- 仅编译期:
RetentionPolicy.SOURCE
不会被jvm编译进class文件中 - 仅class文件:
RetentionPolicy.CLASS
在class文件中有效,在类加载的时被丢弃,运行时无法获取到。 - 运行期:
RetentionPolicy.RUNTIME
运行时有效,可以使用反射获得该注解的信息。自定义的注解最常用的使用方式。
注解也是一种类,继承自 java.lang.annotation.Annotation
,所以也会被编译成class文件,就拿上面生命周期最短的RetentionPolicy.SOURCE
来说,它是指这类注解在变成class文件之前就被**注解处理器(Annotation Processor)**去掉了,等于说不会被编译到class文件中。 生命周期第二短的RetentionPolicy.Class
会被编译到class文件中,不过在加载后该类型的注解就会被丢弃,而RetentionPolicy.RUNTIME
不光会被编译到class文件,在加载之后也会被保留,在运行期间可以反射读取对应的一些方法和变量信息。
所以生命周期范围大小是: RetentionPolicy.SOURCE(编译)
< RetentionPolicy.Class(类加载)
< RetentionPolicy.RUNTIME(运行)
在实际应用中,需要程序在运行过程中去解析一个class对象,反射获取变量方法来执行一些操作,所以 RetentionPolicy.RUNTIME
是最恰当的(连反射都反射不到,自定义注解拿来干啥?)。
# 其他的元注解
还有一些其他的元注解我觉得可以一笔带过,因为真的不常用:
- @Repeatable 自定义注解是否可重复,就是说加这个元注解之后,在同一个方法(打比方)上面可以添加多个相同的自定义注解
- @Inherited 定义子类是否可继承父类定义的注解,就是说父类定义了一个注解,子类继承父类后也会自动继承该注解
- @Documented 将注解中的元素包含到Javadoc中去,用来生成javadoc用
# 注解的属性
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SignCheck {
String value() default "啦啦啦";
}
2
3
4
5
感觉有点奇怪,为什么定义一个参数后面会跟 ()
,这不是方法的格式吗?其实你这样可以理解,String value()只是一个接口中待实现的方法,在实际的使用过程中比如反射获取注解对象信息时:
SignCheck signCheck = [某个Class对象].getAnnotation(SignCheck.class);//通过反射获取直接注解对象
会在内存中生成一个实现该注解接口的子类对象,这个return看起来就很好理解了:
//实际不会产生以下代码,按照上述思路假设
public class SignCheckImpl implements SignCheck{
public String value(){
return 给注解赋的value值;
}
}
2
3
4
5
6
自定义注解中如果定义另一个属性值叫 value()
,那么在实际使用过程中注解不加这个属性值也能赋予自定义注解变量,但如果属性值定义了多个,就必须一一指明对应:
@SignCheck("lala") //等价于@SignCheck(value = "lala")
public void annotationtest(){
...
}
2
3
4
注解的属性值支持很多类型,除了String,还有八种基本数据类型,Class、枚举类,甚至是注解都可以。 上面的自定义注解扩充下:
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SignCheck {
//基本数据类型
int iValue();
double douValue();
long lonValue();
float floValue();
char chValue();
boolean booValue();
short shoValue();
byte byteValue();
//字符串类型,注意跟char的区别
String stringValue();
//注解类
ExampleAnnoation annocationValue();
//Class
Class<?> classValue();
//枚举类
WeekEnum enumValue();
//还有一些数组,下面就举一个例子
int[] iListValue();
}
enum WeekEnum{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
@interface ExampleAnnoation{
}
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
# 如何使用自定义注解
这个不会陌生,因为平常潜移默化的时候已经在用了,只需要在对的地方(target指定的类/方法/变量等)加上相应的注解和变量就行了,比如上面 SignCheck
注解可以用在方法和类上,那么我们随便找一个方法,在其头部加上注解并声明变量:
@Resubmit(delaySeconds = 5)
# 利用反射获取注解信息
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RunBefore {
String run_name() default "这是默认的值";
}
public class AnnocationTest {
public static void main(String[] args) throws NoSuchMethodException {
Class<AnnocationTest> annocationTestClass = AnnocationTest.class;
//反射获取testAnnocation方法 这里只举例Method类
Method method = annocationTestClass.getMethod("testAnnocation");
//判断是否存在某个注解对象
boolean isExist = method.isAnnotationPresent(RunBefore.class);
System.out.println("是否存在目标注解:" + isExist);
//反射获取目标注解对象
RunBefore runBefore = method.getAnnotation(RunBefore.class);
System.out.println("注解对象属性:" + runBefore.run_name());
}
@RunBefore(run_name = "测试注解运行")
public void testAnnocation() {
}
}
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
# 多注解的获取
上面的例子只是举了反射方法类 Method
上的注解,其他的诸如Field、Class、Constructor操作API都类似,可以自己敲一遍,之所以拿方法类来举例是因为觉得方法类反射会麻烦点,比如testAnnocation方法改成这样:
public void testAnnocation(@NotNull @Range(minlength = 5,maxlength = 10) String name
,@CanBeNull int age) {
}
2
3
一个方法中包多个参数,一个参数包含多个注解(构造器也会这种情况),那么就需要定义一个二维数组来接收这些反射数据,有一个 getParameterAnnotations
方法
public Annotation[][] getParameterAnnotations() {
return sharedGetParameterAnnotations(parameterTypes, parameterAnnotations);
}
2
3
那应该这么接收了:
Class<AnnocationTest> annocationTestClass = AnnocationTest.class;
//反射获取testAnnocation方法
Method method = annocationTestClass.getMethod("testAnnocation", String.class, int.class);
// 获取所有参数的Annotation:
Annotation[][] annos = method.getParameterAnnotations();
for (Annotation[] anno : annos) {
for (Annotation annotation : anno) {
if (annotation instanceof StringRange) {
//StringRange注解
System.out.println("找到了StringRange注解");
System.out.println("minlength:" + ((StringRange) annotation).minlength());
System.out.println("maxlength:" + ((StringRange) annotation).maxlength());
}
if (annotation instanceof NotNull) {
//NotNull注解
System.out.println("找到了NotNull注解");
}
if (annotation instanceof CanBeNull) {
//CanBeNull注解
System.out.println("找到了CanBeNull注解");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
双重遍历,先遍历方法method中的param参数,然后再遍历params参数中的各个注解,根据返回的类型判断来找到对应的注解和输出对应的值:
找到NotNull注解 找到StringRange注解 minlength:5 maxlength:10 找到CanBeNull注解
# 自定义注解示例
1、自定义注解
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Resubmit {
/**
* 延时时间 在延时多久后可以再次提交
*
* @return Time unit is one second
*/
int delaySeconds() default 20;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
2、AOP 切面
import com.alibaba.fastjson.JSONObject;
import com.cn.xxx.common.annotation.Resubmit;
import com.cn.xxx.common.annotation.impl.ResubmitLock;
import com.cn.xxx.common.dto.RequestDTO;
import com.cn.xxx.common.dto.ResponseDTO;
import com.cn.xxx.common.enums.ResponseCode;
import lombok.extern.log4j.Log4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 数据重复提交校验
**/
@Log4j
@Aspect
@Component
public class ResubmitDataAspect {
private final static String DATA = "data";
private final static Object PRESENT = new Object();
@Around("@annotation(com.cn.xxx.annotation.Resubmit)")
public Object handleResubmit(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
//获取注解信息
Resubmit annotation = method.getAnnotation(Resubmit.class);
int delaySeconds = annotation.delaySeconds();
Object[] pointArgs = joinPoint.getArgs();
String key = "";
//获取第一个参数
Object firstParam = pointArgs[0];
if (firstParam instanceof RequestDTO) {
//解析参数
JSONObject requestDTO = JSONObject.parseObject(firstParam.toString());
JSONObject data = JSONObject.parseObject(requestDTO.getString(DATA));
if (data != null) {
StringBuffer sb = new StringBuffer();
data.forEach((k, v) -> {
sb.append(v);
});
//生成加密参数 使用了content_MD5的加密方式
key = ResubmitLock.handleKey(sb.toString());
}
}
//执行锁
boolean lock = false;
try {
//设置解锁key
lock = ResubmitLock.getInstance().lock(key, PRESENT);
if (lock) {
//放行
return joinPoint.proceed();
} else {
//响应重复提交异常
return new ResponseDTO<>(ResponseCode.REPEAT_SUBMIT_OPERATION_EXCEPTION);
}
} finally {
//设置解锁key和解锁时间
ResubmitLock.getInstance().unLock(lock, key, delaySeconds);
}
}
}
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
3、注解使用
@PostMapping("/posts/save")
@Resubmit(delaySeconds = 10)
public ResponseDTO<BaseResponseDataDTO> saveOrder(@RequestBodyRequestDTO<OrderDTO> requestDto) {
// TODO
}
2
3
4
5
完整示例:https://zhuanlan.zhihu.com/p/460493986
- 01
- 保姆级教程 用DeepSeek+飞书,批量写文案、写文章,太高效了06-06
- 03
- 熬夜做PPT?AI一键生成高逼格幻灯片,效率提升10倍!06-06