# AOPTest **Repository Path**: javaprog/aoptest ## Basic Information - **Project Name**: AOPTest - **Description**: springboot整合Aspectj、aopdemo - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2024-09-21 - **Last Updated**: 2024-09-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Springboot整合Aspectj、Aspectj基本语法 >我们知道**AOP**(面向切面编程)给开发人员带来了**极大的便利性**,特别是有强迫症的猿们,项目中出现一堆**冗余**且与**实际业务无关**的代码,看一次想撕一次。 > >网上找不到比较全的相关文章,特此花时间整理下,方便你我他。 **下述实例源码传送门** https://gitee.com/peterven/aoptest ## 开箱即用 ### 核心依赖 ```xml org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-aop ``` ### 场景一 (接口方法执行前) ```java package com.gitee.peterven.controller.admin; @Slf4j @RestController public class AdminController { @GetMapping("admin/hello") public void hello() { log.info("I am admin method!"); } } ``` ```java @Slf4j @Aspect @Component public class MyAspect { @Before("execution(public * com.gitee.peterven.controller.admin.*.*(..))") public void beforeTest() throws Throwable { log.info("before invoke"); } } ``` **执行日志** ```shell before invoke I am admin method! ``` **语法分析** ```java @Before // 表示方法执行前织入该方法 execution // 表示根据匹配规则执行方法 public // 表示匹配的方法必须是public的 * // 表示匹配的方法返回值可以是任意类型 com.gitee.peterven.controller.admin.* // 表示匹配的方法是在该包下任意类中 *(..) // 表示匹配的方法名是任意的,且入参参数没有任何要求 ``` **注意** `@Before`是方法执行前调用的注解,所以在切入点方法中**不能够传入方法执行切入点对象**`ProceedingJoinPoint`,否则会抛异常 ```java @Slf4j @Aspect @Component public class MyAspect { @Before("execution(public * com.gitee.peterven.controller.admin.*.*(..))") public void beforeTest(ProceedingJoinPoint joinPoint) throws Throwable { joinPoint.proceed(); log.info("before invoke"); } } ``` ```java Error creating bean with name 'tomcatServletWebServerFactory' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration$EmbeddedTomcat.class]: BeanPostProcessor before instantiation of bean failed; nested exception is java.lang.IllegalArgumentException: ProceedingJoinPoint is only supported for around advice ``` ### 场景二 (接口方法前后执行) ```java package com.gitee.peterven.controller; @Slf4j @RestController public class BaseController { @GetMapping("base/hello") public User hello (Integer i) { try { log.info("basecontroller method start"); } catch (Exception e) { log.info("basecontroller method exception"); throw e; } finally { log.info("basecontroller method finally"); } return new User("gitee", 20); } } ``` ```java // controller包下任意方法 @Around("execution(public * com.gitee.peterven.controller.*.*(..))") public Object systemTimer(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); log.info("方法执行前置工作"); Object result = joinPoint.proceed(); log.info("方法共执行了{}毫秒", System.currentTimeMillis() - start); log.info("方法执行后置处理"); return result; // 记得把结果return出去 } ``` **执行日志** ``` 方法执行前置工作 basecontroller method start basecontroller method finally 方法共执行了9毫秒 方法执行后置处理 ``` **语法分析** ```java @Around // 表示方法执行前后织入该方法 execution // 表示根据匹配规则执行方法 public // 表示匹配的方法必须是public的 * // 表示匹配的方法返回值可以是任意类型 com.gitee.peterven.controller.* // 表示匹配的方法是在该包下任意类中 *(..) // 表示匹配的方法名是任意的,且入参参数没有任何要求 ``` ## 以下理论知识作为补充内容,不想看的可以略过 ### AOP是什么 与OOP对比,AOP是处理一些与业务无关的问题,比如日志记录、全局异常处理、事务管理等。这些横切性问题不会影响到主逻辑的实现,但是会散落到代码的各个部分,难以维护。AOP就是把这些问题和业务逻辑分开,达到与主业务逻辑解耦的目的。 #### 专业名词 * joint point * 连接点,在日常使用中,可以理解成是一个对象的方法。 * pointcut * 切点,用于匹配连接点的谓词表达式。 * Advice * 通知,指一个切面在特定连接点需要做的事。 * Aspect * 切面,它是多个切点的集合。 * weaving * 织入,将切面应用到目标对象的过程,这个过程可以是在**编译时**(例如使用 AspectJ 编译器)、**类加载时**、**运行时**来完成。Spring AOP是在运行时完成。 * Advisor * 这个概念是从 Spring 1.2的 AOP 支持中提出的,一个 Advisor 相当于一个小型的切面,不同的是它只有一个通知(Advice),Advisor 在事务管理里面会经常遇到。 * Target Object * 目标对象,被一个或者多个切面通知的对象。 * AOP proxy * AOP代理。 ### Aspectj是什么 实现了aop的一个框架 **Aspectj官网**(我感觉在官网看了个寂寞) http://www.eclipse.org/aspectj/ ``` AspectJ is a seamless aspect-oriented extension to the Javatm programming language Java platform compatible easy to learn and use AspectJ enables clean modularization of crosscutting concerns, such as error checking and handling, synchronization, context-sensitive behavior, performance optimizations, monitoring and logging, debugging support, and multi-object protocols ``` ### 类注解@Aspect ```java package org.aspectj.lang.annotation; /** * Aspect declaration * * @author Alexandre Vasseur */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Aspect { /** * @return the per clause expression, defaults to singleton aspect. * Valid values are "" (singleton), "perthis(...)", etc */ public String value() default ""; } ``` 切面声明的注解,一般是会自行创建一个类来管理aop,类上加上该注解,表示会使用Aspectj方式来实现aop。 ### 常用的方法注解 * @Before * 方法执行之前 * @After * 方法结束后执行 * @AfterReturning * 方法返回值之前执行 * @AfterThrowing * 抛出异常后执行 * @Around * 方法前后执行 **抛异常的情况** ```java package com.gitee.peterven.controller.admin; @Slf4j @RestController public class AdminController { @GetMapping("admin/hello") public String hello() { try { log.info("controller method start"); int i = 1 / 0; } catch (Exception e) { log.info("controller method exception"); throw e; } finally { log.info("controller method finally"); } return "haha"; } } ``` **执行结果** ``` @Around before invoke @Before.... controller method start controller method exception controller method finally @AfterThrowing.... @After.... ``` **若没有异常抛出,则结果如下** ``` @Around before invoke @Before.... controller method start controller method finally @AfterReturnning... @After.... result is:finish @Around after invoke ``` ### 切点表达式语法 `@Pointcut("xxxx")` ### 切点函数 | 类别 | 函数 | 说明 | 备注 | | ---------------- | -------------- | ------------------------------------------------------------ | ------------------------ | | 方法切点函数 | execution( ) | 表示满足某一匹配模式的所有目标类方法连接点。 | | | 方法切点函数 | @annotation( ) | 表示标注了特定注解的目标类方法连接点。 | | | 方法入参切点函数 | args( ) | 通过判别目标类方法运行时入参对象的类型定义指定连接点。 | 实验失败了,有时间再研究 | | 方法入参切点函数 | @args( ) | 通过判别目标类方法运行时入参对象的类是否标注特定注解来指定连接点。 | 实验失败了,有时间再研究 | | 目标类切点函数 | within( ) | 表示特定域下的所有连接点。 | | | 目标类切点函数 | @within( ) | 假如目标类按类型匹配于某个类A,且类A标注了特定注解,则目标类的所有连接点匹配这个切点。 | | | 目标类切点函数 | target( ) | 表示特定域下的所有连接点。可包含其子类或实现类。 | | | 目标类切点函数 | @target( ) | 假如目标类标注了注解,则目标类的所有连接点都匹配该切点。 | 实验失败了,有时间再研究 | | 代理类切点函数 | this( ) | 代理类按类型匹配于指定类,则被代理的目标类的所有连接点都匹配该切点。 | | #### execution >根据表达式来匹配对应的方法 **用法** * `@Pointcut("execution(public * com.gitee.peterven.controller.admin.AdminController.*(..))")` * `AdminController`类下任意方法 * `@Pointcut("execution(public * com.gitee.peterven.controller.admin.*.*(..))")` * `admin`包下任意类的任意方法 * `@Pointcut("execution(public * com.gitee.peterven.controller.admin.AdminController.h*(..))")` * `AdminController`类下,`h`开头的方法 #### @annotation >用于匹配加了指定注解的方法 **用法** ```java /** * 作用于类上的注解 * @author wangwenwen * @date 2021/6/7 14:43 * @version v1.0.0 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface AopMethodAnnotation { } ``` * `@Pointcut("@annotation(com.gitee.peterven.annotation.AopMethodAnnotation)")` * 用于匹配加了`AopMethodAnnotation`注解的方法 #### args和@args >都是用来匹配入参类型的 不过我试了下都是启动失败 先放着吧 #### within >根据指定类型来匹配方法,注意是去匹配**类**,不是去匹配方法 **用法** * `@Pointcut("within(com.gitee.peterven.controller.admin.*)")` * `admin`包下所有类的类型下对应的方法 * `@Pointcut("within(com.gitee.peterven.controller.admin.AdminController)")` * `AdminController`类下所有方法 * `@Pointcut("within(com.gitee.peterven.controller.admin.AdminController.*)")` * 这是无效的!!!请注意 #### @within >根据自定义的注解来匹配类,使该类下所有方法都进入aop **用法** ```java /** * 作用于类上的注解 * @author wangwenwen * @date 2021/6/7 14:43 * @version v1.0.0 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface AopClassAnnotation { } ``` * `@Pointcut("@within(com.gitee.peterven.annotation.AopClassAnnotation)")` * 被`AopClassAnnotation`注解标记的类下所有方法都会被匹配上 #### target >与within类似,但是该方式可以去匹配目标类及其子类,而within不行 ``` /** * 公共controller * @author wangwenwen * @date 2021/6/7 14:50 * @version v1.0.0 */ public class BaseController { } ``` **用法** * `@Pointcut("target(com.gitee.peterven.controller.BaseController)")` * 可以匹配到`BaseController`类及其子类下的方法 #### @target >不知道为什么 `@Pointcut("@target(com.gitee.peterven.annotation.AopClassAnnotation)")`这样会启动失败,有空再看看。 #### this >代理类按类型匹配于指定类(最终效果和within()差不多?) **用法** * `@Pointcut("this(com.gitee.peterven.controller.BaseController)")` * 可以匹配到`BaseController`类下所有方法 ### 参考文章 * https://blog.csdn.net/qq_33704186/article/details/88413438 * https://csdnnews.blog.csdn.net/article/details/103231804