SpringBootAOP记录操作日志

1、基本概念

AOP(Aspect Oriented Programming)称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待

1.1 相关概念

横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

Aspect(切面): 通常是一个类,里面可以定义切入点通知

JointPoint(连接点): 程序执行过程中明确的点,一般是方法的调用。被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

Advice(通知):AOP在特定的切入点上执行的增强处理,有before(前置),after(后置),afterReturning(最终),afterThrowing(异常),around(环绕)

Pointcut(切入点): 就是带有通知的连接点,在程序中主要体现为书写切入点表达式

**weave(织入)**:将切面应用到目标对象并导致代理对象创建的过程

introduction(引入):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

**AOP代理(AOP Proxy)**:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类

目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。

1.2 AOP使用场景

Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging  调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence  持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务

2、AOP实现记录操作日志

2.1 引入依赖

在Spring框架中,使用AOP配合自定义注解可以方便的实现用户操作的监控。首先搭建一个基本的Spring Boot Web环境开启Spring Boot,然后引入必要依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- aop依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- oracle驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- druid数据源驱动 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.6</version>
</dependency>
<!--Mybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>

2.2 自定义注解

定义一个方法级别的@Log注解,用于标注需要监控的方法:

1
2
3
4
5
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}

2.3 创建表和实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
create table sys_log(
id int(10) not null AUTO_INCREMENT,
username varchar(50),
operation varchar(50),
time int(11),
method varchar(200),
params varchar(500),
ip varchar(64),
create_time datetime,
primary key(id) using btree
)ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
public class SysLog implements Serializable {
private int id;
private String username;
private String operation;
private int time;
private String method;
private String params;
private String ip;
private Date createTime;

//此处省略setter和getter方法
}

2.4 保存日志的方法

1
2
3
4
@Mapper
public interface SysLogDao {
public void saveSysLog(SysLog sysLog);
}

SysLogMapper.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lyq.dao.SysLogDao">
<insert id="saveSysLog">
insert into sys_log (username,operation,time,method,params,ip,create_time)
values (#{username},#{operation},#{time},#{method},#{params},#{ip},#{createTime})
</insert>
</mapper>

2.5 切面和切点

定义一个LogAspect类,使用@Aspect标注让其成为一个切面,切点为使用@Log注解标注的方法,使用@Around环绕通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@Aspect
@Component
public class LogAspect {
@Autowired
private SysLogDao sysLogDao;

@Pointcut("@annotation(com.lyq.annotation.Log)")
public void pointCut(){}

@Around("pointCut()")
public Object around(ProceedingJoinPoint point) {
Object result = null;
long beginTime = System.currentTimeMillis();
try {
// 执行方法
result = point.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
// 执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
// 保存日志
saveLog(point, time);
return result;
}

private void saveLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysLog sysLog = new SysLog();
Log logAnnotation = method.getAnnotation(Log.class);
if (logAnnotation != null) {
// 注解上的描述
sysLog.setOperation(logAnnotation.value());
}
// 请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
// 请求的方法参数值
Object[] args = joinPoint.getArgs();
// 请求的方法参数名称
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
if (args != null && paramNames != null) {
String params = "";
for (int i = 0; i < args.length; i++) {
params += " " + paramNames[i] + ": " + args[i];
}
sysLog.setParams(params);
}
// 获取request
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
// 设置IP地址
sysLog.setIp(IPUtils.getIpAddr(request));
// 模拟一个用户名
sysLog.setUsername("testUser");
sysLog.setTime((int) time);
sysLog.setCreateTime(new Date());
// 保存系统日志
sysLogDao.saveSysLog(sysLog);
}
}

点击这里查看项目源码