为什么会有自定义注解的存在呢?因为一个接口可能需要执行某个动作,而有些接口不需要,自定义注解应用灵活,比如验证是否登录注解,只需要在接口上面加上自定义的注解就可以拦截,又或者一些关键性的吊用接口调用操作,比如登录,需要将日志记录到数据库,也需要自定义注解
下面以一个例子解释自定义注解的使用
package com.bootdo.clouddocommon.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
package com.bootdo.clouddocommon.aspect;
import com.bootdo.clouddocommon.annotation.Log;
import com.bootdo.clouddocommon.context.FilterContextHandler;
import com.bootdo.clouddocommon.dto.LogDO;
import com.bootdo.clouddocommon.service.LogRpcService;
import com.bootdo.clouddocommon.utils.HttpContextUtils;
import com.bootdo.clouddocommon.utils.IPUtils;
import com.bootdo.clouddocommon.utils.JSONUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.BeforeAdvice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
@Aspect
@Component
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Autowired
LogRpcService logService;
@Pointcut("@annotation(com.bootdo.clouddocommon.annotation.Log)")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
// 执行方法
Object result = point.proceed();
// 执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//异步保存日志
saveLog(point, time);
return result;
}
void saveLog(ProceedingJoinPoint joinPoint, long time) throws InterruptedException {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LogDO sysLog = new LogDO();
Log syslog = method.getAnnotation(Log.class);
if (syslog != null) {
// 注解上的描述
sysLog.setOperation(syslog.value());
}
// 请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
// 请求的参数
Object[] args = joinPoint.getArgs();
try {
String params = JSONUtils.beanToJson(args[0]).substring(0, 4999);
sysLog.setParams(params);
} catch (Exception e) {
}
// 获取request
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
// 设置IP地址
sysLog.setIp(IPUtils.getIpAddr(request));
// 用户名
sysLog.setUserId(Long.parseLong(FilterContextHandler.getUserID() == null ? "000000" : FilterContextHandler.getUserID()));
sysLog.setUsername(FilterContextHandler.getUsername() == null ? "" : FilterContextHandler.getUsername());
sysLog.setTime((int) time);
// 系统当前时间
Date date = new Date();
sysLog.setGmtCreate(date);
// 保存系统日志
logService.save(sysLog);//这里使用的是远程调用,这里不解释
}
}
@Log("获取当前用户的菜单")
@GetMapping("currentUserMenus")
R currentUserMenus() {
return R.ok().put("currentUserMenus",menuService.RouterDTOsByUserId(SecuityUtils.getCurrentUser().getId()));
}
上面三步即可完成
首先解释上篇文章的target注解
java.lang.annotation.Target 用于设定注解使用范围
java.lang.annotation.ElementType Target通过ElementType来指定注解可使用范围的枚举集合
具体见下表
取值 | 注解使用范围 |
---|---|
METHOD | 可用于方法上 |
TYPE | 可用于类或者接口上 |
ANNOTATION_TYPE | 可用于注解类型上(被@interface修饰的类型) |
CONSTRUCTOR | 可用于构造方法上 |
FIELD | 可用于域上 |
LOCAL_VARIABLE | 可用于局部变量上 |
PACKAGE | 用于记录java文件的package信息 |
PARAMETER | 可用于参数上 |
这个简单,表示这个注解的声明周期,周期过了就不起作用了。
类别 | 声明周期 |
---|---|
RetentionPolicy.SOURCE | 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃; |
RetentionPolicy.CLASS | 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期 |
RetentionPolicy.RUNTIME | 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在; |
@Pointcut("@annotation(com.bootdo.clouddocommon.annotation.Log)")
public void logPointCut() {
}
有些人可能不解这里有什么用、
主要是用于下面的方法
@Around("logPointCut()") 用在这里
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
// 执行方法
Object result = point.proceed();
// 执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//异步保存日志
saveLog(point, time);
return result;
}
当然这里可以不要 logPointCut()这个方法,直接这样写
@Around("@annotation(com.bootdo.clouddocommon.annotation.Log)")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
// 执行方法
Object result = point.proceed();
// 执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//异步保存日志
saveLog(point, time);
return result;
}
上面的代码中计算了每个接口执行所消耗的时间,那么程序是如何判断执行完毕的呢,这就是环绕通知的强大之处了。因为这里必须加上Object result = point.proceed();表示执行currentUserMenus这个方法,这样就可在方法执行完以后计算消耗时间
环绕通知:可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法
方法 | 解释 |
---|---|
java.lang.Object proceed() throws java.lang.Throwable | 通过反射执行目标对象的连接点处的方法 |
java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable | 通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参 |
@Around是在方法执行前后执行增强,他最强大的地方在于可以替换原来的入参,改变方法的执行结果
此文章是基于demo写的,感兴趣的可以下载运行看看。
下载地址:https://download.csdn.net/download/qq_28483283/11233595
前端是vue写的,后端是springboot+springcloud
ps:本来不想要设置下载积分的,可是csdn强制要5积分,我也很无奈.