SpringBoot中使用AOP打印接口日志的方法

 更新时间:2018年05月28日 10:25:23   作者:高超杨  
本篇文章主要介绍了SpringBoot中使用AOP打印接口日志的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

前言

AOP 是 Aspect Oriented Program (面向切面)的编程的缩写。他是和面向对象编程相对的一个概念。在面向对象的编程中,我们倾向于采用封装、继承、多态等概念,将一个个的功能在对象中来实现。但是,我们在实际情况中也发现,会有另外一种需求就是一类功能在很多对象的很多方法中都有需要。例如有一些对数据库访问的方法有事务管理的需求,有很多方法中要求打印日志。按照面向对象的方式,那么这些相同的功能要在很多地方来实现或者在很多地方来调用。这就非常繁琐并且和这些和业务不相关的需求耦合太紧密了。所以后来就出现了面向切面的编程来解决这一类问题,并对面向对象的编程做了很好的补充

概念

要很好的理解面向切面的编程,先要理解 AOP 的一些概念。在 Java 中 AspectJ 比较完整的实现了 AOP 的功能,但是使用起来也比较复,所以这里主要是讨论 Spring 的 AOP 。Spring AOP 采用简单够用的原则,实现了 AOP 的核心功能。下面先说说 AOP 中的具体概念

  1. Aspect:方面。一个可以切入多个类的关注点。这个关注点实现了我们前面说的具体的业务功能。例如打印日志,进行数据库的事务管理等。
  2. Joint point:被切入点。是指具体要实现前面所说的例如打印日志,数据库事务管理的被切入的点。也就是通过 AOP 将切面功能动态加入进去的程序位置。在 Spring AOP 里面这个指的都是某个方法
  3. Pointcut:切点。用来指明如何通过规则匹配 Joint point。这个规则是一个表达式。在 Spring 中,默认使用的是 AspectJ 的 pointcut 表达式语言
  4. Advice:指明在一个切入点的不同位置上采取的动作。例如对于一个数据库访问事务管理来说,在进入方法后要开启事务,在方法结束前要提交事务,在发生错误的时候要回滚事务。这属于三个不同的 Advice,要分别进行实现。Advice 通常和具体的 Pointcut 关联在一起。
  5. AOP proxy:AOP 代理。用来实现将 Advice 功能动态加入到 Pointcut 的方法。在 Spring 的 AOP 中采用动态代理和 CGLIB 代理的方式来实现。而 AspectJ 则采用了特定编译器侵入字节码的方式来实现。

SprinBoot AOP 实现

前面我们已经用好几章讲述了 SpringBoot 的基本使用。那么这里我们就用 SpringBoot 和 AOP 结合来实现一个输出所有 Rest 接口输入参数和返回参数的日志的功能。

实现 rest 服务功能。

根据前面的文章,我们先建立一个 SpingBoot 的工程如下图所示

demo 工程

SpringBoot 项目配置

我们对 SpringBoot 项目配置如下

server:
 port: 3030
 servlet:
  context-path: /aop-demo
spring:
 jackson:
  date-format: yyyy-MM-dd HH:mm:ss
  serialization:
   indent-output: true
logging:
 level:
  com.yanggch: debug

其中 jackson 相关配置是为了将对象输出成 json 字符串后能够格式化输出

实现一个 rest 接口的 Controller 类

在这里,我们实现两个 rest 接口。一个是返回 hello 信息。一个是根据输入返回登录信息。

package com.yanggch.demo.aop.web;

import com.yanggch.demo.aop.domain.LoginEntity;
import com.yanggch.demo.aop.domain.SecurityEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

/**
 * 安全相关 rest 服务
 *
 * @author : 杨高超
 * @since : 2018-05-27
 */
@RestController
@RequestMapping("/api/v1/security")
public class SecurityApi {
  @RequestMapping(value = "/login/{shopId}", method = RequestMethod.POST)
  public SecurityEntity login(@RequestBody LoginEntity loginEntity, @PathVariable Long shopId) {
    SecurityEntity securityEntity = new SecurityEntity();
    securityEntity.setShopId(shopId);
    securityEntity.setAccount(loginEntity.getAccount());
    securityEntity.setPwd(loginEntity.getPwd());
    securityEntity.setLoginTime(new Date());
    return securityEntity;
  }

  @RequestMapping(value = "/echo/{name}", method = RequestMethod.GET)
  public String login(@PathVariable String name) {
    return "hello," + name;
  }
}

先在我们要通过 AOP 功能将所有 Rest 接口的输入参数和返回结果输出到日志中。

实现 Web Aop 功能。

package com.yanggch.demo.aop.comment;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * web 接口日志
 *
 * @author : 杨高超
 * @since : 2018-05-27
 */
@Aspect
@Component
public class WebLogAspect {
  private static Logger log = LoggerFactory.getLogger(WebLogAspect.class);

  private final ObjectMapper mapper;

  @Autowired
  public WebLogAspect(ObjectMapper mapper) {
    this.mapper = mapper;
  }

  @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
  public void webLog() {
  }

