46. Spring Boot中使用AOP统一处理Web请求日志

时间:2022-09-10 11:42:04

在之前一系列的文章中都是提供了全部的代码,在之后的文章中就提供核心的代码进行讲解。有什么问题大家可以给我留言或者加我QQ,进行咨询。

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是Spring框架中的一个重要内容,它通过对既有程序定义一个切入点,然后在其前后切入不同的执行内容,比如常见的有:打开数据库连接/关闭数据库连接、打开事务/关闭事务、记录日志等。基于AOP不会破坏原来程序逻辑,因此它可以很好的对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

本文就是要通过AOP技术统一处理web请求的日志。

准备工作

因为需要对web请求做切面来记录日志,所以先引入web模块,并创建一个简单的hello请求的处理。

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

实现一个简单请求处理:

@RestController

publicclass HelloController {

@RequestMapping("/hello")

public String hello(String name,int state){

return"name "+name+"---"+state;

}

}

这时候我们编写一个启动类启动运行程序访问:

http://127.0.0.1:8080/hello?name=林峰&state=1  就能看到页面返回的信息了,但是现在还是不能进行拦截的。

引入AOP依赖

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-aop</artifactId>

</dependency>

在完成了引入AOP依赖包后,一般来说并不需要去做其他配置。也许在Spring中使用过注解配置方式的人会问是否需要在程序主类中增加@EnableAspectJAutoProxy来启用,实际并不需要。

可以看下面关于AOP的默认配置属性,其中spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy

# AOP spring.aop.auto=true # Add @EnableAspectJAutoProxy.

spring.aop.proxy-target-class=false # Whether subclass-based (CGLIB) proxies are to be created (true) as opposed to standard Java interface-based proxies (false).

我在做测试的时候,以上两个配置我都没有进行使用,请自行进行测试。

而当我们需要使用CGLIB来实现AOP的时候,需要配置spring.aop.proxy-target-class=true,不然默认使用的是标准Java的实现。

实现Web层的日志切面

实现AOP的切面主要有以下几个要素:

使用@Aspect注解将一个java类定义为切面类

使用@Pointcut定义一个切入点,可以是一个规则表达式,比如下例中某个package下的所有函数,也可以是一个注解等。

根据需要在切入点不同位置的切入内容

使用@Before在切入点开始处切入内容

使用@After在切入点结尾处切入内容

使用@AfterReturning在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)

使用@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容

使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑

package com.kfit.config.aop.log;

import java.util.Arrays;

import java.util.Enumeration;

import javax.servlet.http.HttpServletRequest;

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.core.annotation.Order;

import org.springframework.stereotype.Component;

import org.springframework.web.context.request.RequestContextHolder;

import org.springframework.web.context.request.ServletRequestAttributes;

/**

* 实现Web层的日志切面

* @author Angel(QQ:412887952)

* @version v.0.1

*/

@Aspect

@Component

@Order(-5)

public class WebLogAspect {

private Logger logger =  LoggerFactory.getLogger(this.getClass());

/**

* 定义一个切入点.

* 解释下:

*

* ~ 第一个 * 代表任意修饰符及任意返回值.

* ~ 第二个 * 任意包名

* ~ 第三个 * 代表任意方法.

* ~ 第四个 * 定义在web包或者子包

* ~ 第五个 * 任意方法

* ~ .. 匹配任意数量的参数.

*/

@Pointcut("execution(public * com.kfit.*.web..*.*(..))")

publicvoid webLog(){}

@Before("webLog()")

publicvoid doBefore(JoinPoint joinPoint){

// 接收到请求,记录请求内容

logger.info("WebLogAspect.doBefore()");

ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

HttpServletRequest request = attributes.getRequest();

// 记录下请求内容

logger.info("URL : " + request.getRequestURL().toString());

logger.info("HTTP_METHOD : " + request.getMethod());

logger.info("IP : " + request.getRemoteAddr());

logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());

logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));

//获取所有参数方法一:

Enumeration<String> enu=request.getParameterNames();

while(enu.hasMoreElements()){

String paraName=(String)enu.nextElement();

System.out.println(paraName+": "+request.getParameter(paraName));

}

}

@AfterReturning("webLog()")

publicvoid  doAfterReturning(JoinPoint joinPoint){

// 处理完请求,返回内容

logger.info("WebLogAspect.doAfterReturning()");

}

}

整个代码比较不好理解地方就是切点表达式,已经在注释中详细说明了,这里不再过多的介绍。编码中需要根据您自己的包命名规范进行修改下。

这时候运行程序访问 http://127.0.0.1:8080/hello?name=林峰&state=1  就可以看到控制台的打印信息了。

: WebLogAspect.doBefore()

: URL : http://127.0.0.1:8080/hello

: HTTP_METHOD : GET

: IP : 127.0.0.1

: CLASS_METHOD : com.kfit.demo.web.HelloController.hello

: ARGS : [林峰, 1]

name: 林峰

