aop,又叫面向切面编程,通俗理解就是,将那些与业务无关,却为业务模块所共同调用的逻辑代码封装起来,形成一个切面,减少重复代码,降低模块间的耦合度,方便后期操作和维护。
Spring AOP属于运行时的增强,而Aspect AOP属于编译时的增强。SpringAOP是基于代理(Proxying),而AspectAOP是基于字节码操作。如果切面比较少,两者差不多,如果切面太多,最好使用AspectAOP,它比SpringAOP快很多。
使用spring-boot-starter-aop启动器默认帮我们引入了对Aspectj的实现
@Aspect -- 作用是把当前类标识为一个切面供容器读取 @Pointcut -- (切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式 @Before -- 标识一个前置增强方法,相当于BeforeAdvice的功能 @AfterReturning -- 后置增强,相当于AfterReturningAdvice,方法退出时执行 @AfterThrowing -- 异常抛出增强,相当于ThrowsAdvice @After -- final增强,不管是抛出异常或者正常退出都会执行 @Around -- 环绕增强,相当于MethodInterceptor
@Aspect
– 作用是把当前类标识为一个切面供容器读取
@Aspect @Component @Slf4j public class LogAspect { }
@Pointcut -- (切入点)
:就是带有通知的连接点,在程序中主要体现为书写切入点表达式或者基于注解的切入点
基于注解的切入点
@Pointcut("@annotation(cn.zysheep.annotation.Log)") //@annotation(cn.zysheep.annotation.Log),为自定义注解 public void Pointcut() {}
基于切入点表达式
execution()
是最常用的切点函数
@Pointcut("execution(* cn.zysheep.springaop.service.impl..*.*(..))") public void Pointcut() {} 整个表达式可以分为四个部分: 1. 第一个*号:表示返回类型, *号表示所有的类型 2. 包名: 表示需要拦截的包名,后面的..表示当前包和当前包的所有子包,cn.zysheep.springaop.service.impl包、子孙包下所有类的方法。 3. 第二个*号: 表示类名,*号表示所有的类。 4. *(..):最后这个星号表示方法名, *号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数 // cn.zysheep.controller包中所有的类的所有方法切面 // @Pointcut("execution(public * cn.zysheep.controller.*.*(..))") // 只针对 UserController 类切面 // @Pointcut("execution(public * cn.zysheep.controller.UserController.*(..))") // 统一切点,对cn.zysheep及其子包中所有的类的所有方法切面 // @Pointcut("execution(* cn.zysheep.controller.*.*(..))")
个人认为基于注解的方式可以实现更加细粒度的操作,比如日志管理,只要自己声明一个自定义的注解@Log,可以在任意三层中定义到不同的方法中
标识一个前置增强方法,相当于BeforeAdvice的功能
//@Before: 前置通知 ,Pointcut()定义的切入点函数 @Before("Pointcut()") public void beforeMethod() { log.info("调用了前置通知"); }
后置增强,相当于AfterReturningAdvice
,方法退出时执行
//@AfterRunning: 返回通知 result为切入点返回内容 @AfterReturning(value="Pointcut()",returning="result") public void afterReturningMethod(JoinPoint joinPoint,Object result){ log.info("调用了返回通知,result :{}",result); }
异常抛出增强,相当于ThrowsAdvice
//@AfterThrowing: 异常通知 e为切入点执行异常的信息 @AfterThrowing(value="Pointcut()",throwing="e") public void doAfterThrowing(JoinPoint joinPoint, Exception e){ log.info("调用了异常通知 joinPoint:{},e :{}",joinPoint, e.getStackTrace()); }
final增强,不管是抛出异常或者正常退出都会执行
//@After: 后置通知 @After("Pointcut()") public void afterMethod(JoinPoint joinPoint){ //joinPoint: 当前的连接点,即执行的切入点 log.info("调用了后置通知,joinPoint:{}",joinPoint); }
环绕增强,相当于MethodInterceptor
//@Around: 环绕通知 @Around("Pointcut()") public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { log.info("around执行方法之前"); // 执行方法 Object object = proceedingJoinPoint.proceed(); // 执行时长(毫秒) long time = System.currentTimeMillis() - beginTime; log.info("around执行方法之后--返回值: " +object); return object; }
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.0</version> </parent> <dependencies> <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> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log { //模块名 String value() default ""; }
@Aspect @Component @Slf4j public class LogAspect { long beginTime; @Pointcut("@annotation(cn.zysheep.annotation.Log)") public void Pointcut() {} @Before("Pointcut()") public void beforeMethod() { log.info("调用了前置通知@Before"); beginTime = System.currentTimeMillis(); } @AfterReturning(value="Pointcut()",returning="result") public void afterReturningMethod(JoinPoint joinPoint, Object result){ log.info("调用了返回通知@AfterReturning,result :{}",result); } @AfterThrowing(value="Pointcut()",throwing="e") public void doAfterThrowing(JoinPoint joinPoint, Exception e){ log.info("调用了异常通知@AfterThrowing joinPoint:{},e :{}",joinPoint, e.getMessage()); } @After("Pointcut()") public void afterMethod(JoinPoint joinPoint){ //joinPoint: 当前的连接点,即执行的切入点 log.info("调用了后置通知@After,joinPoint:{}",joinPoint); } @Around("Pointcut()") public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { log.info("@Around执行方法之前"); // 执行方法 Object object = proceedingJoinPoint.proceed(); // 执行时长(毫秒) long time = System.currentTimeMillis() - beginTime; log.info("@Around执行方法之后--返回值: " +object); return object; } }
@RestController @Slf4j public class TestAopController { @GetMapping("/save") @Log("保存数据") public String save() { log.info("执行了controller中的save方法"); return "保存数据"; } }
@SpringBootApplication public class AOPApplication { public static void main(String[] args) { SpringApplication.run(AOPApplication.class,args); } }
执行流程:
controller
中的save方法如果切入点出现异常:
执行流程: