概念
1.Aspect Oriented Programming面向切面编程
处理面向对象编程中业务需求重复的部分,作为横切面插入到面向对象当中,一般有固定的应用场景,例如日志记录,登录校验,数据验证,统计耗时,事务管理等(AOP是OOP的延伸,简单一句话就是对基于面向对象编程的某些业务方法进行增强)
2. OOP(Object Oriented Programming)面向对象编程
处理主要业务逻辑 (AOP是OOP的延伸,简单一句话就是对基于面向对象编程的某些业务方法进行增强)
3.优点
灵活性
切面与主要业务逻辑分离,对它们的更改不会影响核心代码,提供了高度的灵活性。
降低耦合度
AOP有助于减少代码之间的耦合度,使得业务逻辑各部分之间的耦合度降低,提高了程序的可重用性。
提高开发效率
通过AOP,可以在不修改源代码的情况下给程序动态统一添加某种特定功能,提高了开发的效率。
SpringAOP
1.Spring提供的对面向切面编程的支持
2.其底层是动态代理技术实现
jdk动态代理
被代理的类必须要实现一个接口
CGLIB动态代理
基于继承,被代理的类可以是普通类也可以是接口
基本介绍
Code Generation Library 代码生成类库,可以动态的给代理类生成一个子类,然后使用继承方式重写被代理方法
基本使用
第一步
创建增强器对象Enhancer
第二步
给增强器对象设置要代理的父类以及拦截器回调回调
第三步
创建方法拦截器类,实现MethodInterceptor接口
第四步
创建代理对象,并调用方法
如何使用SpringAOP
首先导SpringbootAop的起步依赖
第一步
定义切面类
@Aspect
@Component
第二步
编写切面方法
具体要执行的切面逻辑
第三步(需要定义切入点表达式或者使用@PointCut注解)
定义切面方法通知,即告诉该切面方法对哪个业务方法进行增强
@Around(工作中常用)
环绕通知业务方法
即在被通知增强的业务方法调用前后都执行一段编写的切面方法逻辑
需要给切面方法添加参数ProcedingJointPoint 来调用被增强的业务方法
返回值给Object
@Before
前置通知
即在被通知增强的业务方法执行前先执行切面方法
@After
后置通知
即在被通知增强的业务方法执行后执行切面方法
@AfterReturning
返回后通知
被通知增强的业务方法返回后执行逻辑,有异常不会执行
@AfterThrowing
异常通知
被通知增强的业务方法抛出异常后执行
SpringAop中的一些概念(了解一下)
连接点(JointPoint)
把被通知的方法抽象成了连接点
切入点(PointCut)
需要切入哪些业务方法
通知(Advice)
五种通知类型,切入时机
切面(Aspect)
多个切面的通知运行顺序(@Order)
切入点表达式
execution( )
通过方法签名匹配被通知方法
可以通过单个的 * 通配符匹配任意的返回值,包名,类名,方法名 以及任意类型的一个参数
可以通过 .. 匹配 任意类型,任意包层级,任意个数的方法参数
@annotation 通过被通知方法上添加的注解来匹配
@Around("@annotation(com.lu.tlias84.annotation.Log)")
连接点(通过反射获取被通知方法的运行时信息)
ProceedingJointPoint
环绕通知使用的类
JointPoint
其他四种通知使用的类
使用SpringAop完成日志记录
1.AOP导包
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>3.3.2</version>
</dependency>
2.AOP使用规范
使用AOP前要新建一个包aspect
3.基本使用
package com.lu.tlias84.aspect;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* 统计接口耗时切面逻辑
*/
@Aspect
@Component
@Slf4j
public class CountTimeAspect {
@Resource
HttpServletRequest request;
@Around("execution(* com.lu.tlias84.service.impl.*.*(..))")
public Object count(ProceedingJoinPoint point) {
String token = request.getHeader("token");
log.info("获取toke{}",token);
//获取参数
Object[] args = point.getArgs();
log.info("请求参数",args);
//获取类名
Object target = point.getTarget();
log.info("获取类名{}",target);
long start = System.currentTimeMillis();
//执行父类方法
try {
Object proceed = point.proceed();
long end = System.currentTimeMillis() - start;
log.info("{}该接口执行时间为{}",point.getSignature().getName(),end);
return proceed;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
使用AOP完成接口增删改日志增加
1.创建annotation
这个包中是自定义注解,定义一个Log注解,用来标记哪个接口需要被日志记录
package com.lu.tlias84.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 日志切面注解,用于切面识别添加方法日志
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}
哪个类的方法需要被增强就在这个类的方法上加个@Log注解
2.定义日志接参类
用于插入
package com.lu.tlias84.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LogInsertVO {
private Integer operatorEmpId;
private LocalDateTime operateTime;
private String className;
private String methodName;
private String methodParams;
private String returnValue;
private String costTime;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
3.定义日志插入mapper
<insert id="insertLog">
insert into log(operator_emp_id, operate_time, class_name, method_name, method_params, return_value, cost_time,
create_time, update_time)
values (#{l.operatorEmpId}, #{l.operateTime}, #{l.className}, #{l.methodName}, #{l.methodParams},
#{l.returnValue}, #{l.costTime}, #{l.createTime}, #{l.updateTime})
</insert>
4.AOP切面配置
新建一个aspect包用来储存AOP切面配置
package com.lu.tlias84.aspect;
import com.alibaba.fastjson.JSONObject;
import com.lu.tlias84.mapper.EmpMapper;
import com.lu.tlias84.mapper.LogMapper;
import com.lu.tlias84.utils.JwtUtil;
import com.lu.tlias84.vo.LogInsertVO;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Arrays;
/**
* 标识切面类
*/
@Slf4j
@Aspect
@Component
public class LogAspect {
@Resource
private HttpServletRequest request;
@Resource
private EmpMapper empMapper;
@Resource
private LogMapper logMapper;
@Around("@annotation(com.lu.tlias84.annotation.Log)")
public Object insertLog(ProceedingJoinPoint point) {
String token = request.getHeader("token");
JSONObject jsonObject = JwtUtil.verifyToken(token);
String username = (String) jsonObject.get("username");
Integer userNameId = empMapper.selectEmpByUserName(username);
long startTime = System.currentTimeMillis();
Object result = null;
try {
result = point.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
long executionTime = System.currentTimeMillis() - startTime;
LogInsertVO logInsertVO = new LogInsertVO();
logInsertVO.setCostTime(executionTime + "ms"); // 设置毫秒
logInsertVO.setOperatorEmpId(userNameId); // 设置操作人
logInsertVO.setCreateTime(LocalDateTime.now()); // 设置创建时间
logInsertVO.setUpdateTime(LocalDateTime.now()); // 设置更新时间
logInsertVO.setClassName(point.getTarget().getClass().getName()); // 获取类名
logInsertVO.setMethodParams(Arrays.toString(point.getArgs())); // 获取请求参数
logInsertVO.setMethodName(point.getSignature().getName()); // 获取方法名
logInsertVO.setReturnValue(result != null ? result.toString() : "null"); // 获取返回值
logInsertVO.setOperateTime(LocalDateTime.now()); // 设置操作时间
log.info("{}", logInsertVO);
logMapper.insertLog(logInsertVO);
return result;
}
}