  @Before("webLog()")
  public void doBefore(JoinPoint joinPoint) {
    for (Object object : joinPoint.getArgs()) {
      if (
        object instanceof MultipartFile
          || object instanceof HttpServletRequest
          || object instanceof HttpServletResponse
        ) {
        continue;
      }
      try {
        if (log.isDebugEnabled()) {
          log.debug(
            joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName()
              + " : request parameter : " + mapper.writeValueAsString(object)
          );
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  @AfterReturning(returning = "response", pointcut = "webLog()")
  public void doAfterReturning(Object response) throws Throwable {
    if (response != null) {
      log.debug("response parameter : " + mapper.writeValueAsString(response));
    }
  }
}

这里有几个需要注意的地方,

  1. 需要在类上声明 org.aspectj.lang.annotation.Aspect 注解。
  2. 需要通过方法上的 org.aspectj.lang.annotation.Pointcut 注解声明一个 Pointcut ,用来指明要在哪些方法切入。我们的 rest 接口都有 org.springframework.web.bind.annotation.RequestMapping 注解,所以我们这里就用了 "@annotation(org.springframework.web.bind.annotation.RequestMapping)" 表达式来指明。
  3. 通过 Advice 相关注解来说明在切入方法的什么位置做什么事。这里用 org.aspectj.lang.annotation.Before
  4. 这个实现是指明在所有具备 org.springframework.web.bind.annotation.RequestMapping 注解的方法上,方法进入后打印入口参数。方法返回后,打印返回参数。

测试

在前台通过 postman 发起请求,后台日志输入结果如下

2018-05-27 19:58:42.941 DEBUG 86072 --- [nio-3030-exec-4] c.yanggch.demo.aop.comment.WebLogAspect  : com.yanggch.demo.aop.web.SecurityApi.login : request parameter : {
  "account" : "yanggch",
  "pwd" : "123456"
}
2018-05-27 19:58:42.941 DEBUG 86072 --- [nio-3030-exec-4] c.yanggch.demo.aop.comment.WebLogAspect  : com.yanggch.demo.aop.web.SecurityApi.login : request parameter : 2001
2018-05-27 19:58:42.942 DEBUG 86072 --- [nio-3030-exec-4] c.yanggch.demo.aop.comment.WebLogAspect  : response parameter : {
  "shopId" : 2001,
  "account" : "yanggch",
  "pwd" : "123456",
  "loginTime" : "2018-05-27 11:58:42"
}
2018-05-27 19:58:45.796 DEBUG 86072 --- [nio-3030-exec-5] c.yanggch.demo.aop.comment.WebLogAspect  : com.yanggch.demo.aop.web.SecurityApi.echo : request parameter : "yanggch"
2018-05-27 19:58:45.796 DEBUG 86072 --- [nio-3030-exec-5] c.yanggch.demo.aop.comment.WebLogAspect  : response parameter : "hello,yanggch"

由此可见,我们虽然没有在 rest 接口方法中写输出日志的代码,但是通过 AOP 的方式可以自动的给各个 rest 入口方法中添加上输出入口参数和返回参数的代码并正确执行。

其他说明

前面提到了 Advice 的类型和 Pointcut 的 AOP 表达式语言。具体参考如下。

Advice 类型

  1. before advice 在方法执行前执行。
  2. after returning advice 在方法执行后返回一个结果后执行。
  3. after throwing advice 在方法执行过程中抛出异常的时候执行。
  4. Around advice 在方法执行前后和抛出异常时执行,相当于综合了以上三种通知。

AOP 表达式语言

1、方法参数匹配
@args()

2、方法描述匹配
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
其中 returning type pattern,name pattern, and parameters pattern是必须的.
. ret-type-pattern:可以为表示任何返回值,全路径的类名等.
*. name-pattern:指定方法名, *代表所有
.set代表以set开头的所有方法.
. parameters pattern:指定方法参数(声明的类型),(..)代表所有参数,()代表一个参数
. (,String)代表第一个参数为任何值,第二个为String类型.

3、当前AOP代理对象类型匹配

4、目标类匹配
@target()
@within()

5、标有此注解的方法匹配
@annotation()

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • springboot配置Jackson返回统一默认值的实现示例

    springboot配置Jackson返回统一默认值的实现示例

    在项目开发中,我们返回的数据或者对象没有的时候一般直接返回的null,那么如何返回统一默认值,感兴趣的可以了解一下
    2021-07-07
  • SpringBoot图文并茂带你掌握devtools热启动

    SpringBoot图文并茂带你掌握devtools热启动

    这篇文章主要介绍springBoot插件工具热部署Devtools,本文分步骤给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06
  • kafka运维consumer-groups.sh消费者组管理

    kafka运维consumer-groups.sh消费者组管理

    这篇文章主要为大家介绍了kafka运维consumer-groups.sh消费者组管理,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • Jax-rs规范REST接口文件上传代码实例

    Jax-rs规范REST接口文件上传代码实例

    这篇文章主要介绍了Jax-rs规范REST接口文件上传代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • 什么是Spring Boot

    什么是Spring Boot

    Spring是一个非常受欢迎的Java框架,它用于构建web和企业应用。本文介绍将各种Spring的配置方式,帮助您了解配置Spring应用的复杂性
    2017-08-08
  • Java必须学会的类的继承与多态

    Java必须学会的类的继承与多态

    这篇文章主要介绍了Java类的继承与多态的相关资料,帮助大家更好的理解和学习Java,感兴趣的朋友可以了解下
    2020-08-08
  • 详解Mybatis中javaType和ofType的区别

    详解Mybatis中javaType和ofType的区别

    本文主要介绍了详解Mybatis中javaType和ofType的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • JPA多条件复杂SQL动态分页查询功能

    JPA多条件复杂SQL动态分页查询功能

    这篇文章主要介绍了JPA多条件复杂SQL动态分页查询功能,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-09-09
  • springboot与redis的简单整合实例

    springboot与redis的简单整合实例

    Redis是一个缓存、消息代理和功能丰富的键值存储。StringBoot提供了基本的自动配置。这篇文章主要介绍了springboot与redis的简单整合实例,非常具有实用价值,需要的朋友可以参考下
    2019-01-01
  • Java设计模式之单一职责原则精解

    Java设计模式之单一职责原则精解

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。本篇介绍设计模式七大原则之一的单一职责原则
    2022-02-02

最新评论