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()

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

(0)

相关推荐

  • SpringBoot项目中使用AOP的方法

    本文介绍了SpringBoot项目中使用AOP的方法,分享给大家,具体如下: 1.概述 将通用的逻辑用AOP技术实现可以极大的简化程序的编写,例如验签.鉴权等.Spring的声明式事务也是通过AOP技术实现的. 具体的代码参照 示例项目 https://github.com/qihaiyan/springcamp/tree/master/spring-aop Spring的AOP技术主要有4个核心概念: Pointcut: 切点,用于定义哪个方法会被拦截,例如 execution(* cn.sp

  • 在SpringBoot项目中利用maven的generate插件

    使用maven 插件 generate生成MyBatis相关文件 在项目中增加 maven 依赖 - mybatis-spring-boot-starter - mysql-connector-java - mybatis-generator-maven-plugin 插件 自动读取 resources 下的generatorConfig.xml 文件 <?xml version="1.0" encoding="UTF-8"?> <project

  • SpringBoot AOP控制Redis自动缓存和更新的示例

    导入redis的jar包 <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.0.4.RELEASE</version> </dependency> 编写自定义缓存注解 /**

  • 在SpringBoot中通过jasypt进行加密解密的方法

    1.用途 在SpringBoot中,通过jasypt可以进行加密解密. 这个是双向的, 且可以配置密钥. 2.使用: 2.1通过UT创建工具类,并认识jasypt import org.jasypt.util.text.BasicTextEncryptor; import org.junit.Test; public class UtilTests { @Test public void jasyptTest() { BasicTextEncryptor encryptor = new Basi

  • SpringBoot AOP方式实现多数据源切换的方法

    最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据库的性能,考虑把这个查询走从库.所以涉及到需要在一个项目中配置多数据源,并且能够动态切换.经过一番摸索,完美实现动态切换,记录一下配置方法供大家参考. 设计总体思路 Spring-Boot+AOP方式实现多数据源切换,继承AbstractRoutingDataSource实现数据源动态的获取,在service层使用注解指定数据源. 步骤 一.多数据源配置 在applica

  • Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验 使用消息资源文件对消息国际化

    导包和配置 导入 JSR 303 的包.hibernate valid 的包 <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.5.Final</version> </dependency> <dependency> <

  • SpringBoot AOP使用笔记

    1. 启用AOP a. 在类上添加@Aspect注解 b. 注入该类, 可以使用@Component进行注入到Spring容器中 2. 通过PointCut对象创建切入点 a. 在某个方法使用类似下面的方法进行注入 @Pointcut("execution(* com.sguess.service.IAOPService.*(..))") private void pointcut() { } i. 其中,execution表达式为 execution(modifiers-patter

  • 配置SpringBoot方便的切换jar和war的方法示例

    网上关于如何切换,其实说的很明确,本文主要通过profile进行快速切换已实现在不同场合下,用不同的打包方式. jar到war修改步骤 pom文件修改 packaging配置由jar改为war 排除tomcat等容器的依赖 配置web.xml或者无web.xml打包处理 入口类修改 添加ServletInitializer 特别注意:当改成war包的时候,application.properties配置的server.port和server.servlet.context-path就无效了,遵从

  • SpringBoot项目访问任意接口出现401错误的解决方案

    之前搭建了一个SpringBoot项目用于测试集成Redis和MyBatis以及Freemarker,搭建完成测通之后就没有再打开过.今天打开之后想要测试一个问题,发现在这个项目下无论请求哪个接口,浏览器都会跳转到一个登录页面,而且这个页面不是我写的,如下图: 地址栏里的login也是在我输入了自己的接口之后,自动跳转到了login 于是用Postman测试,得到401响应: 当时一脸蒙蔽,心想我代码里面没有写拦截器啊,而且拦截之后的页面也不是我写的.刚开始认为可能和端口有关,后来发现不是.于是

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

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

  • springboot+mybatis配置控制台打印sql日志的方法

    此处项目环境为简单的springboot+mybatis环境.可查看到上一篇文章搭建的简单springboot+mybatis的项目 想要控制台打印sql日志. 只需要在resources目录下添加logback.xml文件即可 logback内容如下.直接复制即可 <?xml version="1.0" encoding="UTF-8"?> <configuration debug="false"> <!--定义日

  • 在springboot中使用AOP进行全局日志记录

    目录 前言 1. spring AOP 是什么? 2.spring AOP 能做什么? 3.spring AOP 我能用 AOP 解决什么问题? 一.引入依赖,增加自定义注解 1.引入 maven 依赖 2.增加自定义注解 OperationLog 二.为自定义注解编写切面实现 三.使用自定义日志注解 前言 此前项目上需要对用户的操作进行日志记录,以便后续追踪问题,所以就学习了使用 spring AOP 来进行日志记录. 1. spring AOP 是什么? spring 的两大核心就是 IOC

  • SpringBoot中通过AOP整合日志文件的实现

    目录 1.导入相关的依赖 2.log4j2 日志文件 3.dao层的接口以及实现类 4.Service层业务实现类 5.Controller层接口控制类 6.编写业务类增强类,加入一个日志文件记录 7.运行测试,查看结果 1.导入相关的依赖 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter&

  • SpringBoot中利用AOP和拦截器实现自定义注解

    目录 前言 Spring实现自定义注解 1.引入相关依赖 2.相关类 Java实现自定义注解 通过Cglib实现 通过JDk动态代理实现 Cglib和JDK动态代理的区别 写在最后 前言 最近遇到了这样一个工作场景,需要写一批dubbo接口,再将dubbo接口注册到网关中,但是当dubbo接口异常的时候会给前端返回非常不友好的异常.所以就想要对异常进行统一捕获处理,但是对于这种service接口使用@ExceptionHandler注解进行异常捕获也是捕获不到的,应为他不是Controller的

  • 如何在SpringBoot中使用Spring-AOP实现接口鉴权

    目录 面向切面编程 AOP的底层原理实现 AOP的相关术语 相关注解以及切入点表达式 实现接口鉴权 1. 配置yml文件 2. 读取账密配置 3.编写接口鉴权方法 4. 编写AOP 5.编写接口测试 面向切面编程 面向切面编程,可以将与业务无关但是需要被各个业务模块共同调用的逻辑抽取出来,以切面的方式切入到代码中,从而降低系统中代码的耦合度,减少重复的代码. Spring AOP是通过预编译方式和运行期间动态代理实现程序面向切面编程 AOP的底层原理实现 AOP底层使用动态代理完成需求,为需要增

  • springboot下mybatis-plus如何打印sql日志和参数到日志文件

    最近在使用springboot过程中用到了mybatis-plus ,springboot版本是2.3.1.RELEASE,mybatis-plus 版本3.2.0. <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.2.0</version> </dep

  • springboot实现基于aop的切面日志

    本文实例为大家分享了springboot实现基于aop的切面日志的具体代码,供大家参考,具体内容如下 通过aop的切面方式实现日志 通切面拦截所有指定包下的所有方法 @Aspect @Component @EnableAspectJAutoProxy public class LogAspect1{     Logger logger = LoggerFactory.getLogger(LogAspect.class);      /**  * 拦截切点  */     @Pointcut("e

  • 详解SpringBoot中自定义和配置拦截器的方法

    目录 1.SpringBoot版本 2.什么是拦截器 3.工作原理 4.拦截器的工作流程 4.1正常流程 4.2中断流程 5.应用场景 6.如何自定义一个拦截器 7.如何使其在Spring Boot中生效 8.实际使用 8.1场景模拟 8.2思路 8.3实现过程 8.4效果体验 9.总结 1.SpringBoot版本 本文基于的Spring Boot的版本是2.6.7 . 2.什么是拦截器 Spring MVC中的拦截器(Interceptor)类似于ServLet中的过滤器(Filter),它

  • SpringBoot中的异常处理与参数校验的方法实现

    兄弟们好,这次来跟老铁交流两个问题,异常和参数校验,在说参数校验之前我们先来说异常处理吧,因为后面参数的校验会牵扯到异常处理这块的内容. 异常处理 说到异常处理,我不知道大家有没有写过或者遇到过如下的写法. public void saveUser() { try { // 所有的业务内容,目测几百行 }catch (Exception e) { e.printStackTrace(); } } 如果出现上述的代码,里面包含了大量的业务代码,如果是你写的,赶紧改掉,不是你写的找写的,吐槽赶紧改掉

随机推荐