目录
1、为什么要使用自定义注解?
以【记录操作日志】为例,一般在开发中为了记录用户操作,当系统出现问题时,可以追溯日志所提供详细的错误信息,帮助开发者快速定位问题的原因
最简单的方法:在每个方法中增加日志信息打印代码,来记录操作日志,但是这样重复代码也很多,也不易扩展,而且易出错
使用自定义注解就会方便很多,减少不必要的重复代码,减少非业务代码的侵入性,如果需要扩展操作用户的信息以及IP就会很方便,易于扩展和维护
2、自定义注解
2.1、@interface定义注解接口
public @interface LogOperation {
String value() default "";
}
2.2、@Retention
指定注解的保留策略
即注解在什么阶段可用,可选值有:
RetentionPolicy.SOURCE
:注解仅在源码阶段保留,编译时丢弃。RetentionPolicy.CLASS
:注解在编译时保留,但在运行时丢弃(默认值)。RetentionPolicy.RUNTIME
:注解在运行时保留,可以通过反射获取
@Retention(RetentionPolicy.RUNTIME)
public @interface LogOperation {
String value() default "";
}
2.3、@Target指定注解的作用目标
最常用的元注解是@Target,使用@Target可以定义Annotation能够被应用于源码的哪些位置:
-
类或接口:
ElementType.TYPE
-
字段:
ElementType.FIELD
-
方法:
ElementType.METHOD
-
构造方法:
ElementType.CONSTRUCTOR
-
方法参数:
ElementType.PARAMETER
🌰:在操作日志自定义注解@LogOperation
可用在方法上,必须添加一个@Target(ElementType.METHOD)
:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogOperation {
String value() default "";
}
2.4、注解处理
🔴运行时处理(常用)
- 切面(AOP):通过
@Aspect
注解和Spring AOP的代理机制,利用反射机制在方法调用前后插入额外的逻辑 - 拦截器:通过实现
HandlerInterceptor
或MethodInterceptor
接口,利用反射机制在方法调用前后执行额外逻辑
🔴编译时处理
- 注解处理器:通过实现 javax.annotation.processing.AbstractProcessor接口创建自定义的注解处理器,在编译阶段处理注解,可以生成额外的源代码或字节码文件
2.4.1、运行时注解处理
🍃常见场景:日志记录、权限控制、缓存管理、事务管理、自定义逻辑注入等等
首先定义SysLogOperation,记录操作日志内容,其中包方法信息、操作用户信息、网络信息其他等等
@Data
public class SysLogOperation {
private Long id;
private String module;
private String operation;
private String requestUri;
private String requestMethod;
private String requestParams;
private Integer requestTime;
private String userAgent;
private String ip;
private Integer status;
private String creatorName;
private Long creator;
private Date createDate;
}
2.4.1.1 切面
定义记录日志信息的切面类LogOperationAspect
@Slf4j
@Aspect // 声明这个类是一个 切面类
@Component。// 把类注册到 Spring 中
public class LogOperationAspect {
@Autowired
private ModuleConfig moduleConfig;
@Autowired
private LogProducer logProducer;
public LogOperationAspect() {
}
// 将一个注解定义成 切点
@Pointcut("@annotation(com.echola.log.annotation.LogOperation)")
public void logPointCut() {
}
// 环绕通知,在切点方法执行前后进行拦截,保存日志
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
long time;
try {
Object result = point.proceed();
time = System.currentTimeMillis() - beginTime;
this.saveLog(point, time, OperationStatusEnum.SUCCESS.value());
log.info(" message is : {}", message);
return result;
} catch (Exception var7) {
time = System.currentTimeMillis() - beginTime;
this.saveLog(point, time, OperationStatusEnum.FAIL.value());
throw var7;
}
}
private void saveLog(ProceedingJoinPoint joinPoint, long time, Integer status) {
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
SysLogOperation log = new SysLogOperation();
LogOperation annotation = (LogOperation)method.getAnnotation(LogOperation.class);
if (annotation != null) {
log.setOperation(annotation.value());
}
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
UserDetail user = SecurityUser.getUser();
if (user != null) {
log.setCreator(user.getId());
log.setCreatorName(user.getUsername());
} else {
String userId = request.getHeader("userId");
if (userId != null) {
log.setCreatorName(userId);
}
}
log.setId(IdWorker.getId());
log.setModule(this.moduleConfig.getName());
log.setStatus(status);
log.setRequestTime((int)time);
log.setCreateDate(new Date());
log.setIp(IpUtils.getIpAddr(request));
log.setUserAgent(request.getHeader("User-Agent"));
log.setRequestUri(request.getRequestURI());
log.setRequestMethod(request.getMethod());
Object[] args = joinPoint.getArgs();
try {
String params = JSON.toJSONString(args[0]);
log.setRequestParams(params);
} catch (Exception var13) {
}
//保存日志
//此处推送日志到消息中间件,可以使用控制台打印、Redis存储日志其他方式保存日志
this.logProducer.saveLog(log);
}
}
2.4.1.2 拦截器
定义记录日志信息的拦截器类LogOperationInterceptor
@Component
public class LogOperationInterceptor implements HandlerInterceptor {
@Autowired
private ModuleConfig moduleConfig;
@Autowired
private LogProducer logProducer;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取当前请求的方法// 获取当前请求的方法
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 检查方法上是否有@LogOperation注解
if (method.isAnnotationPresent(LogOperation.class)) {
LogOperation logOperation = method.getAnnotation(LogOperation.class);
String operation = logOperation.value();
String param = JSON.toJSONString(request.getParameterMap());
//获取请求参数
Object[] args = request.getParameterMap().values().toArray();
//方法耗时
long time;
long beginTime = System.currentTimeMillis();
try {
time = System.currentTimeMillis() - beginTime;
this.saveLog(operation, param, time, OperationStatusEnum.SUCCESS.value());
return true;
} catch (Exception var7) {
time = System.currentTimeMillis() - beginTime;
this.saveLog(operation, param, time, OperationStatusEnum.FAIL.value());
throw var7;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 处理完成后调用
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 请求完成后的回调
}
private void saveLog(String operation, String param, long time, Integer status) {
SysLogOperation log = new SysLogOperation();
log.setOperation(operation);
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
UserDetail user = SecurityUser.getUser();
if (user != null) {
log.setCreator(user.getId());
log.setCreatorName(user.getUsername());
} else {
String userId = request.getHeader("userId");
if (userId != null) {
log.setCreatorName(userId);
}
}
log.setId(IdWorker.getId());
log.setModule(this.moduleConfig.getName());
log.setRequestParams(param);
log.setStatus(status);
log.setRequestTime((int) time);
log.setCreateDate(new Date());
log.setIp(IpUtils.getIpAddr(request));
log.setUserAgent(request.getHeader("User-Agent"));
log.setRequestUri(request.getRequestURI());
log.setRequestMethod(request.getMethod());
//保存日志
//此处推送日志到消息中间件,可以使用控制台打印、Redis存储日志其他方式保存日志
this.logProducer.saveLog(log);
}
}
配置拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoggingInterceptor loggingInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor);
}
}
2.4.2、编译时注解处理
编译时处理是指在编译阶段通过注解处理器(Annotation Processor)来处理注解。这种方式的优点是性能高,因为处理过程在编译阶段完成,不会影响运行时性能
🍃常见场景:代码生成、代码检查、性能优化等等
例如:处理一个@CustoAnnotation的注解
@SupportedAnnotationTypes("CustomAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class CustomAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(MyCustomAnnotation.class)) {
MyCustomAnnotation annotation = element.getAnnotation(MyCustomAnnotation.class);
System.out.println("Annotation value: " + annotation.value());
// 在这里可以生成新的源代码文件或其他处理逻辑
}
return true;
}
}
2.5、使用自定义注解
在需要使用自定义注解的地方使用该注解
public class TestClass {
@LogOperation("新增")
public void save() {
// 方法实现
}
}
3、总结
自定义注解被广泛应用于日常开发中,使用 @interface 关键字来定义自定义注解,之后再使用 AOP切面 或拦截器的方式实现自定义注解,可以实现日志记录、性能监控、权限判断和幂等性判断等功能,从而提高代码的复用性和可维护性