AOP把软件的功能模块分为两个部分:核心关注点和横切关注点。业务处理的主要功能为核心关注点,而非核心、需要拓展的功能为横切关注点。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点进行分离,使用切面有以下好处:

  • 集中处理某一关注点/横切逻辑

  • 可以很方便的添加/删除关注点

  • 侵入性少,增强代码可读性及可维护性 因此当想打印请求日志时很容易想到切面,对控制层代码0侵入

@Aspect => 声明该类为一个注解类

@Pointcut => 定义一个切点,可以简化代码

  • @Before => 在切点之前执行代码

  • @After => 在切点之后执行代码

  • @AfterReturning => 切点返回内容后执行代码,可以对切点的返回值进行封装

  • @AfterThrowing => 切点抛出异常后执行

  • @Around => 环绕,在切点前后执行代码

使用@Pointcut定义切点

@Pointcut("execution(* your_package.controller..*(..))")

=====>@Pointcut定义了一个切点,因为是请求日志切边,因此切点定义的是Controller包下的所有类下的方法。定义切点以后在通知注解中直接使用requestServer方法名就可以了

使用@Before再切点前执行

======>@Before("requestServer()")
public void doBefore(JoinPoint joinPoint) {
    ServletRequestAttributes attributes = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();

    LOGGER.info("===============================Start========================");
    LOGGER.info("IP                 : {}", request.getRemoteAddr());
    LOGGER.info("URL                : {}", request.getRequestURL().toString());
    LOGGER.info("HTTP Method        : {}", request.getMethod());
    LOGGER.info("Class Method       : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
}

使用@Around打印进入控制层的入参

=====>@Around("requestServer()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    Object result = proceedingJoinPoint.proceed();
    LOGGER.info("Request Params       : {}", getRequestParams(proceedingJoinPoint));
    LOGGER.info("Result               : {}", result);
    LOGGER.info("Time Cost            : {} ms", System.currentTimeMillis() - start);

    return result;
}

========>getRquestParams方法

private Map<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {
     Map<String, Object> requestParams = new HashMap<>();

      //参数名
     String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();
     //参数值
     Object[] paramValues = proceedingJoinPoint.getArgs();

     for (int i = 0; i < paramNames.length; i++) {
         Object value = paramValues[i];

         //如果是文件对象
         if (value instanceof MultipartFile) {
             MultipartFile file = (MultipartFile) value;
             value = file.getOriginalFilename();  //获取文件名
         }

         requestParams.put(paramNames[i], value);
     }

     return requestParams;
 }

@After方法调用后执行

======>@After("requestServer()")
public void doAfter(JoinPoint joinPoint) {
    LOGGER.info("===============================End========================");
}

<==================>

最后放一下完整日志请求切面代码:

@Component
@Aspect
public class RequestLogAspect {
    private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class);

    @Pointcut("execution(* your_package.controller..*(..))")
    public void requestServer() {
    }

    @Around("requestServer()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Object result = proceedingJoinPoint.proceed();
        RequestInfo requestInfo = new RequestInfo();
                requestInfo.setIp(request.getRemoteAddr());
        requestInfo.setUrl(request.getRequestURL().toString());
        requestInfo.setHttpMethod(request.getMethod());
        requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(),
                proceedingJoinPoint.getSignature().getName()));
        requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));
        requestInfo.setResult(result);
        requestInfo.setTimeCost(System.currentTimeMillis() - start);
        LOGGER.info("Request Info      : {}", JSON.toJSONString(requestInfo));

        return result;
    }

    @AfterThrowing(pointcut = "requestServer()", throwing = "e")
    public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        RequestErrorInfo requestErrorInfo = new RequestErrorInfo();
        requestErrorInfo.setIp(request.getRemoteAddr());
        requestErrorInfo.setUrl(request.getRequestURL().toString());
        requestErrorInfo.setHttpMethod(request.getMethod());
        requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName()));
        requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint));
        requestErrorInfo.setException(e);
        LOGGER.info("Error Request Info      : {}", JSON.toJSONString(requestErrorInfo));
    }

    /**
     * 获取入参
     * @param proceedingJoinPoint
     *
     * @return
     * */
    private Map<String, Object> getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {
        //参数名
        String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = proceedingJoinPoint.getArgs();

        return buildRequestParam(paramNames, paramValues);
    }

    private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) {
        //参数名
        String[] paramNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = joinPoint.getArgs();

        return buildRequestParam(paramNames, paramValues);
    }

    private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
        Map<String, Object> requestParams = new HashMap<>();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];

            //如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                value = file.getOriginalFilename();  //获取文件名
            }

            requestParams.put(paramNames[i], value);
        }

        return requestParams;
    }

    @Data
    public class RequestInfo {
        private String ip;
        private String url;
        private String httpMethod;
        private String classMethod;
        private Object requestParams;
        private Object result;
        private Long timeCost;
    }

    @Data
    public class RequestErrorInfo {
        private String ip;
        private String url;
        private String httpMethod;
        private String classMethod;
        private Object requestParams;
        private RuntimeException exception;
    }
}

