Spring AOP详细解析
AOP
AOP:面向切面编程,将与业务无关,却为业务所共同调用的逻辑封装起来,减少系统的重复度,降低模块间的耦合度。对于一些“弱共性”。AOP可以同一对他们进行抽象和集中处理
AspectJ:切面,@ Aspect
JoinPoint:连接点,程序执行过程中的可被切面拦截的特定点
Advice:通知,定义横切逻辑
- @Before: 在方法之前执行增强
- @ After在方法之后执行通知
- @ Around:在方法执行前后都通知
- @ AfterReturning:在方法执行后返回结果执行通知
- @ AfterThrowing: 在方法抛出异常后执行通知
PointCut:切点,匹配连接点
@Pointcut("execution(public * com.neilxu..*Controller.*(..))")
AOP实现机制-基于动态代理
动态代理:运行时动态生成代理对象,允许开发者运行时指定需要代理的接口和行为,在不修改原始类的情况下对方法调用进行增强和拦截。
基于JDK的动态代理:基于reflect包的Proxy和InvolationHandler接口实现,需要代理的类实现一个或者多个接口
基于CGLIB的动态代理:当代理的类没有实现接口时,使用CGLIB生成被代理类的子类作为代理
能使用静态代理的方式实现AOP吗?
AOP首先在切面(Aspect)中定义一个Advice(增强),将advice织入到对象的方法中
可以,但是有较多缺点:
- 代码爆炸:假如有100个Service类需要加事务,就需要写100个静态代理类
- 僵化:一旦业务改名,所以相关的代理类都得改名
- 无法动态筛选:假如只想给某个注解得方法加事务,静态代理必须写死逻辑
package com.neilxu.train.member.aspect;import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;@Aspect
@Component
public class LogAspect {public LogAspect() {System.out.println("LogAspect");}private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class);/*** 定义一个切点*///任意返回类型,neilxu下任意包(项目名/模块名),任意Controller类下任意方法任意参数@Pointcut("execution(public * com.neilxu..*Controller.*(..))")public void controllerPointcut() {}@Before("controllerPointcut()")public void doBefore(JoinPoint joinPoint) {// 增加日志流水号MDC.put("LOG_ID", System.currentTimeMillis() + RandomUtil.randomString(3));// 开始打印请求日志ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();Signature signature = joinPoint.getSignature();String name = signature.getName();// 打印请求信息LOG.info("------------- 开始 -------------");LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name);LOG.info("远程地址: {}", request.getRemoteAddr());// 打印请求参数Object[] args = joinPoint.getArgs();// LOG.info("请求参数: {}", JSONObject.toJSONString(args));// 排除特殊类型的参数,如文件类型Object[] arguments = new Object[args.length];for (int i = 0; i < args.length; i++) {if (args[i] instanceof ServletRequest|| args[i] instanceof ServletResponse|| args[i] instanceof MultipartFile) {continue;}arguments[i] = args[i];}// 排除字段,敏感字段或太长的字段不显示:身份证、手机号、邮箱、密码等String[] excludeProperties = {"mobile"};PropertyPreFilters filters = new PropertyPreFilters();PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();excludefilter.addExcludes(excludeProperties);LOG.info("请求参数: {}", JSONObject.toJSONString(arguments, excludefilter));}@Around("controllerPointcut()")public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {long startTime = System.currentTimeMillis();Object result = proceedingJoinPoint.proceed();// 排除字段,敏感字段或太长的字段不显示:身份证、手机号、邮箱、密码等String[] excludeProperties = {"mobile"};PropertyPreFilters filters = new PropertyPreFilters();PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();excludefilter.addExcludes(excludeProperties);LOG.info("返回结果: {}", JSONObject.toJSONString(result, excludefilter));LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);return result;}}