state: 1

: WebLogAspect.doAfterReturning()

: 耗时(毫秒) : 1

优化:AOP切面中的同步问题

在WebLogAspect切面中,分别通过doBefore和doAfterReturning两个独立函数实现了切点头部和切点返回后执行的内容,若我们想统计请求的处理时间,就需要在doBefore处记录时间,并在doAfterReturning处通过当前时间与开始处记录的时间计算得到请求处理的消耗时间。

那么我们是否可以在WebLogAspect切面中定义一个成员变量来给doBefore和doAfterReturning一起访问呢?是否会有同步问题呢?

的确,直接在这里定义基本类型会有同步问题,所以我们可以引入ThreadLocal对象,像下面这样进行记录改造代码为如下:

package com.kfit.config.aop.log;

import java.util.Arrays;

import java.util.Enumeration;

import javax.servlet.http.HttpServletRequest;

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.stereotype.Component;

import org.springframework.web.context.request.RequestContextHolder;

import org.springframework.web.context.request.ServletRequestAttributes;

/**

* 实现Web层的日志切面

* @author Angel(QQ:412887952)

* @version v.0.1

*/

@Aspect

@Component

public class WebLogAspect {

private Logger logger =  LoggerFactory.getLogger(this.getClass());

ThreadLocal<Long> startTime = new ThreadLocal<Long>();

/**

* 定义一个切入点.

* 解释下:

*

* ~ 第一个 * 代表任意修饰符及任意返回值.

* ~ 第二个 * 任意包名

* ~ 第三个 * 代表任意方法.

* ~ 第四个 * 定义在web包或者子包

* ~ 第五个 * 任意方法

* ~ .. 匹配任意数量的参数.

*/

@Pointcut("execution(public * com.kfit.*.web..*.*(..))")

publicvoid webLog(){}

@Before("webLog()")

publicvoid doBefore(JoinPoint joinPoint){

startTime.set(System.currentTimeMillis());

// 接收到请求,记录请求内容

logger.info("WebLogAspect.doBefore()");

ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

HttpServletRequest request = attributes.getRequest();

// 记录下请求内容

logger.info("URL : " + request.getRequestURL().toString());

logger.info("HTTP_METHOD : " + request.getMethod());

logger.info("IP : " + request.getRemoteAddr());

logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());

logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));

//获取所有参数方法一:

Enumeration<String> enu=request.getParameterNames();

while(enu.hasMoreElements()){

String paraName=(String)enu.nextElement();

System.out.println(paraName+": "+request.getParameter(paraName));

}

}

@AfterReturning("webLog()")

publicvoid  doAfterReturning(JoinPoint joinPoint){

// 处理完请求,返回内容

logger.info("WebLogAspect.doAfterReturning()");

logger.info("耗时(毫秒) : " + (System.currentTimeMillis() - startTime.get()));

}

}

优化:AOP切面的优先级

由于通过AOP实现,程序得到了很好的解耦,但是也会带来一些问题,比如:我们可能会对Web层做多个切面,校验用户,校验头信息等等,这个时候经常会碰到切面的处理顺序问题。

所以,我们需要定义每个切面的优先级,我们需要@Order(i)注解来标识切面的优先级。i的值越小,优先级越高。假设我们还有一个切面是CheckNameAspect用来校验name必须为didi,我们为其设置@Order(10),而上文中WebLogAspect设置为@Order(5),所以WebLogAspect有更高的优先级,这个时候执行顺序是这样的:

在@Before中优先执行@Order(5)的内容,再执行@Order(10)的内容

在@After和@AfterReturning中优先执行@Order(10)的内容,再执行@Order(5)的内容

所以我们可以这样子总结:

在切入点前的操作,按order的值由小到大执行

在切入点后的操作,按order的值由大到小执行

在实际中order值可以设置为负值,确保是第一个进行执行的。

Spring Boot 系列博客】

54. spring boot日志升级篇—logback【从零开始学Spring Boot】

52. spring boot日志升级篇—log4j多环境不同日志级别的控制【从零开始学Spring Boot】

51. spring boot属性文件之多环境配置【从零开始学Spring Boot】

50. Spring Boot日志升级篇—log4j【从零开始学Spring Boot】

49. spring boot日志升级篇—理论【从零开始学Spring Boot】

48. spring boot单元测试restfull API【从零开始学Spring Boot】

47. Spring Boot发送邮件【从零开始学Spring Boot】

46. Spring Boot中使用AOP统一处理Web请求日志

45. Spring Boot MyBatis连接Mysql数据库【从零开始学Spring Boot】

44. Spring Boot日志记录SLF4J【从零开始学Spring Boot】

43. Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】

42. Spring Boot多数据源【从零开始学Spring Boot】

41. Spring Boot 使用Java代码创建Bean并注册到Spring中【从零开始学Spring Boot】

40. springboot + devtools(热部署)【从零开始学Spring Boot】

39.4 Spring Boot Shiro权限管理【从零开始学Spring Boot】

