AOP切面加自定义注解,实现日志记录
- 一、AOP
- 二、准备工作
- 三、添加AOP,把日志保存到数据库
一、AOP
- 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。
- 我理解就是对接口执行前后耦合的地方添加统一的抽象方法
- 具体概念:Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Target(目标对象):织入 Advice 的目标对象.。
Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
二、准备工作
- 使用spring3的后端项目为基础,开发工具idea 2023.3.2,jdk17。
- 在pom.xml中添加依赖
<!-- AOP -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 在mysql8.0.36中创建一个bus_log表,内容如下:
- spring项目里正常添加实体类,service和mapper.
添加文件Log如下:
package com.sumo.ipd.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "bus_log")
public class Log {
/**
* Log_ID
*/
@TableId(value = "id",type = IdType.AUTO)
private Long id;
/**
*操作人
*/
@TableField(value = "name")
private String name;
/**
*操作人id
*/
@TableField(value = "certificate_no")
private String certificateNo;
/**
* 操作记录
*/
@TableField(value = "record")
private String record;
/**
* 操作时间
*/
@TableField(value = "time")
private String time;
/**
* 操作ip
*/
@TableField(value = "ip")
private String ip;
public static final String COL_ID="id";
public static final String COL_NAME="name";
public static final String COL_CERTIFICATENO="certificate_no";
public static final String COL_RECORD="record";
public static final String COL_TIME="time";
public static final String COL_IP="ip";
}
添加文件LogMapper如下:
package com.sumo.ipd.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sumo.ipd.entity.Log;
public interface LogMapper extends BaseMapper<Log> {
}
添加文件LogService如下:
package com.sumo.ipd.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.sumo.ipd.entity.Log;
public interface LogService extends IService<Log> {
}
添加文件LogServiceImpl如下:
package com.sumo.ipd.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sumo.ipd.entity.Log;
import com.sumo.ipd.mapper.LogMapper;
import com.sumo.ipd.service.LogService;
import org.springframework.stereotype.Service;
@Service
public class LogServiceImpl extends ServiceImpl<LogMapper, Log> implements LogService {
}
三、添加AOP,把日志保存到数据库
创建两个文件BusLog和BusLogAop如下:
BusLog内容如下:
package com.sumo.ipd.annotation;
import java.lang.annotation.*;
/**
* <p> @Title SystemLog
* <p> @Description 接口日志注解
*
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BusLog {
String name() default "" ;//操作人
String record() default "";//操作记录
}
BusLogAop内容如下:
package com.sumo.ipd.aop;
import com.sumo.ipd.entity.User;
import com.sumo.ipd.entity.Log;
import com.sumo.ipd.service.LogService;
import com.sumo.ipd.annotation.BusLog;
import jakarta.annotation.Resource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import java.lang.reflect.Method;
/**
* 切面处理类,记录操作日志到数据库
*/
@Aspect
@Component
public class BusLogAop {
@Resource
LogService logService;
//为了记录方法的执行时间
ThreadLocal<Long> startTime = new ThreadLocal<>();
/**
* 设置操作日志切入点,这里介绍两种方式:
* 1、基于注解切入(也就是打了自定义注解的方法才会切入)
* @Pointcut("@annotation(com.woniu.pc.anno.MyLog)")
* 2、基于包扫描切入
* @Pointcut("execution(public * org.wujiangbo.controller..*.*(..))")
*/
@Pointcut("@annotation(com.sumo.ipd.annotation.BusLog)")//在注解的位置切入代码
//@Pointcut("execution(public * com.woniu.pc.controller..*.*(..))")//从controller切入
public void operLogPoinCut() {
}
@Before("operLogPoinCut()")
public void beforMethod(JoinPoint point){
startTime.set(System.currentTimeMillis());
}
/**
* 设置操作异常切入点记录异常日志 扫描所有controller包下操作
*/
// @Pointcut("execution(* com.sumo.ipd.controller..*.*(..))")
// public void operExceptionLogPoinCut() {
// }
/**
* 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
*
* @param joinPoint 切入点
* @param result 返回结果
*/
@AfterReturning(value = "operLogPoinCut()", returning = "result")
public void saveOperLog(JoinPoint joinPoint, Object result) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
HttpSession session = request.getSession();
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取操作
BusLog myLog = method.getAnnotation(BusLog.class);
Log pcOperateLog = new Log();
if (myLog != null) {
//记录日志的操作人、内容
pcOperateLog.setName(myLog.name());
pcOperateLog.setRecord(myLog.record());
}
//获取操作者
User currentUser = (User) session.getAttribute("user");
if (currentUser != null) {
pcOperateLog.setCertificateNo(currentUser.getCertificateNo());
}
//获取ip地址
String ip = request.getRemoteHost();
pcOperateLog.setIp(ip);
long time =System.currentTimeMillis();
pcOperateLog.setTime(String.valueOf(time));
//调用Service方法,插入数据库
logService.saveOrUpdate(pcOperateLog);
} catch (Exception e) {
e.printStackTrace();
}
}
}
使用时只需要在controller的接口前添加注解@BusLog即可: