一、使用场景(横切问题关注点)
- 日志记录
- 异常处理
- 权限验证
- 缓存处理
- 事物处理
- 数据持久化
- 效率检查
- 内容分发
二、AOP中主要概念相关理解(切面)
- aspect:切面,切面有切点和通知组成,即包括横切逻辑的定义也包括连接点的定义
- pointcut:切点,每个类都拥有多个连接点,可以理解是连接点的集合
- joinpoint:连接点,程s序执行的某个特定位置,如某个方法调用前后等
- weaving:织出,将增加的目标类的具体连接点的过程
- advice:通知,是织入到目标类连接点上的一段代码,就是增加到什么地方?增加什么内容?
- target:目标对象,通知织入的目标类
- aop Proxy:代理对象,即增加后产生的对象
Spring AOP 底层实现,是通过JDK动态代理或CGLib代理在运行时期在对象初始化阶段织入代码的。
JDK动态代理基于接口实现 --- 区别 --- CGLib是基于类的继承实现的
三、Aspect概念(通知的种类)
- 1.Before advice
- 前置通知,即在目标方法调用之前执行。注意:无论方法是否遇到异常都执行
- 2.After returning advice
- 后置通知,在目标方法执行之后执行,前提是目标方法没有遇到异常,如果有异常则不会执行通知。
- 3.After Throwing advice
- 异常通知,在目标方法抛出异常时执行,可以获取异常信息。
- 4.After finally advice
- 最终通知,在目标方法执行后执行,无论是否有异常执行
- 5.Around advice
- 环绕通知,最强大的通知类型,可以控制目标方法的执行(通过调用ProceedingJoinpoint.proceed())可以在目标全过程中进行执行。
四、@Aspect驱动(实现步骤)
- 1.定义一个切面类Aspect
- 即在声明的类,増加@Component @Aspect。两个注解,Springboot项目中要引入 spring- boot- stater-aop依赖包,
- 2.定义切点Pointcut
- 定义切点,并定义切点在那些地方执行,采用@Pointcut注解完成, 如:
@Pointcut( public * com.xxxx.*.*(..) )
* 规则:修饰符(可以不写,但不能用*) +返回类型+哪些包下的类+哪些方法+方法参数“*” 代表不限,“.." 两个点代表参数不限。
- 3.定义通知
- 利用通知的5种类型注解@Before. @After、 @AfterReturning、@AfterThrowing、 @ Around来完成在某些切点的増强动作。如@Before(" myPointcut()"), myPointcut为第二步骤定义的切点
- 代码实现
package com.yang.aop.controller; import com.yang.aop.service.HelloService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @Description: * @Author: Yang.Guo * @Date: 2021/08/26/10:44 */ @RestController public class HelloController { @Autowired private HelloService helloService; @RequestMapping("/hello") public String hello(@RequestParam("name") String name) { return helloService.testServiceAop(name); } @RequestMapping("/A") public String A(@RequestParam("name") String name) { return helloService.A(name); } @RequestMapping("/testThrow") public String testThrow(@RequestParam("name") String name) { return helloService.testThrow(name); } }
package com.yang.aop.service; import org.springframework.stereotype.Service; @Service public interface HelloService { String testServiceAop(String name); /** * 测试拦截Service层返回数据 * @param name * @return */ String A(String name); /** * 测试抛异常 * @param name * @return */ String testThrow(String name); }
package com.yang.aop.service.impl; import com.yang.aop.service.HelloService; import org.springframework.stereotype.Service; @Service("HelloServiceImpl") public class HelloServiceImpl implements HelloService { @Override public String testServiceAop(String name) { return name; } @Override public String A(String name) { return "name"; } @Override public String testThrow(String name) { throw new RuntimeException("测试抛异常"); } }
package com.yang.aop.aspact; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * @Description: * @Author: Yang.Guo * @Date: 2021/08/26/10:47 */ @Aspect // 设置该类为切面类 @Slf4j @Component // 将切面类注入进Spring中 public class LoggerAspact { /** * 设置切点拦截 * 使用注解@Pointcut 完成 如:@Pointcut(public * com.xxx.xxx.*.*(..)) * 规则:修饰符(可以不写,,但不能用*)+ 返回值类型 + 哪些包下的类 + 哪些方法 + 方法参数 * "*" 代表不限 * ".." 代表参数不限 * @Pointcut(value = "execution(* com.yang.aop.controller.*.*(..))") * */ @Pointcut(value = "execution(* com.yang.aop.controller.*.*(..))") // 设置切点集合 public void myPointcut(){ } /** * 定义通知,也就是切点拦截后执行的动作 * 利用通知的5种类型注解: * @Before * @After * @AfterReturning * @AfterThrowing * @Around * 来完成某个切点的增强动作 * 如@before("myPointcut()"),myPointcut()为第二部定义的切点 */ /** * * @param pjp * @return */ @SneakyThrows @Around("myPointcut()") public Object myLogger(ProceedingJoinPoint pjp){ String className = pjp.getClass().getName().toString(); String methodName = pjp.getSignature().getName(); Object[] array = pjp.getArgs(); ObjectMapper objectMapper = new ObjectMapper(); log.info("调用前:类:"+className + "方法:"+methodName + "参数为:" + objectMapper.writeValueAsString(array)); Object proceed = pjp.proceed(); log.info("调用后:类:"+className + "方法:"+methodName + "参数为:" + objectMapper.writeValueAsString(proceed)); return proceed; } }
package com.yang.aop.aspact; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Component @Slf4j @Aspect public class ServiceAspact { @Pointcut(value = "execution( * com.yang.aop.service.impl.*ServiceImpl.A(..))") public void serviceAspact(){ } @Around("serviceAspact()") public Object doBefore(ProceedingJoinPoint joinPoint) throws Throwable { log.info("拦截Service层的切面"); Object proceed = null; try { // 执行拦截的目标 (执行ServiceImpl里边的方法) proceed = joinPoint.proceed(); // proceed 为执行拦截目标方法后的返回值! // 抛异常,不抓会直接抛异常,抓异常处理后,proceed 返回值为 null }catch (Exception e){ e.printStackTrace(); } log.info("拦截Service层的切面,执行完成"); // joinPoint.getArgs(); 可以获取拦截目标的参数 Object[] args = joinPoint.getArgs(); System.out.println(args); // 通过 joinPoint.proceed(); 可以执行拦截的目标! // TODO: 2022/2/21 切记, joinPoint.proceed();执行后的值 就是拦截目标的返回值 // TODO: 2022/2/21 切记,拦截以后一定要将执行后的返回值,return出去!!! return proceed; } }