39.3 Spring Boot Shiro权限管理【从零开始学Spring Boot】

39.2. Spring Boot Shiro权限管理【从零开始学Spring Boot】

39.1 Spring Boot Shiro权限管理【从零开始学Spring Boot】

38 Spring Boot分布式Session状态保存Redis【从零开始学Spring Boot】

37 Spring Boot集成EHCache实现缓存机制【从零开始学Spring Boot】

36 Spring Boot Cache理论篇【从零开始学Spring Boot】

35 Spring Boot集成Redis实现缓存机制【从零开始学Spring Boot】

34Spring Boot的启动器Starter详解【从零开始学Spring Boot】

33 Spring Boot 监控和管理生产环境【从零开始学Spring Boot】

32 Spring Boot使用@SpringBootApplication注解【从零开始学Spring Boot】

31 Spring Boot导入XML配置【从零开始学Spring Boot】

更多查看博客: http://412887952-qq-com.iteye.com/

46. Spring Boot中使用AOP统一处理Web请求日志的更多相关文章

  1. Spring Boot中使用AOP统一处理Web请求日志

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是Spring框架中的一个重要内容,它通 ...

  2. (转)Spring Boot中使用AOP统一处理Web请求日志

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是Spring框架中的一个重要内容,它通 ...

  3. 转&colon;Spring Boot中使用AOP统一处理Web请求日志

    在spring boot中,简单几步,使用spring AOP实现一个拦截器: 1.引入依赖: <dependency> <groupId>org.springframewor ...

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

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

  5. SpringBoot2&period;0 使用AOP统一处理Web请求日志(完整版)

    一,加入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...

  6. spring Boot使用AOP统一处理Web请求日志记录

    1.使用spring boot实现一个拦截器 1.引入依赖: <dependency>   <groupId>org.springframework.boot</grou ...

  7. AOP统一处理Web请求日志

    <!--aop--> <dependency> <groupId>org.springframework.boot</groupId> <arti ...

  8. springboot Aop 统一处理Web请求日志

    1.增加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...

  9. Spring Boot中使用AOP记录请求日志

    这周看别人写的springboot后端代码中有使用AOP记录请求日志,以前没接触过,因此学习下. 一.AOP简介 AOP为Aspect Oriented Programming的缩写,意为:面向切面编 ...

随机推荐

  1. 批处理命令——choice

    [1]choice命令简介 使用此命令可以提示用户输入一个选择项,根据用户输入的选择项再决定执行具体的过程. 使用时应该加/c:参数,c: 后应写提示可输入的字符或数字,之间无空格.冒号是可选项. 使 ...

  2. windows 80端口被占用

    首先关了iis服务,或者把端口换了. 然后关了SQLServer Reporting Service. http://www.2cto.com/os/201505/399603.html

  3. nginx利用image&lowbar;filter动态生成缩略图

    转自:http://www.nginx.cn/2160.html "我如今是有些图片须要生成缩略图.这个如今加了image_filter这个已经实现了.但我不知道怎么样才干訪问我上传的原图& ...

  4. 【JAVA - 基础】之Annotation注解浅析

    注解在JAVA中,尤其是一些ORM框架(如Hibernate等)中是比较常用的一种机制. 注解是JAVA 1.5之后引入的新功能,正确来说是反射的一部分,没有反射,注解也就无法正常使用.注解可以理解成 ...

  5. JDK8- interface接口可以写方法体了-- 惊 dt&period;jar包等

    基本上所有的Java教程都会告诉我们Java接口的方法都是public.abstract类型的,没有方法体的. 但是在JDK8里面,你是可以突破这个界限的哦. == 一个类实现某个接口,必须重写接口中 ...

  6. C&num;比较两个对象是否为同一个对象。

    两个对象是否为同一个对象:是看两个对象是否指向堆中的同一块内存. 1.使用object.ReferenceEquals() class Program { static void Main(strin ...

  7. dubbo监控中心---dubbo-admin

    dubbo监控中心---dubbo-admin: 一:dubbo管理平台搭建: 1-1:下载dubbo-admin.war包 https://pan.baidu.com/s/16jkoSozY68M7 ...

  8. Lucene6&period;6&period;0 案例与学习路线

    之前在学习Lucene这个全文检索工具,为项目搜索引擎的开发打下基础.在这里先分享一下关于Lucene的学习心得. 核心的学习流程是:索引文件格式--索引创建过程--检索流程. 1.首先建议参看这篇精 ...

  9. 笔记之分布式文件系统(DFS)

    不知何故,老外都挺喜欢使用DFS,但是国内公司用这个的不多.一个具体的需求就是,备份服务器在国外,所以启用DFS把国内的数据同步一份到国外进行备份.最近有机会接触DFS,把一些心得体会记录一下. 1. ...

  10. c&plus;&plus; 判断list是否为空(empty)

    #include <list> #include <iostream> using namespace std; int main() { list<int> nu ...