写在开头:
我是「猿码天地」,一个热爱技术、热爱编程的IT猿。技术是开源的,知识是共享的!
写博客是对自己学习的总结和记录,如果您对Java、分布式、微服务、中间件、Spring Boot、Spring Cloud等技术感兴趣,可以关注我的动态,我们一起学习,一起成长!
用知识改变命运,让家人过上更好的生活,互联网人一家亲!
关注微信公众号【猿码天地】,获取更多干货技能,一起吃肉喝汤,陪你一起撸代码!
SpringBoot AOP 实现埋点日志记录(完整源码)
随着互联网技术的深入发展,各个系统的日活用户、访问量、点击量成指数上升,为保证系统的安全性、易用性,每个系统都需要对用户的访问做埋点记录、跟踪,从而获取用户常用的操作习惯,同时也方便系统管理人员对系统做日常记录、跟踪。
一、Spring Boot AOP
AOP:面向切面编程,相对于OOP面向对象编程,Spring的AOP的存在目的是为了解耦。AOP可以让一组类共享相同的行为。在OOP中只能继承和实现接口,且类继承只能单继承,阻碍更多行为添加到一组类上,AOP弥补了OOP的不足。还有就是为了清晰的逻辑,让业务逻辑关注业务本身,不用去关心其它的事情,比如事务。
实现方式:Spring的AOP是通过JDK的动态代理和CGLIB实现的。
二、AOP的常用术语
AOP有一堆术语,主要包括以下:
通知(Advice) 需要完成的工作叫做通知,就是你写的业务逻辑中需要比如事务、日志等先定义好,然后需要的地方再去用。
连接点(Join point) spring中允许使用通知的地方,基本上每个方法前后抛异常时都可以是连接点。
切点(Poincut) 筛选出的连接点,一个类中的所有方法都是连接点,但又不全需要,会筛选出某些作为连接点做为切点。
切面(Aspect) 通知和切点的结合,通知和切点共同定义了切面的全部内容,它是干什么的,什么时候在哪执行。
引入(Introduction) 在不改变一个现有类代码的情况下,为该类添加属性和方法,可以在无需修改现有类的前提下,让它们具有新的行为和状态。其实就是把切面用到目标类中去。
目标(target) 被通知的对象。也就是需要加入额外代码的对象,真正的业务逻辑被组织织入切面。
织入(Weaving) 把切面加入程序代码的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入。
三、源码实现埋点日志记录
3.1 项目结构图
3.2 代码实现
- 配置文件
这里只有三个配置:
server.port=8081,设置项目启动的端口号,防止被其他服务占用
server.servlet.context-path: /aop,项目上下文
spring.aop.auto=true,开启spring的aop配置,简单明了,不需要多配置其他的配置或注解。
server:
port: 8081
servlet.context-path: /aop
spring:
aop:
auto: true
-
AOP切面类
这个是最主要的类,可以使用自定义注解或针对包名实现AOP增强。
1)这里实现了对自定义注解的环绕增强切点,对使用了自定义注解的方法进行AOP切面处理。
2)对方法运行时间进行监控。
3)对方法名,参数名,参数值,对日志描述的优化处理。在方法上增加@Aspect注解声明切面 使用@Pointcut 注解定义切点,标记方法
使用切点增强的时机注解:
@Before 前置通知, 在方法执行之前执行
@Around 环绕通知, 围绕着方法执行
@AfterReturning 返回通知, 在方法返回结果之后执行
@AfterThrowing 异常通知, 在方法抛出异常之后
@After 后置通知, 在方法执行之后执行
package com.bowen.aspect;
import com.alibaba.fastjson.JSON;
import com.bowen.annotation.OperationLogDetail;
import com.bowen.model.OperationLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* <h3>AspectDemo</h3>
* <p>AOP切面类</p>
* @author : zhang.bw
* @date : 2020-04-16 14:52
**/
@Aspect
@Component
public class LogAspect {
private static final Logger LOG = LoggerFactory.getLogger(LogAspect.class);
/**
* 定义切点
* 此处的切点是注解的方式,也可以用包名的方式达到相同的效果
* '@Pointcut("execution(* com.bowen.service.impl.*.*(..))")'
*/
//@Pointcut("@annotation(com.bowen.annotation.OperationLogDetail)")
@Pointcut("execution(* com.bowen.controller.*.*(..))")
public void operationLog(){
}
/**
* 环绕增强,相当于MethodInterceptor
*/
@Around("operationLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object res = null;
long time = System.currentTimeMillis();
try {
res = joinPoint.proceed();
time = System.currentTimeMillis() - time;
return res;
} finally {
try {
//方法执行完成后增加日志
addOperationLog(joinPoint,res,time);
}catch (Exception e){
LOG.error("LogAspect 操作失败:" + e.getMessage());
}
}
}
/**
* 方法执行完成后增加日志
* @param joinPoint
* @param res
* @param time
*/
private void addOperationLog(JoinPoint joinPoint, Object res, long time){
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
OperationLog operationLog =<