AOP 日志切面的更多相关文章

  1. 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十 || AOP面向切面编程浅解析:简单日志记录 + 服务切面缓存

    代码已上传Github+Gitee,文末有地址 上回<从壹开始前后端分离[ .NET Core2.0 Api + Vue 2.0 + AOP + 分布式]框架之九 || 依赖注入IoC学习 + ...

  2. Spring Boot 2.X(八):Spring AOP 实现简单的日志切面

    AOP 1.什么是 AOP ? AOP 的全称为 Aspect Oriented Programming,译为面向切面编程,是通过预编译方式和运行期动态代理实现核心业务逻辑之外的横切行为的统一维护的一 ...

  3. Z从壹开始前后端分离【 .NET Core2.0/3.0 +Vue2.0 】框架之十 || AOP面向切面编程浅解析:简单日志记录 + 服务切面缓存

    本文梯子 本文3.0版本文章 代码已上传Github+Gitee,文末有地址 大神反馈: 零.今天完成的深红色部分 一.AOP 之 实现日志记录(服务层) 1.定义服务接口与实现类 2.在API层中添 ...

  4. [转] AOP面向切面编程

    AOP面向切面编程 AOP(Aspect-Oriented Programming,面向切面的编程),它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. ...

  5. 【原创】Android AOP面向切面编程AspectJ

    一.背景: 在项目开发中,对 App 客户端重构后,发现用于统计用户行为的友盟统计代码和用户行为日志记录代码分散在各业务模块中,比如在视频模块,要想实现对用户对监控点的实时预览和远程回放行为进行统计, ...

  6. 论AOP面向切面编程思想

    原创: eleven 原文:https://mp.weixin.qq.com/s/8klfhCkagOxlF1R0qfZsgg [前言] AOP(Aspect-Oriented Programming ...

  7. 学习笔记: AOP面向切面编程和C#多种实现

    AOP:面向切面编程   编程思想 OOP:一切皆对象,对象交互组成功能,功能叠加组成模块,模块叠加组成系统      类--砖头     系统--房子      类--细胞     系统--人    ...

  8. SpringBoot系列——aop 面向切面

    前言 项目中我们经常会用到aop切面,比如日志记录:这里简单记录一下springboot是如何使用aop spring对aop的配置,来自springboot参考手册,Common applicati ...

  9. javascript AOP(面向切面编程)

    var func = function () { console.log("2") } Function.prototype.before = function (beforefn ...

随机推荐

  1. 容器之分类与各种测试(四)——unordered_set和unordered_map

    关于set和map的区别前面已经说过,这里仅是用hashtable将其实现,所以不做过多说明,直接看程序 unordered_set #include<stdexcept> #includ ...

  2. Linux基础命令---mailq显示邮件队列

    mailq mailq指令可以显示出待发送的邮件队列. 此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS.Fedora.   1.语法       mailq   2.选项参数列表 ...

  3. ReactiveCocoa操作方法-秩序

    doNext:      执行Next之前,会先执行这个Block doCompleted:      执行sendCompleted之前,会先执行这个Block - (void)doNext { [ ...

  4. dbeaver可视化工具-连接clickhouse

    下载地址 https://dbeaver.io/download/ 本次下载的是ZIP包,这种方式方便移动/备份软件 此软件可连接多种数据库,这里是连接clickhouse 新建连接 点击 上面的倒三 ...

  5. EM配置问题

    配置EM,首先要保证dbconsole在运行. C:\Users\dingqi>emctl start dbconsoleEnvironment variable ORACLE_UNQNAME ...

  6. Element-ui 中对表单进行验证

    Element-ui 中对表单(Form)绑定的对象中的对象属性进行校验 如果是直接绑定属性,是可以的,但是绑定对象中的属性就需要特别处理,需要在rules中添加双引号 " "或者 ...

  7. 【编程思想】【设计模式】【行为模式Behavioral】Publish_Subscribe

    Python版 https://github.com/faif/python-patterns/blob/master/behavioral/publish_subscribe.py #!/usr/b ...

  8. ES6——>let,箭头函数,this指向小记

    let let允许你声明一个作用域被限制在块级中的变量.语句或者表达式. 还是那个经典的问题:创建5个li,点击不同的li能够打印出当前li的序号. 如果在for循环中使用**var**来声明变量i的 ...

  9. spring mvc访问html页面404报错解决

    <servlet> <servlet-name>context</servlet-name> <servlet-class>org.springfram ...

  10. Mysql原有环境部署多个版本

    目录 一.环境准备 二.下载安装包 三.Mysql-5.7单独部署 四.启动Mysql-5.7 五.muliti使用 一.环境准备 原先已经有一个5.6版本的数据库在运行了,当前操作是完全不影响原数据 ...