理解
AOP面向切面编程,一种思想,用自我理解的话来说就是对已有的方法进行操作,可以在不改动原方法代码的基础上增强方法的功能,在原方法执行前,执行后添加一些额外的业务处理操作,这些额外的操作就是“切面”。例如方法执行前记录日子,检查用户是否具有执行某些特定操作的权限。
动态代理
Spring AOP,思想的具体实现,它的底层原理就是动态代理和字节码操作。
JDK 动态代理(默认)
- 只适用于实现了接口的类。
- 在运行时生成代理类的字节码,而不需要编译时生成代理类
CGLIB 动态代理:
- 适用于没有实现接口的普通类,通过生成子类来创建代理对象。
- 在运行时生成目标类的子类,并在子类中覆盖目标类的非
final
方法,从而在方法调用前后插入切面逻辑。
相关概念
连接点Joinpoint: 可以被AOP控制,操作的方法相关信息
通知Advice:指那些可以被用在每一个方法上的重复的逻辑,共性功能(最终抽取为一个方法)
切入点Pointcut: 实际被AOP控制的方法,自己指定(切入点表达式)
执行流程
在程序运行时,通过动态代理为目标对象创建一个代理对象,并在该代理对象中增强原有方法的功能。当注入对象时,实际注入的是这个代理对象,调用的也是代理对象里的方法,这些方法已经过增强处理。
通过连接点,可以在原始方法执行时获取各种信息,例如类名、方法名和参数等。
@annotation匹配自定义注解
目标:实现数据库日志记录,包括记录操作人员ID、执行的方法和操作时间等信息。
对于那些有统一命名规则的增删改方法,可以使用execution表达式来匹配;而对于命名不规则的方法,则可以通过@annotation来实现匹配。
具体流程为:首先自定义一个用于标识的注解,然后将这个注解添加到所需的方法上。最后,在AOP类中匹配这个注解,从而找到并执行相应的方法通知。
自定义标识类注解
@Retention(RetentionPolicy.RUNTIME)//指定此注解运行时有效
@Target(ElementType.METHOD) //指定作用在方法上
public @interface Log {
}
对想要进行操作的方法加上这个注解
@Log
@DeleteMapping("/{id}")
public Result pop(@PathVariable Integer id){
deptService.pop(id);
return Result.success();
}
创建数据库表和对应的日志实体类(属性一致)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
private Integer id;
private Integer operateUser;
private LocalDateTime operateTime;
private String methodName;
}
定义切面类(重复的逻辑)
谁登录并执行操作,该人即为操作员。关于登录,已经实现了JWT,其中包含了用户的ID和姓名。因此,需要从请求中获取JWT信息,并使用工具类中的方法进行解析。
HttpServletRequest 对象包含了每次请求的所有信息
定义一个环绕通知方法,用于封装具体的操作逻辑。该方法将在目标方法执行前后添加额外的操作。
使用@annotation(com.example.jjweb.anno.Log)可以匹配所有标有Log注解的方法,这些方法将会接收到相应的通知。
@Slf4j
@Component
@Aspect
public class LogAOP {
@Autowired
private HttpServletRequest request;
@Autowired
private OperateLogMapper operateLogMapper;
//由于增删改查的方法名不规则,所以我们基于注解来匹配切入点方法
@Around("@annotation(com.example.jjweb.anno.Log)")
public Object recordLogIndb(ProceedingJoinPoint joinPoint) throws Throwable{
//依次对日志实体类中的属性赋值
//操作人员的id,在login登录时,我们已经把员工id加入到了jwt中,解析获取即可
//通过请求头获取已经生成的jwt
String jwt = request.getHeader("token");
if (jwt == null || jwt.trim().isEmpty()) {
log.warn("请求头中未找到 JWT token");
throw new IllegalArgumentException("JWT err");
}
Claims claims = JwtUtils.parseJWT(jwt);
Integer operateUser = (Integer)claims.get("id");
}
}
依次对日志实体类中的属性赋值,类的信息使用ProceedingJoinPoint获取
LocalDateTime operateTime = LocalDateTime.now();
String methodName = joinPoint.getSignature().getName();
//调用目标方法
Object proceed = joinPoint.proceed();
最后将获取到的信息放在实体类对象中,调用mapper方法,将信息插入到数据库表。数据库中id为自动增长,这里给null
OperateLog operateLog = new
OperateLog(null,operateUser,operateTime,className,methodName,methodParams,costTime);
operateLogMapper.insert(operateLog);
JWT令牌验证https://mp.csdn.net/mp_blog/creation/editor/141727904