aop作用
在开发中我们经常使用oop这种纵向结构来开发,但是却会出现一些横切的功能。譬如,日志记录的功能,我们需要在每个方法执行的详细信息通过日志记录,但是我们为每个方法去写日志,明显不合理。再如异常处理功能,我们需要在每个方法执行抛出的异常都专门处理都不合理。这样就需要AOP面向切面开发来处理横切问题
aop术语
- 通知(advice):
通知主要是定义切面是什么以及何时使用。
Before:在接合点之前执行通知。
AfterReturning:在接合点执行完成之后执行通知。
AfterThrowing:如果从接合点抛出了任何异常,都执行通知。
After:接合点执行完成之后,无论是否抛出了异常,都执行通知。
Around:在接合点周围执行通知,意思就是可能在接合点之前执行,也可能在接合点之后执行。 - 连接点(join point):
意思就是代码中的点,在这个点上开始玩切面。效果肯定是向应用程序中插入额外的逻辑。 - 切点(point cut):
用来选择需要执行一个或者多个连接点的表达式。 - 切面(aspect):
切面就是切点和通知的结合。
通知注解
注解 | 用途 |
---|---|
@Pointcut | 定义切入点 |
@Before | 目标方法执行之前执行 |
@After | 目标方法执行之后必定执行,无论是否报错 |
@AfterReturning | 目标方法有返回值且正常返回后执行 |
@AfterThrowing | 目标方法抛出异常后执行 |
@Around | 可以获取到目标方法的入参和返回值 |
maven导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定义切入点
利用execution表达式来给包、类、方法定义切入点。
// 解释:匹配cn.spy.service.impl.MyServiceImpl类下的所有方法
execution(* cn.spy.service.impl.MyServiceImpl.(..))
// 解释:匹配cn.spy.service.impl.MyServiceImpl类下的public void xx();方法。
execution(public void cn.spy.service.impl.MyServiceImpl.(..))
// 解释:匹配cn.spy.service.impl.MyServiceImpl类下第一个参数为String类型,无返回值的所有公共方法。
execution(public void cn.spy.service.impl.MyServiceImpl.*(String,..))
用法:
@Pointcut("execution(public String com.sunpeiyu.demoitem.controller.RedisController.testRequest())")
切入点对象JoinPoint
//获取目标方法的参数信息
Object[] obj = joinPoint.getArgs();
//AOP代理类的信息
joinPoint.getThis();
//代理的目标对象
joinPoint.getTarget();
//用的最多 通知的签名
Signature signature = joinPoint.getSignature();
//代理的是哪一个方法
System.out.println("代理的是哪一个方法"+signature.getName());
//AOP代理类的名字
System.out.println("AOP代理类的名字"+signature.getDeclaringTypeName());
//AOP代理类的类(class)信息
signature.getDeclaringType();
//获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//请求
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
//会话
HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
//响应
HttpServletResponse response = ((ServletRequestAttributes)requestAttributes).getResponse();
//获取请求参数
Enumeration<String> enumeration = request.getParameterNames();
Map<String,String> parameterMap = new HashMap<>();
while (enumeration.hasMoreElements()){
String parameter = enumeration.nextElement();
parameterMap.put(parameter,request.getParameter(parameter));
}
String str = JSON.toJSONString(parameterMap);
if(obj.length > 0) {
System.out.println("请求的参数信息为:"+str);
}
案例1,给指定方法返回的字符串编码
@Slf4j
@Aspect
@Component
public class ExcrypResultAspect {
@Pointcut("execution(public String com.sunpeiyu.demoitem.controller.RedisController.testRequest())")
public void cutHttpRequest() {
}
@Around(value = "cutHttpRequest()")
public Object encrypt(ProceedingJoinPoint joinPoint) throws Throwable {
String targetResult = (String) joinPoint.proceed();
log.info("=========================== targetResult = " + targetResult);
byte[] encodeArr = Base64.getEncoder().encode(targetResult.getBytes(StandardCharsets.UTF_8));
String base64Str = new String(encodeArr, StandardCharsets.UTF_8);
log.info("=========================== base64Str = " + base64Str);
return base64Str;
}
}
结果:
案例2,自定义注解,标识方法,将方法返回值编码
// 自定义注解
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptResult {
String rule() default "MD5";
}
// 切面实现
@Slf4j
@Aspect
@Component
public class ExcrypResultAspect {
@Pointcut("@annotation(com.sunpeiyu.demoitem.aspect.EncryptResult)")
public void cutHttpRequest() {
}
@Around(value = "cutHttpRequest()")
public Object encrypt(ProceedingJoinPoint joinPoint) throws Throwable {
String targetResult = (String) joinPoint.proceed();
log.info("=========================== targetResult = " + targetResult);
EncryptResult encryptResult = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(EncryptResult.class);
String rule = encryptResult.rule();
if (rule.equals("base64")) {
byte[] encodeArr = Base64.getEncoder().encode(targetResult.getBytes(StandardCharsets.UTF_8));
String base64Str = new String(encodeArr, StandardCharsets.UTF_8);
log.info("=========================== base64Str = " + base64Str);
return base64Str;
}
String md5Digest = MD5Utils.md5Digest(targetResult);
log.info("=========================== md5Digest = " + md5Digest);
return md5Digest;
}
}
// 测试
@EncryptResult
@GetMapping("/testRequest")
public String testRequest() {
return "success";
}
结果:
案例3,HttpServletRequest解密
// 请求体解码注解
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptResult {
}
// 切面方法
@Around(value = "@annotation(com.sunpeiyu.demoitem.aspect.DecryptResult)")
public Object decrypt() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
byte[] httpBody = HttpUtils.getHttpBody(request);
byte[] decodeArr = Base64.getDecoder().decode(httpBody);
return new String(decodeArr, StandardCharsets.UTF_8);
}
AOP切面执行顺序
实验
- controller
@Slf4j
@RequestMapping("/demo")
@RestController
public class DemoController {
@GetMapping("/doAopTest")
public String doAopTest() {
log.info("==============> doAopTest");
return "doAopTest";
}
}
- 切面
// ===== 切面1 =====
@Slf4j
@Order(1)
@Component
@Aspect
public class OneAspect {
@Pointcut("execution(* cn.sunpy.demo.controller.DemoController.doAopTest())")
public void cut() {
}
@Before("cut()")
public void beforeExe() {
log.info("==============> OneAspect.beforeExe");
}
@After("cut()")
public void afterExe() {
log.info("==============> OneAspect.afterExe");
}
@Around("cut()")
public Object aroundExe(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("==============> OneAspect.aroundExe first");
Object result = joinPoint.proceed();
log.info("==============> OneAspect.aroundExe final");
return result;
}
@AfterReturning("cut()")
public void afterReturnExe() {
log.info("==============> OneAspect.afterReturnExe first");
}
@AfterThrowing("cut()")
public void afterThrowing() {
log.info("==============> OneAspect.afterThrowing first");
}
}
// ===== 切面2 =====
@Slf4j
@Order(2)
@Component
@Aspect
public class TwoAspect {
@Pointcut("execution(* cn.sunpy.demo.controller.DemoController.doAopTest())")
public void cut() {
}
@Before("cut()")
public void beforeExe() {
log.info("==============> TwoAspect.beforeExe");
}
@After("cut()")
public void afterExe() {
log.info("==============> TwoAspect.afterExe");
}
@Around("cut()")
public Object aroundExe(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("==============> TwoAspect.aroundExe first");
Object result = joinPoint.proceed();
log.info("==============> TwoAspect.aroundExe final");
return result;
}
@AfterReturning("cut()")
public void afterReturnExe() {
log.info("==============> TwoAspect.afterReturnExe first");
}
@AfterThrowing("cut()")
public void afterThrowing() {
log.info("==============> TwoAspect.afterThrowing first");
}
}
AOP中异常处理
- Around处理异常
捕获目标方法的抛出的异常
@Aspect
@Component
public class ExceptionHandlingAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object handleExceptions(ProceedingJoinPoint joinPoint) throws Throwable {
try {
// 执行目标方法
return joinPoint.proceed();
} catch (Exception e) {
// 捕获异常并处理
System.err.println("Exception occurred: " + e.getMessage());
// 可以选择重新抛出异常、返回默认值或执行其他逻辑
throw e; // 重新抛出异常
}
}
}
AOP失效场景
1、同一个类中this调用被AOP拦截的方法
- 解决方法:
开启CGLIB代理(springboot默认开启)。
使用代理类去调用。
((MyService) AopContext.currentProxy()).anotherMethod();
2、目标方法为final类型
不要在被代理的类中定义 public final 方法,因为这些方法无法被代理。
3、代理类为final类型
CGLIB 代理会继承目标类,因此目标类不能是 final 类型,否则无法生成代理类。确保目标类不是 final 类型