本文主要讲述AOP在Springboot项目中如何使用AOP记录接口访问日志,具体的理论知识这边就不做过多的详解,具体的可以以后在另一篇文章做详细的介绍。
什么是AOP
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
再来回顾下AOP的一些相关术语
通知(Advice)
通知描述了切面要完成的工作以及何时执行。比如我们的日志切面需要记录每个接口调用时长,就需要在接口调用前后分别记录当前时间,再取差值。
- 前置通知(Before):在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常;
- 后置通知(After):在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容;
- 返回通知(AfterReturning):在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行;
- 异常通知(AfterThrowing):在连接点抛出异常后执行;
- 环绕通知(Around):环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。
切面(Aspect)
切面是通知和切点的结合,定义了何时、何地应用通知功能。
切点(Pointcut)
切点定义了通知功能被应用的范围。比如日志切面的应用范围就是所有接口,即所有controller层的接口方法。
切点表达式
指定了通知被应用的范围,这里只介绍下我们项目里用到的@annotation 此注解用于定位标注了某个注解的目标切点
@anntation(com.test.annotation.SysLog)
定位于controller层中任何添加@SysLog的方法,这可以方便地对控制层中某些方法被调用(如某人某时间登陆、进入后台管理界面)添加日志记录
具体实现
- 首先定义一个系统日志的注解
/**
* 系统日志注解
*
* @author admin
* @date 2020-12-11 10:19
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
String value() default "";
}
//@Target:注解的作用目标
//@Target(ElementType.TYPE)——接口、类、枚举、注解
//@Retention:注解的保留位置
//@RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。
//@Document:说明该注解将被包含在javadoc中
- 接着我们需要定义一个系统日志的切面处理类
/**
* 系统日志,切面处理类
*
* @author amin
* @date 2020-12-11 10:19
*/
@Aspect
@Component
public class SysLogAspect {
/**
* 日志
*/
private static Logger log = LogUtil.get();
@Reference(version = "1.0.0")
private SysLogService sysLogService;
@Pointcut("@annotation(com.ynt.ai.ivr.cms.annotation.SysLog)")
public void logPointCut() {
}
/**
* 环绕通知
*
* @param point
* @return
* @throws Throwable
*/
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
String methodName = signature.getName();
Object result;
if ("logout".equals(methodName)) {
//保存日志
saveSysLog(point);
//执行方法
result = point.proceed();
} else {
//执行方法
result = point.proceed();
//保存日志
saveSysLog(point);
}
return result;
}
/**
* 新增日志
*
* @param joinPoint
*/
private void saveSysLog(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysLogBean sysLog = new SysLogBean();
SysLog syslog = method.getAnnotation(SysLog.class);
if (syslog != null) {
//注解上的描述
sysLog.setOperationContent(syslog.value());
}
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
//请求的参数
Object[] args = joinPoint.getArgs();
try {
if (args != null && args.length > 0) {
String params = JacksonUtil.toJSon(args[0]);
sysLog.setParams(params);
} else {
sysLog.setParams("");
}
} catch (Exception e) {
sysLog.setParams("");
log.error("日志请求参数序列化有异常", args);
}
//获取request
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
//设置IP地址
sysLog.setIp(IpUtils.getIpAddr(request));
//用户名
UserBean userInfo = (UserBean) SecurityUtils.getSubject().getPrincipal();
if (userInfo != null) {
String username = userInfo.getUserName();
sysLog.setUserName(username);
//保存系统日志
sysLogService.addSysLog(sysLog);
}
}
}
- 最后我们只需在controller的方法上加上定义好的@SysLog注解就可以了
/**
* 登录Controller
*
* @author admin
* @date 2020-12-11 10:55
*/
@RestController
@RequestMapping(value = "/permission")
public class LoginController extends BaseController {
/**
* 日志
*/
private static Logger log = LogUtil.get();
@Reference(version = "1.0.0")
private UserService userService;
/**
* 获取用户权限id
*
* @return Result实体
*/
@SysLog("获取用户权限id")
@PostMapping("/getUserPermList")
@ApiOperation(value = "获取用户权限id", httpMethod = "POST")
public Result getUserPermList(@ApiParam(name = "id", value = "用户id", required = true) @RequestParam Long id) {
return userService.queryUserPermsId(id);
}
}
用起来是不是很简单 ,好了今天的分享就到这里,欢迎大家来指正