在之前一系列的文章中都是提供了全部的代码,在之后的文章中就提供核心的代码进行讲解。有什么问题大家可以给我留言或者加我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. 转: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.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. conda命令不能用的问题

    在Windows下安装了Anaconda3-5.0.1-Windows-x86.exe 但是在cmd端下,打命令conda没反应 打pip显示如下错误 这是由于cmd编码为gbk的问题导致的 解决方案 ...

  2. LOJ#557. 「Antileaf's Round」你这衣服租来的吗(FHQ Treap+珂朵莉树)

    题面 传送门 题解 好吧我是不太会复杂度分析-- 我们对于每种颜色用一个数据结构维护(比方说线段树或者平衡树,代码里写的平衡树),那么区间询问很容易就可以解决了 所以现在的问题是区间修改,如果区间颜色 ...

  3. epoll IO多路复用(异步阻塞AIO)

    epoll的异步阻塞(AIO): 用户线程创建epoll后,其实是内核线程负责扫描 fd 列表(在网络服务器上可以是socket,socket在创建后返回的也是文件描述符),并填充事件链表.但是,并不 ...

  4. LN : leetcode 207 Course Schedule

    lc 207 Course Schedule 207 Course Schedule There are a total of n courses you have to take, labeled ...

  5. [ CodeForces 1059 C ] Sequence Transformation

    \(\\\) \(Description\) 你现在有大小为\(N\)的一个数集,数字分别为 \(1,2,3,...N\) ,进行\(N\)轮一下操作: 输出当前数集内所有数的\(GCD\) 从数集中 ...

  6. 从React看weight开发

    从当前云发展的势头来看几乎所有互联网应用都趋向大一统的趋势,一个node下面加一堆应用,同时我们项目也趋向把复杂的大应用拆分成多个小应用,通过各种复杂的Api来协作,通信,达到同样的效果. 可以看出, ...

  7. android视频播放器系列(二)——VideoView

    最近在学习视频相关的知识,现在也是在按部就班的一步步的来,如果有同样需求的同学可以跟着大家一起促进学习. 上一节说到了可以使用系统播放器以及浏览器播放本地以及网络视频,但是这在很大程度上并不能满足我们 ...

  8. Rxjava1升级Rxjava2踩坑一记

    Rxjava1升级Rxjava2坑 共存问题 通常情况下,如果我们希望在一个模块中既想使用rxjava1又想使用rxjava2,这个时候在运行的时候会出现一下报错: ... APK META/-INF ...

  9. 【转载】 python sort、sorted高级排序技巧

    这篇文章主要介绍了python sort.sorted高级排序技巧,本文讲解了基础排序.升序和降序.排序的稳定性和复杂排序.cmp函数排序法等内容,需要的朋友可以参考下 Python list内置so ...

  10. CAD在网页中如何实现嵌套打印?

    当用户需要打印两个控件的图纸时,可以采用嵌套打印实现.点击此处在线演示. 实现嵌套打印功能,首先将两个控件放入网页中,js代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...