个人博客 Spring框架核心之一,面向切面编程。
1、使用场景 系统日志处理 系统事务处理 系统安全验证 系统数据缓存 2、优点 在不改变原有功能代码的基础上扩展新的功能实现——OCP原则。 可以简化代码开发提高效率。 可以将非核心业务代码将业务层抽离。 3、相关概念 切面(aspect):横切面对象,一般为一个具体类对象(可以借助@Aspect声明),可以理解为要植入的新的业务功能,这个功能交给某个类负责,这个类就是切面。 切入点(pointcut):对连接点拦截内容的一种定义,在原有的哪些业务方法上扩展新的业务,可以将切入点理解为方法的集合,可以是1个类或某些类。 连接点(joinpoint):程序执行过程中某个特定的点,一般指被拦截到的的方法,可以简单理解为切入点中的一个具体方法。 通知(advice):拦截到连接点之后只要执行的方法,可以理解为一个业务中的扩展逻辑的若干步骤,先做什么(before),再做什么(afterReturn),最后做什么。 目标对象(target):封装原业务逻辑的对象。 代理对象(proxy):负责调用切面中的方法为目标对象植入新的功能。 4、通知注解 注解 用途 @Pointcut 定义切入点 @Before 目标方法执行之前执行 @After 目标方法执行之后必定执行,无论是否报错 @AfterReturning 目标方法有返回值且正常返回后执行 @AfterThrowing 目标方法抛出异常后执行 @Around 可以获取到目标方法的入参和返回值
5、实现原理 默认使用 Java 动态代理来创建 AOP 代理,这样就可以为任何接口实例创建代理了。 当需要代理的类不是代理接口的时候,Spring 会切换为使用 CGLIB 代理。 6、Maven导包 1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-aop</artifactId > </dependency >
7、定义切面 创建1个切面类。 在类上添加注解@Aspect
、@Component
。 如果有多个切面可以使用@Order
注解指定顺序,指定的值越小越先执行。 8、定义切入点 可以为指定的beanId或者通过execution表达式来给1个类或某些类定义切入点。并且这两种方式还可以和指定注解一起通过逻辑运算符(||、&&)
来决定对连接点是否启用某个通知方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Order(10) @Aspect @Component @Slf4j public class LogAspect { @Pointcut("bean(aopController)") public void methods () {} @Before("methods()") public void doBefore (JoinPoint joinPoint) { log.info("=====Before=====" ); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); log.info("URL:[{}]" , request.getRequestURL().toString()); log.info("HTTP_METHOD:[{}]" , request.getMethod()); log.info("IP:[{}]" , request.getRemoteAddr()); log.info("CLASS_METHOD:[{}]" , joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); log.info("ARGS:\r\n{}" , JSON.toJSONString(joinPoint.getArgs(), true )); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Order(1) @Aspect @Component @Slf4j public class TimeAspect { @Pointcut("execution(public * net.zhaoxiaobin.aop.web..*.*(..))") public void methods () {} @Around("methods()") public Object doAround (ProceedingJoinPoint point) { long start = System.currentTimeMillis(); Object[] args = point.getArgs(); log.info("=====环绕通知开始=====" ); Object result; try { result = point.proceed(args); } catch (Throwable e) { log.error("=====切面捕获异常=====" , e); JSONObject response = new JSONObject(); response.put("returnCode" , "999999" ); response.put("returnMsg" , "失败" ); response.put("timestamp" , DateUtil.formatDateTime(new Date())); result = response; } log.info("环绕通知结束:\r\n{}" , JSON.toJSONString(result, true )); long end = System.currentTimeMillis(); log.info("=====耗时:[{}]ms=====" , end - start); return result; } }
符号 含义 第一个 * 符号 表示返回值的类型任意 net.zhaoxiaobin.aop.web AOP所切的服务的包名,即需要进行横切的业务类 包名后面的 .. 表示当前包及子包 第二个 * 表示类名 .*(..) 表示任何方法名,括号表示参数,两个点表示任何参数类型
1 2 3 4 5 6 7 8 9 @After("methods() && @annotation(annot)") public void doAfter (NeedAspect annot) { log.info("=====After=====" ); log.debug("=====注解值[{}]=====" , annot.value()); }
1 2 3 4 5 6 7 8 9 10 11 12 @RestController @Slf4j @RequestMapping("/aop") public class AopController { @PostMapping("/add") @NeedAspect("add") public JSONObject add (@RequestBody JSONObject request) { log.info("=====请求报文=====\r\n{}" , JSON.toJSONString(request, true )); return success(); } }
9、说明 不需要添加@EnableAspectJAutoProxy来启用切面,原来SpringMVC中需要在配置中添加<aop:aspectj-autoproxy/>
才会使切面生效。SpringBoot只需要导入aop jar包,声明好切面,就自动会为切入点下的所有bean生成代理对象。
环绕通知通过切入点的proceed方法向后调用,先执行Before前置通知方法,接着调用目标方法,如果有其它切面的Order比当前切面的Order更大,则执行其它切面的After、AfterReturning(AfterThrowing)方法,最后获取返回值,环绕通知结束后再执行当前切面的After、AfterReturning(AfterThrowing)相关方法。
代码地址