前言
Java注解是一种元数据(metadata)机制,它提供了在Java代码中添加附加信息的方式。注解可以应用于类、方法、字段和其他程序元素,以提供关于这些元素的额外信息。
注解以 @
符号开头,后面跟着注解的名称。注解可以包含属性,属性可以带有默认值。使用注解时,可以为属性提供值,也可以使用默认值。
注解可以用于各种目的,例如:
- 提供编译时的静态检查和验证。
- 在运行时通过反射获取元数据信息。
- 自动生成代码或配置文件。
- 在框架和库中进行配置和扩展。
Java提供了一些内置的注解,如@Override
、@Deprecated
和@SuppressWarnings
。此外,开发人员还可以自定义注解,以满足特定的需求。
通过使用注解,可以在代码中添加额外的信息,从而提供更多的灵活性和可读性,同时也可以为工具和框架提供更多的上下文信息。
一、自定义注解与元注解
分析 Java 中自带的 @Override 注解 , 源码如下 :
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
注解分为两部分 :
- 元注解:
元注解是专门用来注解其他注解的注解,听起来有些绕口,实际上就是专门为自定义注解提供的注解。java.lang.annotation提供了五种元注解:
@Documented
– 注解是否将包含在JavaDoc中
@Retention
– 什么时候使用该注解
@Target
– 注解用于什么地方
@Inherited
– 是否允许子类继承该注解
@Repeatable
- 是否可重复注解,jdk1.8引入 - 自定义注解:public @interface 注解名称 ;
除了上面的元注解其他的注解其实都是自定义注解。
这里不准备深入介绍以上四个元注解的作用,大家可以自行学习。
本文即将提到的几个例子,都是在日常工作中真实使用到的场景,这例子有一个共同点,那就是都用到了Spring的AOP技术。
什么是AOP以及他的用法相信很多人都知道,这里也就不展开介绍了。
1.注解属性类型
注解属性 ( 接口方法 ) 返回值类型要求 :
① 基本数据类型 : byte , short , int , long , float , double , char , boolean ;
② 字符串类型 : String ;
③ 枚举类型 : enum ;
④ 注解类型 ;
⑤ 以上类型的数组形式 ;
注解属性返回值必须是以上的类型 , 不能设置其它类型返回值 , 否则会报错 ;
注解中定义了属性 , 在使用注解时 , 需要 给 注解属性 赋值;
定义 注解属性 时 , 可以 使用 default 关键字 指定属性默认值 , 下面代码中 , 制定 注解属性 intValue 值类型为 int 整型 , 默认值 88;
int intValue() default 88
如果 注解属性 指定了默认值 , 在使用注解时 , 可以选择 不为该属性赋值 ( 此时使用默认属性值 ) , 也可以进行赋值 ( 指定一个新的属性值 ) ;
如果 注解属性 没有指定默认值 , 则使用 注解 时 , 必须为其指定一个默认值 , 否则编译时报错 ;
数组类型 的 注解属性 赋值 时 , 使用大括号进行赋值 , 大括号内是数组元素 , 如果只有一个属性 , 可以省略大括号 ,
注解声明示例 :
public @interface Annotation {
/**
* 字符串类型
* @return
*/
String stringValue();
/**
* int 基本类型
* @return
*/
int intValue() default 88;
/**
* 枚举类型
* @return
*/
Number enumValue();
/**
* 注解类型
* @return
*/
Annotation2 annotationValue();
/**
* 字符串数组类型
* @return
*/
String[] stringArrayValue();
}
枚举类:
public enum Number {
ONE, TWO, THREE
}
Annotation2 注解类 :
public @interface Annotation2 {
// 自定义属性可以为空,只声明一个自定义注解
}
自定义注解使用:
/**
* 注解生成文档
*
* @author xjw
* @version 1.0
* @since 1.0
*/
public class Example{
/**
* 构造函数
* @param name 参数一
* @param age 参数二
*/
// 如果你的注解定义了属性,在引用时这些属性都是不可缺少的
@Annotation(
stringValue = "tom",
enumValue = Number.ONE,
annotationValue = @Annotation2,
stringArrayValue = {"tom", "jerry"})
Student(String name, int age){
}
@SuppressWarnings("all")
@Override
public String toString() {
return super.toString();
}
}
代码分析 : 重点关注注解的使用 , 使用注解时 , 需要给 没有默认值 的 注解属性 赋值 , 格式为 注解属性名称 = 对应类型属性值 , 如果 注解属性 有默认值 , 则
@Annotation(stringValue = "tom", enumValue = Number.ONE, stringArrayValue = {"tom", "jerry"})
二、注解的生命周期以及作用目标
1.生命周期
通过@Retention
定义注解的生命周期,格式如下:
@Retention(RetentionPolicy.SOURCE)
其中RetentionPolicy的不同策略对应的生命周期如下:
RetentionPolicy.SOURCE : 仅存在于源代码中,编译阶段会被丢弃,不会包含于class字节码文件中。@Override
, @SuppressWarnings
都属于这类注解。
RetentionPolicy.CLASS : 默认策略,在class字节码文件中存在,在类加载的时被丢弃,运行时无法获取到。
RetentionPolicy.RUNTIME : 始终不会丢弃,可以使用反射获得该注解的信息。自定义的注解最常用的使用方式。
2.作用目标
通过@Target
定义注解作用的目标,比如作用于类、属性、或方法等,默认可用于任何地方。格式如下:
@Target(ElementType.TYPE)
对应ElementType参数值适用范围如下:
- ElementType.TYPE: 类、接口、注解、enum
- ElementType.CONSTRUCTOR: 构造函数
- ElementType.FIELD: 成员变量、对象、属性、枚举的常量
- ElementType.LOCAL_VARIABLE: 局部变量
- ElementType.METHOD: 方法
- ElementType.PACKAGE: 包
- ElementType.PARAMETER: 参数
- ElementType.ANNOTATION_TYPE): 注解
- ElementType.TYPE_PARAMETER:类型参数,表示这个注解可以用在 Type的声明式前,jdk1.8引入。
- ElementType.TYPE_USE:类型的注解,表示这个注解可以用在所有使用Type的地方(如:泛型,类型转换等),jdk1.8引入。
Documented
@Documented
,表示是否将此注解的相关信息添加到javadoc文档中。
Inherited
@Inherited
,定义该注解和子类的关系,使用此注解声明出来的自定义注解,在使用在类上面时,子类会自动继承此注解,否则,子类不会继承此注解。注意,使用Inherited声明出来的注解,只有在类上使用时才会有效,对方法,属性等其他无效。
三,简单使用
结合aop使用,完成一个当方法抛出异常时发送通知:
package com.kimo.shop.admin.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 异常捕获切面
* author:xjw💕
* time:2023.07.20 10:52
* */
@Aspect
@Component
public class AbnormalAspect {
@AfterThrowing(value = "@annotation(com.kimo.shop.admin.aop.AbnormalAnnotations)",throwing = "exception")
public void afterThrowing(JoinPoint joinPoint,Exception exception){
// get joinPoint methodName
String methodName = joinPoint.getSignature().getName();
// get joinPoint className
String className = joinPoint.getTarget().getClass().getName();
// Get the exception information
String exceptionMessage = exception.getMessage();
// // Perform other operations, such as querying switch status and sending notifications
System.out.println("方法 " + methodName + " 在类 " + className + " 中抛出了异常");
System.out.println("异常信息:" + exceptionMessage);
// 当你需要从自定义注解里面取出属性,可以看下面的方法
// MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// Method method = methodSignature.getMethod();
// AbnormalAnnotations annotation = method.getAnnotation(AbnormalAnnotations.class);
// System.out.println("发生异常时间:" + annotation.date());
}
}
第二个示例:通过注解进行赋值和通过注解进行校验。
两个注解:一个用来赋值,一个用来校验。
/**
* 性别赋值
* @author xjw
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
@Inherited
public @interface InitSex {
enum SEX_TYPE {MAN, WOMAN}
SEX_TYPE sex() default SEX_TYPE.MAN;
}
/**
* 年龄校验
* @author xjw
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
@Inherited
public @interface ValidateAge {
/**
* 最小值
*/
int min() default 18;
/**
* 最大值
*/
int max() default 99;
/**
* 默认值
*/
int value() default 20;
}
定义数据模型
这里用User类来表示具体待处理的数据对象。
/**
* user
* @author xjw
*/
public class User {
private String username;
@ValidateAge(min = 20, max = 35, value = 22)
private int age;
@InitSex(sex = InitSex.SEX_TYPE.MAN)
private String sex;
// 省略getter/setter方法
}
测试调用
具体测试调用的过程,参考代码中的注解,其中initUser方法来演示通过反射给属性赋值,checkUser方法通过反射拿到当前属性的值进行对比校验。
import java.lang.reflect.Field;
/**
* @author xjw
*/
public class TestInitParam {
public static void main(String[] args) throws IllegalAccessException {
User user = new User();
initUser(user);
// 年龄为0,校验为通过情况
boolean checkResult = checkUser(user);
printResult(checkResult);
// 重新设置年龄,校验通过情况
user.setAge(22);
checkResult = checkUser(user);
printResult(checkResult);
}
static void initUser(User user) throws IllegalAccessException {
// 获取User类中所有的属性(getFields无法获得private属性)
Field[] fields = User.class.getDeclaredFields();
// 遍历所有属性
for (Field field : fields) {
// 如果属性上有此注解,则进行赋值操作
if (field.isAnnotationPresent(InitSex.class)) {
InitSex init = field.getAnnotation(InitSex.class);
field.setAccessible(true);
// 设置属性的性别值
field.set(user, init.sex().toString());
System.out.println("完成属性值的修改,修改值为:" + init.sex().toString());
}
}
}
static boolean checkUser(User user) throws IllegalAccessException {
// 获取User类中所有的属性(getFields无法获得private属性)
Field[] fields = User.class.getDeclaredFields();
boolean result = true;
// 遍历所有属性
for (Field field : fields) {
// 如果属性上有此注解,则进行赋值操作
if (field.isAnnotationPresent(ValidateAge.class)) {
ValidateAge validateAge = field.getAnnotation(ValidateAge.class);
field.setAccessible(true);
int age = (int) field.get(user);
if (age < validateAge.min() || age > validateAge.max()) {
result = false;
System.out.println("年龄值不符合条件");
}
}
}
return result;
}
static void printResult(boolean checkResult) {
if (checkResult) {
System.out.println("校验通过···");
} else {
System.out.println("校验未通过···");
}
}
}
打印日志
完成属性值的修改,修改值为:MAN
年龄值不符合条件
校验未通过···
校验通过···
四,注解属性赋值简化
如果注解属性名称是 value , 并且注解中只有1个属性 , 那么在使用注解为注解属性赋值时 , 可以省略注解名称 , 直接传入注解属性值;
示例 : JDK 自带的 SuppressWarnings 注解:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
注解使用 : 使用 SuppressWarnings 注解时 , 直接传入 “all” 参数 , 省略了注解属性名称:
@SuppressWarnings("all")
@Override
public String toString() {
return super.toString();
}
满足两个条件 , 才能使用上述简化方式 ;