简介
注解@interface是一种在Java代码中添加元数据(metadata)的方式,它可以用于提供程序的额外信息,但本身并不会直接影响程序的执行。注解可以应用于类、方法、字段和其他程序元素,用于提供关于这些元素的额外信息。
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口。
在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
自定义注解的应用场景
自定义注解注释在方法上,给方法提供元数据,一般与过滤器,拦截器,SpringAOP配置使用,这些系统自动调用的功能,可以获取自定义元数据的信息做相应的处理。也可以程序在调用的方法中,使用注解的元信息,例如阿里提供的easyExcel,在需要输出excel的数据的类上添加注解提供列名,数据格式等信息,在客户化方法调用生成excel获取这些元数据形成表格。
- 1.记录操作日志
- 2.权限校验
- 3.参数校验
- 4.easy excel
- 5.事务管理
- 6.数据源切换
元注解(可以注解在自定义注解上的注解)
-
@Target:用于指定注解可以应用于的目标元素类型,限定注解可以放在那些对象上
- ElementType.TYPE: 类、接口或枚举类型
- ElementType.FIELD: 字段(包括枚举常量)
- ElementType.METHOD: 方法
- ElementType.PARAMETER: 方法或构造函数的参数
- ElementType.CONSTRUCTOR: 构造函数
- ElementType.LOCAL_VARIABLE: 局部变量
- ElementType.ANNOTATION_TYPE: 注解类型
- ElementType.PACKAGE: 包
- ElementType.TYPE_PARAMETER: 类型参数(Java 8+)
- ElementType.TYPE_USE: 类型使用(Java 8+)
-
@Retention:用于指定注解的保留策略(retention policy)。保留策略定义了注解在何时有效以及在何时丢弃。
- RetentionPolicy.SOURCE: 这是最短的保留策略。注解仅保留在源代码中,不会被编译进 class 文件,也不会对运行时产生任何影响。一般用于生成文档等需要在源代码中保留信息的情况。
- RetentionPolicy.CLASS: 注解会被编译进 class 文件中,但在运行时不会被 JVM 保留。这是默认的保留策略,如果不指定 @Retention,则会使用这种策略。
- RetentionPolicy.RUNTIME: 注解会被编译进 class 文件中,并在运行时被 JVM 保留,可以通过反射机制获取注解信息。这种保留策略通常用于实现自定义的注解处理器,或者需要在运行时获取注解信息的情况。
-
@Inherited:表示:父类有这个注解,子类继承父类,会一并继承这个注解。
-
@Documented:API 文档中,而不影响注解应用的元素是否包含在文档中。
案例:自定义注解+环绕通知 记录操作日志
要求:知道操作类型(type):增删改查,操作说明(action):具体做什么,方法入参,出参数,方法名,执行结果。
定义表:
CREATE TABLE learn.user_operation_log (
id bigint ,
type VARCHAR(30) NOT NULL,
action VARCHAR(240) NOT NULL,
method_name VARCHAR(80) NOT NULL,
method_params TEXT,
result_code VARCHAR(240),
result_msg VARCHAR(240) ,
create_by long NOT NULL,
update_by long NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
定义DAO
@Data
@TableName("user_operation_log")
public class OperationLogDO extends BaseDO {
private String type;
private String action;
private String methodName;
private String methodParams;
private String resultCode;
private String resultMsg;
}
这里面的ID字段使用mybatis自动填充,也可以手工赋值
@Data
public class BaseDO implements Serializable {
/**
*
*/
// 雪花算法:一位未定 + 41位时间数字 + 10位机器数(5位机器时区+5位机器编码)+ 12位随机数
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
*
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
*
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
/**
*
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
*
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
Mapper和持久化Service省略
定义自定义注解
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface AuditLog {
// 1.操作描述
String action() default "";
// 2.操作类型(增删改查)
OperateEnum type() default OperateEnum.MODIFY;
// 3.是否记录参数
boolean isRecord() default true;
}
定义切面:环绕通知
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class AuditAspect {
private final ObjectMapper objectMapper;
private final OperationLogService operationLogService;
@Around("@annotation(auditLog)")
public Object around(ProceedingJoinPoint joinPoint, AuditLog auditLog) throws Throwable {
log.info("开始记录日志");
// 执行连接点前,记录信息
OperationLogDO userLogRecordDO = buildRequestParams(joinPoint, auditLog);
try {
Object result = joinPoint.proceed();
if (result != null) {
String resultJson = objectMapper.writeValueAsString(result);
userLogRecordDO.setResultCode("S");
userLogRecordDO.setResultMsg(resultJson);
}
return result;
} catch (Exception e) {
userLogRecordDO.setResultCode("E");
userLogRecordDO.setResultMsg("后端未知异常,错误消息:" + e.getMessage());
throw e;
} finally {
//操作日志入库
try {
if (userLogRecordDO.getId() != null) {
operationLogService.updateById(userLogRecordDO);
}
} catch (Exception e) {
log.error("更新用户操作异常", e);
}
log.info("结束记录日志");
}
}
private OperationLogDO buildRequestParams(ProceedingJoinPoint joinPoint, AuditLog auditLog) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
// AuditLog auditLog = method.getAnnotation(AuditLog.class);
OperationLogDO operationLogDO = new OperationLogDO();
operationLogDO.setAction(auditLog.action());
operationLogDO.setType(auditLog.type().name());
// 类名+方法名
operationLogDO.setMethodName(method.getDeclaringClass().getSimpleName() + "." + method.getName());
// 处理入参数
try {
Parameter[] parameters = methodSignature.getMethod().getParameters();
HashMap<String, Object> paramMap = new HashMap<>();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < parameters.length; i++) {
// Class<?> type = parameters[i].getType();
// if (ServletResponse.class.isAssignableFrom(type) || ServletRequest.class.isAssignableFrom(type)) {
// continue;
// }
String name = parameters[i].getName();
paramMap.put(name, args[i]);
operationLogDO.setMethodParams(objectMapper.writeValueAsString(paramMap));
}
} catch (JsonProcessingException e) {
log.warn("构建入参异常:{}", e.getMessage());
}
//操作日志入库
try {
operationLogService.save(operationLogDO);
} catch (Exception e) {
log.error("记录用户操作异常:", e);
}
return operationLogDO;
}
}
操作类型常量
public enum OperateEnum {
ADD,
DELETE,
MODIFY,
SAVE_OR_MODIFY,
SELECT;
}
方法上添加自定义注解
@Slf4j
@Service
@RequiredArgsConstructor
public class StudentServiceImpl implements StudentService {
private final StudentRepository studentRepository;
@Override
// @MetricTime("createStudent") // 切面与注释配套使用
@AuditLog(action = "创建学生", type = OperateEnum.ADD, isRecord = true)
public StudentResult createStudent(StudentCreateCmd cmd) {
log.info("create student cmd:{}", cmd);
StudentDO studentDO = StudentCreateCmd.convertToDO(cmd);
studentRepository.save(studentDO);
log.info("create student end");
return StudentResult.convertFromDO(studentDO);
}
}
运行测试结果
参考网址:
https://blog.csdn.net/m0_52767002/article/details/133793285