AOP工作重点:
1.如何通过切点(Pointcut)和增强(Advice)定位到连接点(Jointpoint)上;
2.如何在增强(Advice)中编写切面的代码。
面向切面编程
几个重要概念搞清楚就行
- 执行点(Executepoint)-类初始化,方法调用。
- 连接点(Joinpoint)-执行点+方位的组合,可确定Joinpoint,比如类开始初始化前,类初始化后,方法调用前,方法调用后。
- 切点(Pointcut)-在众多执行点中,定位感兴趣的执行点。Executepoint相当于数据库表中的记录,而Pointcut相当于查询条件。
- 增强(Advice)-织入到目标类连接点上的一段程序代码。除了一段程序代码外,还拥有执行点的方位信息。
- 目标对象(Target)-增强逻辑的织入目标类
- 引介(Introduction)-一种特殊的增强(advice),它为类添加一些额外的属性和方法,动态为业务类添加其他接口的实现逻辑,让业务类成为这个接口的实现类。
- 代理(Proxy)-一个类被AOP织入后,产生一个结果类,它便是融合了原类和增强逻辑的代理类。
- 切面(Aspect)-切面由切点(Pointcut)和增强(Advice/Introduction)组成,既包括横切逻辑定义,也包括连接点定义。
引入依赖pom.xml
<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>
配置文件application.yml
server:
port: 8092
spring:
profiles:
active: dev
---
spring:
profiles: dev
logging:
level:
root: INFO
com.example: DEBUG
path: D:/logs/springboot-aop
控制器
package com.example.aop.controller;
import com.example.aop.anno.UserAccess;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@RequestMapping("/first")
public Object first() {
return "first controller";
}
@RequestMapping("/doError")
public Object error() {
return 1 / 0;
}
@RequestMapping("/second")
@UserAccess(desc = "second")
public Object second() {
int i = 1 / 0;
return "second controller";
}
}
定义切面
package com.example.aop.aspect;
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 LogAspect {
//切点
@Pointcut("execution(public * com.example.aop.controller.*.*(..))")
public void webLog() {
}
//连接点
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) {
System.out.println("2、方法before.....");
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("IP : " + request.getRemoteAddr());
System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
}
//环绕通知,环绕增强,相当于MethodInterceptor
@Around("webLog()")
public Object arround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("1、最先执行,方法环绕start.....");
try {
Object o = pjp.proceed();
System.out.println("3、方法环绕proceed,结果是 :" + o);
return o;
} catch (Throwable e) {
throw e;
}
}
//后置异常通知
@AfterThrowing("webLog()")
public void throwss(JoinPoint jp) {
System.out.println("5、方法异常时执行.....");
}
//后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
@After("webLog()")
public void after(JoinPoint jp) {
System.out.println("4、方法最后执行.....");
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) {
// 处理完请求,返回内容
System.out.println("5、方法的返回值 : " + ret);
}
}
请求和输出
请求地址:http://127.0.0.1:8092/first
打印信息:
1、最先执行,方法环绕start.....
2、方法before.....
URL : http://127.0.0.1:8092/first
HTTP_METHOD : GET
IP : 127.0.0.1
CLASS_METHOD : com.example.aop.controller.UserController.first
ARGS : []
3、方法环绕proceed,结果是 :first controller
4、方法最后执行.....
5、方法的返回值 : first controller
请求地址:http://127.0.0.1:8092/doError
打印信息:
1、最先执行,方法环绕start.....
2、方法before.....
URL : http://127.0.0.1:8092/doError
HTTP_METHOD : GET
IP : 127.0.0.1
CLASS_METHOD : com.example.aop.controller.UserController.error
ARGS : []
4、方法最后执行.....
5、方法异常时执行.....
2022-06-13 23:29:20.749 ERROR 8100 --- [nio-8092-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause
java.lang.ArithmeticException: / by zero
自定义注解
package com.example.aop.anno;
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 UserAccess {
String desc() default "无信息";
}
注解切面
package com.example.aop.aspect;
import com.example.aop.anno.UserAccess;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 切面
*/
@Component
@Aspect
public class UserAccessAspect {
//切点
@Pointcut(value = "@annotation(com.example.aop.anno.UserAccess)")
public void access() {
}
@Before("access()")
public void deBefore(JoinPoint joinPoint) {
System.out.println("second before");
}
@Around("@annotation(userAccess)")
public Object around(ProceedingJoinPoint pjp, UserAccess userAccess) {
//获取注解里的值
System.out.println("second around:" + userAccess.desc());
try {
return pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
}
请求地址:http://127.0.0.1:8092/second
打印信息:
1、最先执行,方法环绕start.....
2、方法before.....
URL : http://127.0.0.1:8092/second
HTTP_METHOD : GET
IP : 127.0.0.1
CLASS_METHOD : com.example.aop.controller.UserController.second
ARGS : []
second around:second
second before
3、方法环绕proceed,结果是 :null
4、方法最后执行.....
5、方法的返回值 : null
java.lang.ArithmeticException: / by zero
aop执行顺序
AOP切面的优先级Order属性
如果有两个切面,那么谁先谁后怎么判断?
那如果我们要指定切面的执行顺序呢?
可以使用@Order注解指定切面的优先级,值越小优先级越高。
如果不指定事务切面和缓存切面的 Order,它们的 Order 都将是默认值 —— Integer.MAX_VALUE,即最小优先级。如果两个切面 Order 相同,那么是按照切面的字母顺序来执行的切面。所以如果一个方法上同时存在 @Transactional(对应切面为 TransactionInterceptor)和 @Cacheable (对应切面为 CacheInterceptor),且如果没有指定事务切面和缓存切面的 Order,因为 CacheInterceptor 的字母顺序在 TransactionInterceptor 之前,所以先执行 @Cacheable 对应的切面,再执行 @Transactional 对应的切面。
那么如何指定缓存切面或者事务切面的 Order ?
使用注解:
@EnableCaching(order = 1)
@EnableTransactionManagement(order = 2)