05 Spring AOP

AOP 是一种与语言无关的程序思想、编程范式。项目业务逻辑中,将通用的模块以水平切割的方式进行分离统一处理,常用于日志、权限控制、异常处理等业务中。

AOP 注解

  • @Aspect:切面,这个注解标注在类上表示为一个切面

  • @Joinpoint:连接点,被 AOP 拦截的类或者方法

  • @Pointcut:切入点,从哪里开始切入

  • Advice:通知的几种类型

    • @Before:前置通知,在目标方法调用前调用通知功能;

    • @After:后置通知,在目标方法调用之后调用通知功能,不关心方法的返回结果;

    • @AfterReturning:返回通知,在目标方法成功执行之后调用通知功能;

    • @AfterThrowing:异常通知,在目标方法抛出异常后调用通知功能;

    • @Around:环绕通知,通知包裹了目标方法,在目标方法调用之前和之后执行自定义的行为

示例

伪代码表示通知顺序:

try {
    // @Before 执行前通知

    // @Around 执行环绕通知 成功走finall,失败走catch
} finally {
    // @After 执行后置通知

    // @AfterReturning 执行返回后通知
} catch(e) {
    // @AfterThrowing 抛出异常通知
}

实际代码例子,统一日志处理切面 WebLogAspect.java

@Slf4j
@Aspect
@Component
@Order(1)
public class WebLogAspect {

    // *.*(..) 表示任何类的任何方法的任何参数
    @Pointcut("execution(public * com.chanshiyu.moemall.admin.controller.*.*(..))")
    public void webLog() {}

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {}

    @AfterReturning(value = "webLog()", returning = "ret")
    public void doAfterReturning(Object ret) throws Throwable {}

    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        // 获取当前请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        // 记录请求信息
        WebLog webLog = new WebLog();
        Object result = joinPoint.proceed();
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method.isAnnotationPresent(ApiOperation.class)) {
            ApiOperation log = method.getAnnotation(ApiOperation.class);
            webLog.setDescription(log.value());
        }
        long endTime = System.currentTimeMillis();
        String urlStr = request.getRequestURL().toString();
        webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
        webLog.setIp(request.getRemoteAddr());
        webLog.setMethod(request.getMethod());
        webLog.setParameter(getParameter(method, joinPoint.getArgs()));
        webLog.setResult(result);
        webLog.setStartTime(DateUtil.date(startTime).toString());
        webLog.setSpendTime((int) (endTime - startTime));
        webLog.setUri(request.getRequestURI());
        webLog.setUrl(request.getRequestURL().toString());
        log.info("请求日志: {}", JSONUtil.parse(webLog).toString());
        return result;
    }

    /**
     * 根据方法和传入的参数获取请求参数
     */
    private Object getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            // 将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }
            // 将RequestParam注解修饰的参数作为请求参数
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            if (requestParam != null) {
                Map<String, Object> map = new HashMap<>();
                String key = parameters[i].getName();
                if (!StringUtils.isEmpty(requestParam.value())) {
                    key = requestParam.value();
                }
                map.put(key, args[i]);
                argList.add(map);
            }
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }

}

参考文章: SpringBoot 应用中使用 AOP 记录接口访问日志 Spring Boot 实战系列 AOP 面向切面编程

最后更新于