Spring AOP(Aspect-Oriented Programming)是 Spring 框架中的一项功能,旨在通过切面(Aspect)将横切关注点(Cross-Cutting Concerns)与业务逻辑解耦,从而使代码更加模块化和易维护。以下是 Spring AOP 的主要目的和应用场景:
Spring AOP(面向切面编程)的目的
Spring AOP(Aspect-Oriented Programming)是 Spring 框架中的一项功能,旨在通过切面(Aspect)将横切关注点(Cross-Cutting Concerns)与业务逻辑解耦,从而使代码更加模块化和易维护。以下是 Spring AOP 的主要目的和应用场景:
日志记录
安全认证与授权
性能监控
事务管理
缓存 通过 AOP,可以将这些横切关注点从业务代码中分离出来,增强代码的关注点分离。
2. 动态增强功能
AOP 允许在运行时动态地增强目标对象的功能,而无需修改目标对象的源码。这种增强可以是方法的前置处理、后置处理、异常处理等。
例如:
前置增强:方法执行前记录日志。
后置增强:方法执行完成后清理资源。
异常增强:捕获异常并记录详细信息。
环绕增强:在方法执行前后动态添加逻辑。
3. 提高代码可维护性
通过 AOP,可以将重复的代码提取到一个单独的切面中,避免在多个地方编写相同的逻辑,从而减少代码冗余,提高代码可读性和可维护性。
4. 实现透明化操作
AOP 的增强功能对业务逻辑代码是透明的,开发者无需感知这些功能如何实现。例如:
日志记录的切面会自动记录方法的执行情况,开发者只需专注于业务逻辑的实现。
权限检查逻辑可以通过切面动态嵌入,而不用在每个业务方法中显式调用。
5. 提供灵活的配置
AOP 支持通过配置实现切面的动态应用,例如:
通过注解(如 @Aspect 和 @Pointcut)精确指定切入点。
通过 XML 配置或 Java 配置文件动态启用或禁用切面。 这使得 AOP 非常灵活,可以根据需求动态调整功能。
6. 典型应用场景
以下是 Spring AOP 的常见应用场景:
日志管理:记录方法的调用、参数、返回值及执行时间。
安全控制:检查用户是否有权限调用某些服务或接口。
事务管理:在方法调用前开启事务,调用后提交或回滚事务。
性能监控:统计方法执行的时间。
异常处理:统一捕获并记录系统中出现的异常。
Spring AOP 工作原理
Spring AOP 主要通过代理模式实现:
基于 JDK 动态代理:对接口进行代理。
基于 CGLIB 动态代理:对没有实现接口的类进行代理。
在代理类中,通过拦截方法调用,在方法执行前后动态添加切面逻辑。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class LogAspect {
//时间发布器
private final ApplicationEventPublisher applicationEventPublisher;
@Pointcut("execution(* com.*.controller..*.*(..))")
public void eventPoint(){}
/**
* 请求时间之前
*/
@Before("eventPoint()")
public void doBefore(JoinPoint joinPoint) {
log.info("日志记录之前");
RequestHolder.set(System.currentTimeMillis());
}
//环绕通知
@Around("eventPoint()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object proceed = joinPoint.proceed();
log.info("环绕增强器:{}",proceed);
return proceed;
}
//请求之后
@AfterReturning(value = "eventPoint()", returning = "keys")
public void doAfterReturning(JoinPoint joinPoint, Object keys) {
try{
log.info("日志记录之后");
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// if(requestAttributes == null){
// log.error("日志事件不进行发布,没有请求链路");
// return;
// }
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method.isAnnotationPresent(Log.class)) {
Log log = method.getAnnotation(Log.class);
// webLog.setDescription(log.summary());
Long requestStartTime = RequestHolder.get();
applicationEventPublisher.publishEvent(new LogInfoEvent(this,requestStartTime,log,joinPoint,keys,requestAttributes));
}
}catch (Exception exception){
log.error("日志事件无法发布,出现错误:",exception);
} catch (Throwable throwable) {
throwable.printStackTrace();
} finally{
RequestHolder.remove();
}
}
//请求报错
@AfterThrowing(value = "eventPoint()",throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e){
RequestHolder.remove();
}
}
@Pointcut("execution(* com.*.controller..*.*(..))"
含义:
execution 表达式: 定义切入点,指定方法的执行匹配规则。
* (通配符):
匹配任意的返回值类型。
匹配任意的包名或类名的一部分。
com.*.controller:
匹配 com 包下任意一级子包的 controller 包。
比如:com.example.controller、com.test.controller。
.. (双点):
匹配任意子包及以下的层级。
匹配方法的任意参数。
*.*(..):
匹配任意类 (*) 的任意方法 (*)
方法参数为任意类型和任意数量 ((..))
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 操作模块
*
* @return
*/
String modul() default "";
/**
* 操作类型
*
* @return
*/
String type() default "";
/**
* 操作说明
*
* @return
*/
String desc() default "";
}
我们这个切片处理采用了异步监听来处理,减少了操作响应时间。
异步时间发布
/**
* 日志详情事件类
* @Author D
*/
@Getter
@Setter
public class LogInfoEvent extends ApplicationEvent {
/**
* 操作人实体
*/
private Object sysUser;
/**
* 织入点
*/
private JoinPoint joinPoint;
/**
* 返回值
*/
private Object keys;
/**
* 上下文
*/
private HttpServletRequest request;
/**
* 请求开始时间
*/
private Long requestStartTime;
/**
* 异步上下文
*/
private AsyncContext asyncContext;
public LogInfoEvent(Object source, Long requestStartTime, Object sysUser, JoinPoint joinPoint, Object keys, RequestAttributes requestAttributes) {
super(source);
this.requestStartTime = requestStartTime;
this.sysUser = sysUser;
this.joinPoint = joinPoint;
this.keys = keys;
HttpServletRequest httpServletRequest = (HttpServletRequest)requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
assert httpServletRequest != null;
this.request = httpServletRequest;
this.asyncContext = httpServletRequest.startAsync();
}
}
异步事件监听
@Slf4j
@Component
public class LogInfoEventListener {
@Async(ThreadPoolConfig.ASSET_MANAGE_EXECUTOR_ASYNC)
@EventListener(classes = LogInfoEvent.class)
public void sendLogInfo(LogInfoEvent event) {
JoinPoint joinPoint = event.getJoinPoint();
Object sysUser = event.getSysUser();
Long requestStartTime = event.getRequestStartTime();
String methodName = "";
//开启该请求被标记为异步处理
AsyncContext asyncContext = event.getAsyncContext();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = event.getRequest();
try {
log.info("正常日志事件开始");
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 请求的方法名
methodName = className + "." + method.getName();
Log annotation = method.getAnnotation(Log.class);
// 如果没有配置注解,并不入库备注信息获取操作
log.info("正常日志事件入库完成");
} catch (Exception e) {
log.error("正常日志事件解析出现错误,访问的方法为:{},详细信息为:{}",methodName,e);
}finally {
asyncContext.complete();
}
}
}