AOP
AOP全称是Aspect Oriented Programming 即面向切面编程,是对一类统一事务的集中处理
例如,我们的网页许多地方都需要进行登陆验证,这时就需要在很多地方添加重复的验证代码,而AOP可以集中配置需要登陆验证的地方,就可以简化我们的代码
除此以外还可以实现:
- 统一日志的记录
- 统一方法执行的时间记录
- 统一返回格式的设置
- 统一异常的处理
- 事务的开启和提交
springAOP
就像IoC是思想,而DI是实现,SpringAOP也是对AOP这种思想的实现
基本组成
- 切面:用来处理问题的一个类,包含切点和通知的相关方法
- 切点:用来设置拦截的规则
- 通知:程序拦截后进行的具体操作
- 连接点:触发AOP相关规则的点
其中对于通知,还有下面几种不同的分类:
- 前置通知: 使用@Before注解,在目标方法执行前执行
- 后置通知:使用@After注解,在目标方法返回或抛出异常后执行
- 返回通知:使用@AfterRetruning注解,在目标方法返回后执行
- 异常通知:使用@AfterThrowing注解,在目标方法抛出异常后执行
- 环绕通知:使用@Around注解,在包裹的方法执行前后执行后执行
也就是说,page1,2,3是需要验证是否登陆的页面,也就是连接点
验证是否登陆的这个方法就是切点
处理用户登陆问题是通知
而整个验证用户登陆的这个行为,称为切面
实现
首先在程序中加入SpringAOP的相关依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-bo
ot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
在程序中专门建一个类用来存储切面
@Aspect
@Component
public class 切面方法名{
@Poiontcut("execution(修饰符 返回类型 包名.类名.方法名(参数) 异常)")
public void 切点方法名(){}
}
其中Aspect注解代表此类是切面
而Component注解使该类在运行时启动
这里的切点方法并没有具体的实现,只是用来为通知方法标明执行的是那个切点
而@Pointcut注解后面的表达式则是标明拦截的规则
并且,表达式中还支持通配符:
- *匹配任意字符,只匹配一个元素
- …匹配任意字符,匹配多个元素
- +匹配指定类及其子类
也就是说:
修饰符
例子 | 表达 |
---|---|
public | 匹配所有公共方法 |
* | 匹配所有方法 |
返回值(不能省略)
例子 | 表达 |
---|---|
String | 匹配所有返回String的方法 |
* | 匹配所有方法 |
包名
例子 | 表达 |
---|---|
com.example.demo | 匹配com.exaple.demo这个包 |
com.*.demo | 匹配前面是com,结尾是demo的包 |
com.demo… | 匹配com下面的所有子包 |
类名
例子 | 表达 |
---|---|
User | 匹配User类 |
User* | 匹配以User开头的类 |
* | 匹配所有类 |
方法名(不能省略)
例子 | 表达 |
---|---|
getUser | 匹配getUser这个方法 |
*getUser | 匹配以getUser结尾的方法 |
* | 匹配所有方法 |
参数
例子 | 表达 |
---|---|
() | 匹配没有参数的方法 |
(int, float) | 匹配一个int,一个float参数的方法 |
(…) | 匹配任意类型的参数 |
例如:
定义一个匹配com.example.demo.controller包下,UserController的任意方法
@Component //随框架启动而启动
@Aspect //切面类
public class UserAspect {
/**
* 定义切点
*/
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){
}
}
实现通知
前置通知
/**
* 前置通知
*/
@Before("pointcut()")
public void beforeAdvice(){
System.out.println("执行前置通知");
}
后置通知
/**
* 后置通知
*/
@After("pointcut()")
public void afterAdvice(){
System.out.println("执行后置通知");
}
环绕通知
/**
* 环绕通知
*/
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint){
System.out.println("进入环绕通知");
Object object = null;
try{
//执行拦截方法
object = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("结束环绕通知");
return object;
}
返回通知
@AfterReturning("pointcut()")
public void returnAdvice(){
System.out.println("执行返回通知");
}
异常通知
@AfterThrowing("pointcut()")
public void throwAdvice(){
System.out.println("执行抛出异常通知");
}
把UserController类定义出来,验证一下这些通知是否执行
@RequestMapping("/user")
@RestController
public class UserController {
@RequestMapping("/hi")
public String sayHi(String name){
System.out.println("执行sayHi方法");
return "hi" + name;
}
@RequestMapping("/hello")
public String sayHello(){
System.out.println("执行sayHello方法");
return "hello world";
}
}
在访问127.0.0.1:8080/user/hi页面时,程序台就会打印如下信息
实现原理
springAOP只支持方法级别的拦截,这是因为其基于动态代理实现
其中分别用JDK Proxy(实现接口的类生成代理类),和CGLIB(没实现接口的类生产代理类)
把切面连接到目标对象的相关位置(连接点)的行为叫织入
其中一般在目标对象的这几个时期织入
- 编译器
- 类加载期
- 代码运行期(SpringAOP实现方式)