基础
概念
AOP,一般称为面向切面,作为面向对象OOP的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块之间的耦合度,提高了系统的可维护性。可用于权限认证,日志和事务处理.
相关注解
@Component
将当前类注入到Spring容器内。
@Aspect
表明当前类是一个切面类。
@Pointcut
切入点,PointCut(切入点)表达式有很多种,其中execution用于使用切面的连接点。
@Before
前置通知,在被切入方法执行之前执行。
@After
后置通知,在被切入方法执行之后执行。
@AfterRuturning
返回通知,在被切入方法返回结果之后执行。使用此注解的方法,可以使用returning = "XXX"返回被切入方法的返回值,XXX即为被切入方法的返回值。
@Around
环绕通知,围绕着被切入方法执行。参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。环绕通知一般单独使用,环绕通知可以替代上面的几种通知。
AOP demo使用
- 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.1</version>
</dependency>
2.新建控制器及路由方法
package com.example.springbootTest.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@RequestMapping("/test1")
public String test1() {
System.out.println("test1");
return "test1";
}
@RequestMapping("/test2")
public Object test2(String name){
System.out.println("test2");
return "test2";
}
}
3.新建切面类并根据业务配置切片类
package com.example.springbootTest.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
@Aspect
@Component
public class TestAop {
@Pointcut("execution(* *.*.*.*.TestController.test1(..))")
public void log(){};
@Before("log()")
public void deBefore(JoinPoint jp) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录请求内容
System.out.println("URL : " + request.getRequestURL().toString());
System.out.println("HTTP_METHOD : " + request.getMethod());
System.out.println("CLASS_METHOD : " + jp);
System.out.println("ARGS : " + Arrays.toString(jp.getArgs()));
System.out.println("前置通知:方法执行前执行...");
}
@After("log()")
public void deAfter(JoinPoint jp) throws Throwable {
System.out.println("后置通知:方法执行后执行...");
}
@AfterReturning(returning = "ret", pointcut = "log()")
public void deAfterReturning(String ret) throws Throwable{
System.out.println("返回通知:方法的返回值 : " + ret);
System.out.println("AfterReturning");
}
@Around("log()")
public Object deAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("around");
Object rtValue = null;
try {
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("通知类中的aroundAdvice方法执行了。。前置");
rtValue = pjp.proceed(args);//明确调用切入点方法(切入点方法)
System.out.println("通知类中的aroundAdvice方法执行了。。返回");
System.out.println("返回通知:"+rtValue);
} catch (Throwable e) {
System.out.println("通知类中的aroundAdvice方法执行了。。异常");
throw new RuntimeException(e);
} finally {
System.out.println("通知类中的aroundAdvice方法执行了。。后置");
}
}
}
PointCut表达式
execution
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)),修饰符模式为非必填
execution(public * *(..))
匹配所有目标类的public方法,第一个“”代表方法返回值类型,第二个“”代表方法名,而".."代表任意入参的方法;
execution(* *st2(..))
匹配目标类所有以st2为后缀的方法。第一个“”代表任意方法返回类型,而“To”代表任意以To结尾的方法名。
execution(* ....TestController.*(..))
匹配TestController接口的所有方法,第一个“*”代表任意返回类型,“com.taotao.Waiter.”代表Waiter接口中的所有方法。
within
是用来指定类型的,指定类型中的所有方法将被拦截是用来指定类型的,指定类型中的所有方法将被拦截
within(com.example.springbootTest.service.impl.TestServiceImpl) 匹配TestServiceImpl类对应对象的所有方法调用,并且只能是TestServiceImpl对象,不能是它的子对象
within(com.demo…*)匹配com.demo包及其子包下面的所有类的所有方法的外部调用
this
SpringAOP是基于代理的,this就代表代理对象,语法是this(type),当生成的代理对象可以转化为type指定的类型时表示匹配。
this(com.demo.service.IUserService)匹配生成的代理对象是IUserService类型的所有方法的外部调用
target
SpringAOP是基于代理的,target表示被代理的目标对象,当被代理的目标对象可以转换为指定的类型时则表示匹配。
target(com.demo.service.IUserService) 匹配所有被代理的目标对象能够转化成IuserService类型的所有方法的外部调用。(暂时想不到应用场景)
@annotation
带有相应标注的任意方法
@Pointcut("@annotation(com.example.springbootTest.annotation.Test))")
annotation 自定义注解
@Retention(保留)
RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
@Target
Annotation所修饰的对象范围,@Target(value = ElementType.METHOD)修饰范围为方法。
interface和@interface的区别
①、interface :声明了这是一个java 的接口
②、@interface : 是用来修饰 Annotation 的。这个关键字声明隐含了一个信息:它是继承了 java.lang.annotation.Annotation 接口。
切片获取annotation参数
MethodSignature methodSignature = (MethodSignature) jp.getSignature();
//获取方法注解的类型
Test annotation = methodSignature.getMethod().getAnnotation(Test.class);
System.out.println("annotation:"+annotation.userId());
参考资料:https://blog.csdn.net/XiongHuyi/article/details/118417301