Spring-AOP应用

上一篇《Spring-AOP理解》讲到aop基于代理对原有方法进行增强,Spring很多注解的都是使用了AOP的动态代理去实现。

例如:

  1. 事务管理 (调用方法前开启事务, 调用方法后提交关闭事务 )
  2. 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
  3. 权限控制(security框架hasAuthority(),hasRole()等)

有时的业务场景需要我们自定义去实现一个切面进行处理。比如记录指定接口的请求响应数据,其实拦截器也可以做到,但是拦截器针对某个接口拦截使用配置相对较麻烦。下面将使用Spring的AOP进行日志记录,当要记录接口的时候添加在接口方法上即可,使用起来也比较简单。

一、定义一个自定义注解

import java.lang.annotation.*;


@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {


    String name();
}

二、 切面类进行拦截注解逻辑处理

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.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.Executor;


@Aspect
@Component
public class OperationLogAspect {

  

    @Pointcut(value = "@annotation(com.xy.annotation.OperationLog)")
    public void aspect() {

    }

    @Around("aspect()&&@annotation(operationLog)")
    public Object around(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        OperationLogSaveReq log = new OperationLogSaveReq();
        log.setName(operationLog.name());
        log.setIp(IpUtils.getLocalIp(request));
        log.setParameter(JSONObject.toJSONString(joinPoint.getArgs()));
        log.setUrl(String.valueOf(request.getRequestURL()));

        Long startTime = System.currentTimeMillis();
        Object obj=null;
        try {
             obj= joinPoint.proceed();
        }catch (Throwable throwable){
            Long endTime = System.currentTimeMillis();
            log.setSpendTime(endTime.intValue()-startTime.intValue());
            log.setResult(JSONObject.toJSONString(throwable.getMessage()));
//            log.setIsSuccess(false);
            saveOperationLog(log);
            throw throwable;
        }
        Long endTime = System.currentTimeMillis();
        log.setSpendTime(endTime.intValue()-startTime.intValue());
        log.setResult(JSONObject.toJSONString(obj));
        if ( obj instanceof ResponseResult){
            log.setIsSuccess(((ResponseResult<?>) obj).isSuccess());
        }else {
            log.setIsSuccess(true);
        }
        saveOperationLog(log);
        return obj;
    }

}

三、需要记录操作日志的地方 添加 @OperationLog 注解。

有必要给这个接口定义一个名称,便于排查日志检索。

    @PostMapping("delete")
    @OperationLog(name = "删除数据")
    public ResponseResult<Void> delete(@RequestBody @Validated BaseIdReq req){
        delete(req.getId());
        return ResponseResult.success();
    }