前言

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 项目配置如下

1
2
3
4
5
6
7
8
9
10
11
12
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 字符串后能够格式化输出

 

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

实现 Web Aop 功能。

@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.isInfoEnabled()) {
log.info(
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.info("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()

ps:没有打印出来的看看自己设置的日志级别

SpringBoot中使用AOP打印接口日志的方法(转载)的更多相关文章

  1. MVC 中使用log4net 打印重复日志解决方法

    最近在项目中引用log4net 来打印日志,会发现在同一时间点 打印重复记录: 详见图

  2. SpringBoot应用中使用AOP记录接口访问日志

    SpringBoot应用中使用AOP记录接口访问日志 本文主要讲述AOP在mall项目中的应用,通过在controller层建一个切面来实现接口访问的统一日志记录. AOP AOP为Aspect Or ...

  3. Springboot中使用AOP统一处理Web请求日志

    title: Springboot中使用AOP统一处理Web请求日志 date: 2017-04-26 16:30:48 tags: ['Spring Boot','AOP'] categories: ...

  4. 在SpringBoot中配置aop

    前言 aop作为spring的一个强大的功能经常被使用,aop的应用场景有很多,但是实际的应用还是需要根据实际的业务来进行实现.这里就以打印日志作为例子,在SpringBoot中配置aop 已经加入我 ...

  5. Springboot学习06-Spring AOP封装接口自定义校验

    Springboot学习06-Spring AOP封装接口自定义校验 关键字 BindingResult.Spring AOP.自定义注解.自定义异常处理.ConstraintValidator 前言 ...

  6. SpringBoot图文教程5—SpringBoot 中使用Aop

    有天上飞的概念,就要有落地的实现 概念+代码实现是本文的特点,教程将涵盖完整的图文教程,代码案例 文章结尾配套自测面试题,学完技术自我测试更扎实 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例 ...

  7. 编写SpringBoot 中的AOP

    编写SpringBoot 中的AOP 在程序开发的过程中会使用到AOP的思想,面向切面进行开发,比如登录的验证,记录日志等等-频繁需要操作的步骤,在遇到这种情况时就要使用Spring 的AOP了 Sp ...

  8. Springboot中mybatis控制台打印sql语句

    Springboot中mybatis控制台打印sql语句 https://www.jianshu.com/p/3cfe5f6e9174 https://www.jianshu.com/go-wild? ...

  9. Spring Boot 2.x(十一):AOP实战--打印接口日志

    接口日志有啥用 在我们日常的开发过程中,我们可以通过接口日志去查看这个接口的一些详细信息.比如客户端的IP,客户端的类型,响应的时间,请求的类型,请求的接口方法等等,我们可以对这些数据进行统计分析,提 ...

随机推荐

  1. 萌新计划 PartⅡ

    Part Ⅱ web 9-15 这一部分的题,主要是绕过过滤条件,进行命令执行 0x01 web 9 过滤条件: if(preg_match("/system|exec|highlight/ ...

  2. 数据可视化之powerBI基础(十六)PowerQuery的这个小功能,让你轻松发现数据质量问题

    https://zhuanlan.zhihu.com/p/64418072 源数据常常包含各种差错值,为了进行下一步的分析,我们必须先找出并更正这些差错,做这些工作几乎不会有什么快乐感可言,但却往往需 ...

  3. SQLAlchemy04 /SQLAlchemy查询高级

    SQLAlchemy04 /SQLAlchemy查询高级 目录 SQLAlchemy04 /SQLAlchemy查询高级 1.排序 2.limit.offset和切片操作 3.懒加载 4.group_ ...

  4. Django之Model、Form、ModelForm区别

    本节内容: 1:Model               https://www.cnblogs.com/shuai1991/p/10844662.html 2:Form                 ...

  5. C与lua交互--lua调用栈分析(2)

    0 预备知识: 至少对Lua手册C API有简单的了解.lua5.3手册中文 1  Lua虚拟机的栈,如图: 假设虚拟机的栈有n个数据: 解释: 两种解释方式: A,栈顶开始, -1 ...-n B, ...

  6. oracle添加配置多个端口监听

    原来配置:listener.ora文件如下: LISTENER = (DESCRIPTION_LIST = (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOS ...

  7. Ethical Hacking - GAINING ACCESS(17)

    CLIENT SIDE ATTACKS - Backdooring exe' s Download an executable file first. VEIL - FRAMEWORK A backd ...

  8. Ethical Hacking - GAINING ACCESS(16)

    CLIENT SIDE ATTACKS - Social Engineering Social Engineering Information gathering Tool: Maltego Gath ...

  9. 如何将 Bitbucket 的 pull request 签出到本地 review

    将 pull request 签出到本地进行 review, 最大的好处是可以通过 IDE 来查找各种变量和方法的上下文引用,以便充分发挥我们杠精的本领,将 pull request 中的各种合理和不 ...

  10. 题解 洛谷 P3340 【[ZJOI2014]星系调查】

    根据题意,发现题目中的图,其实就是一颗树或者是一颗基环树,每个节点上有一个点对\((x,y)\),每次询问为给定端点,找一条直线到端点间的所有点的距离之和最小. 设这条直线为\(y=kx+b\),根据 ...