内容目录
参考资料
https://juejin.cn/post/6999570632409088008
细说Spring——AOP详解(AOP概览)-CSDN博客
定义
AOP,即面向切面编程,其核心思想就是把业务分为核心业务和非核心业务两大部分。例如一个论坛系统,用户登录、发帖等等这是核心功能,而日志统计等等这些就是非核心功能。
即,提取公共的且非核心的业务,将其封装成一个类(例如上图的检测有效性等,还有使用较多的打印接口调用记录日志功能等)
术语
- Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
- Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
- Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
- Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
- Target(目标对象):织入 Advice 的目标对象.。
- Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
实现
引入依赖
在Spring Boot中,我们使用@AspectJ
注解开发AOP,首先需要在pom.xml
中引入如下依赖:
<!-- Spring Boot AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
常用注解
@Pointcut
定义切点@Before
前置通知@After
后置通知@AfterReturning
返回通知@AfterThrowing
异常通知-
@Around
环绕通知定义切面
import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Aspect @Component public class LogAspect { /** * 日志打印 */ private Logger logger = LoggerFactory.getLogger(this.getClass()); /** * 使用Pointcut给这个方法定义切点,即UserService中全部方法均为切点。<br> * 这里在这个log方法上面定义切点,然后就只需在下面的Before、After等等注解中填写这个切点方法"log()"即可设置好各个通知的切入位置。 * 其中: * <ul> * <li>execution:代表方法被执行时触发</li> * <li>*:代表任意返回值的方法</li> * <li>com.example.springbootaop.service.impl.UserServiceImpl:这个类的全限定名</li> * <li>(..):表示任意的参数</li> * </ul> */ @Pointcut("execution(* com.example.springbootaop.service.impl.UserServiceImpl.*(..))") public void log() { } /** * 前置通知:在被代理方法之前调用 */ @Before("log()") public void doBefore() { logger.warn("调用方法之前:"); logger.warn("接收到请求!"); } /** * 后置通知:在被代理方法之后调用 */ @After("log()") public void doAfter() { logger.warn("调用方法之后:"); logger.warn("打印请求内容完成!"); } /** * 返回通知:被代理方法正常返回之后调用 */ @AfterReturning("log()") public void doReturning() { logger.warn("方法正常返回之后:"); logger.warn("完成返回内容!"); } /** * 异常通知:被代理方法抛出异常时调用 */ @AfterThrowing("log()") public void doThrowing() { logger.error("方法抛出异常!"); } }
感觉实际使用中,直接通过类似` @Pointcut("execution( com.example.springbootaop.service.impl.UserServiceImpl.(..))") `的方法去定义切点位置很不灵活
因此考虑与注解结合,然后用注解去控制切口位置
通过注解去控制AOP
自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {
}
这个注解没有实现具体功能,仅仅是作为切面的定位作用
- `@Target(ElementType.METHOD)`指定这个注解只能用在方法上。
- `@Retention(RetentionPolicy.RUNTIME)`表示这个注解在运行时保留,这样AOP框架在运行时能够通过反射读取这个注解。
切面
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Pointcut("@annotation(com.hellomeme.v2.common.Annotation.LogExecution)")
public void log() {
}
@Before("log()")
public void doBefore() {
logger.warn("调用方法之前:");
logger.warn("接收到请求!");
}
核心代码:@Pointcut("@annotation(com.hellomeme.v2.common.Annotation.LogExecution)")
表示所有标有@LogExecution
注解的方法都会被匹配。
@annotation(xxx)中的参数是子类定义注解类的位置
实现
随后只需要在方法类上面打上一开始的注解,便可以实现这个功能。