Spring AOP
- 1.什么是AOP?
- 1.1引入AOP依赖
- 1.2编写AOP程序
- 2.Spring AOP核⼼概念
- 2.1 切点(Pointcut)
- 2.2连接点(Join Point)
- 2.3通知(Advice)
- 2.4 切⾯(Aspect)
- 3.通知类型
- 3.1顺序
- 3.2切⾯优先级 @Order
- 3.3 ⾃定义注解 @MyAspect
- 4. Spring AOP 原理
- 5 动态代理怎么实现
- 5.1 JDK 动态代理类实现步骤
- 定义JDK动态代理类
- InvocationHandler
- Proxy
- 5.2CGLIB 动态代理类实现步骤
- 6.总结
1.什么是AOP?
AOP是⾯向切⾯编程切⾯。
什么是⾯向特定⽅法编程呢? ⽐如 登录校验拦截器, 就是对"登录校验"这类问题的统⼀处理. 所以, 拦截器也是AOP的⼀种应⽤. AOP是⼀种思想, 拦截器是AOP思想的⼀种实现. AOP是⼀种思想, 是对某⼀类事情的集中处理
1.1引入AOP依赖
在pom.xml⽂件中添加配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
1.2编写AOP程序
记录Controller中每个⽅法的执⾏时间
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class TimeAspect {
/**
* 记录⽅法耗时
*/
@Around("execution(* com.example.demo.controller.*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
//记录⽅法执⾏开始时间
long begin = System.currentTimeMillis();
//执⾏原始⽅法
Object result = pjp.proceed();
//记录⽅法执⾏结束时间
long end = System.currentTimeMillis();
//记录⽅法执⾏耗时
log.info(pjp.getSignature() + "执⾏耗时: {}ms", end - begin);
return result;
}
结论:
1.@Aspect: 标识这是⼀个切面类
2. @Around: 环绕通知, 在⽬标⽅法的前后都会被执⾏. 后⾯的表达式表⽰对哪些⽅法进⾏增强.
3. ProceedingJoinPoint.proceed() 让原始⽅法执⾏
2.Spring AOP核⼼概念
2.1 切点(Pointcut)
切点(Pointcut), 也称之为"切⼊点"
Pointcut 的作⽤就是提供⼀组规则, 告诉程序对哪些⽅法来进⾏功能增强.
2.2连接点(Join Point)
满⾜切点表达式规则的⽅法, 就是连接点. 也就是可以被AOP控制的⽅法
以⼊⻔程序举例, 所有 com.example.demo.controller 路径下的⽅法, 都是连接点
2.3通知(Advice)
通知就是具体要做的⼯作, 指哪些重复的逻辑,也就是共性功能(最终体现为⼀个⽅法)
⽐如上述程序中记录业务⽅法的耗时时间, 就是通知.
2.4 切⾯(Aspect)
切⾯(Aspect) = 切点(Pointcut) + 通知(Advice)
通过切⾯就能够描述当前AOP程序需要针对于哪些⽅法, 在什么时候执⾏什么样的操作.
3.通知类型
3.1顺序
Spring中AOP的通知类型有以下⼏种:
• @Around: 环绕通知, 此注解标注的通知⽅法在⽬标⽅法前, 后都被执⾏
• @Before: 前置通知, 此注解标注的通知⽅法在⽬标⽅法前被执⾏
• @After: 后置通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, ⽆论是否有异常都会执⾏
• @AfterReturning: 返回后通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, 有异常不会执⾏
• @AfterThrowing: 异常后通知, 此注解标注的通知⽅法发⽣异常后执⾏
程序正常运⾏的情况下, @AfterThrowing 标识的通知⽅法不会执⾏
@Around 标识的通知⽅法包含两部分, ⼀个"前置逻辑", ⼀个"后置逻辑".其中"前置逻辑" 会先于 @Before 标识的通知⽅法执⾏, “后置逻辑” 会晚于 @After 标识的通知⽅法执⾏
@Slf4j
@Aspect
@Component
public class AspectDemo {
//定义切点(公共的切点表达式)
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
//...代码省略
}
//后置通知
@After("pt()")
public void doAfter() {
//...代码省略
}
//返回后通知
@AfterReturning("pt()")
public void doAfterReturning() {
//...代码省略
}
//抛出异常后通知
@AfterThrowing("pt()")
public void doAfterThrowing() {
//...代码省略
}
//添加环绕通知
@Around("pt()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//...代码省略
}
}
当切点定义使⽤private修饰时, 仅能在当前切⾯类中使⽤, 当其他切⾯类也要使⽤当前切点定义时, 就需要把private改为public. 引⽤⽅式为: 全限定类名.⽅法名()
@Slf4j
@Aspect
@Component
public class AspectDemo2 {
//前置通知
@Before("com.example.demo.aspect.AspectDemo.pt()")
public void doBefore() {
log.info("执⾏ AspectDemo2 -> Before ⽅法");
}
3.2切⾯优先级 @Order
@Before 通知:数字越⼩先执⾏
@After 通知:数字越⼤先执⾏
@Order 控制切⾯的优先级, 先执⾏优先级较⾼的切⾯, 再执⾏优先级较低的切⾯, 最终执⾏⽬标⽅法.
3.3 ⾃定义注解 @MyAspect
import java.lang.annotation.ElementType;
2 import java.lang.annotation.Retention;
3 import java.lang.annotation.RetentionPolicy;
4 import java.lang.annotation.Target;
5
6 @Target(ElementType.METHOD)
7 @Retention(RetentionPolicy.RUNTIME)
8 public @interface MyAspect {
9
10 }
1.@Target 标识了 Annotation 所修饰的对象范围, 即该注解可以⽤在什么地⽅.
常⽤取值:
ElementType.TYPE: ⽤于描述类、接⼝(包括注解类型) 或enum声明
ElementType.METHOD: 描述⽅法
ElementType.PARAMETER: 描述参数
ElementType.TYPE_USE: 可以标注任意类型
2. @Retention 指Annotation被保留的时间⻓短, 标明注解的⽣命周期
4. Spring AOP 原理
Spring AOP 是基于动态代理来实现AOP的
⽣活中的代理
• 艺⼈经纪⼈: ⼴告商找艺⼈拍⼴告, 需要经过经纪⼈,由经纪⼈来和艺⼈进⾏沟通.
• 房屋中介: 房屋进⾏租赁时, 卖⽅会把房屋授权给中介, 由中介来代理看房, 房屋咨询等服务.
实现
1.Subject: 业务接⼝类. 可以是抽象类或者接⼝(不⼀定有)
2. RealSubject: 业务实现类. 具体的业务执⾏, 也就是被代理对象.
3. Proxy: 代理类. RealSubject的代理.
5 动态代理怎么实现
1. JDK动态代理
2. CGLIB动态代理
5.1 JDK 动态代理类实现步骤
- 定义⼀个接⼝及其实现类(静态代理中的 HouseSubject 和 RealHouseSubject )
- ⾃定义 InvocationHandler 并重写 invoke ⽅法,在 invoke ⽅法中我们会调⽤⽬标⽅
法(被代理类的⽅法)并⾃定义⼀些处理逻辑 - 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[]
interfaces,InvocationHandler h) ⽅法创建代理对象
定义JDK动态代理类
实现 InvocationHandler 接⼝
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocationHandler implements InvocationHandler {
//⽬标对象即就是被代理对象
private Object target;
public JDKInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Thro
// 代理增强内容
System.out.println("我是中介, 开始代理");
//通过反射调⽤被代理类的⽅法
Object retVal = method.invoke(target, args);
//代理增强内容
System.out.println("我是中介, 代理结束");
return retVal;
}
}
InvocationHandler
InvocationHandler 接⼝是Java动态代理的关键接⼝之⼀, 它定义了⼀个单⼀⽅法 invoke() , ⽤于处理被代理对象的⽅法调⽤.
public interface InvocationHandler {
/**
* 参数说明
* proxy:代理对象
* method:代理对象需要实现的⽅法,即其中需要重写的⽅法
* args:method所对应⽅法的参数
*/
```bash
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
通过实现 InvocationHandler 接⼝, 可以对被代理对象的⽅法进⾏功能增强.
Proxy
Proxy 类中使⽤频率最⾼的⽅法是: newProxyInstance() , 这个⽅法主要⽤来⽣成⼀个代理对象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,InvocationHandler h)
throws IllegalArgumentException
{
//...代码省略
}
5.2CGLIB 动态代理类实现步骤
- 定义⼀个类(被代理类)
- ⾃定义 MethodInterceptor 并重写 intercept ⽅法, intercept ⽤于增强⽬标⽅
法,和 JDK 动态代理中的 invoke ⽅法类似 - 通过 Enhancer 类的 create()创建代理类
6.总结
- AOP是⼀种思想, 是对某⼀类事情的集中处理. Spring框架实现了AOP, 称之为SpringAOP
- Spring AOP常⻅实现⽅式有两种: 1. 基于注解@Aspect来实现 2. 基于⾃定义注解来实现, 还有⼀些更原始的⽅式,⽐如基于代理, 基于xml配置的⽅式, 但⽬标⽐较少⻅
- Spring AOP 是基于动态代理实现的, 有两种⽅式: 1. 基本JDK动态代理实现 2. 基于CGLIB动态代理实现. 运⾏时使⽤哪种⽅式与项⽬配置和代理的对象有关.
- 代理类只能用CGLIB,代理接口,CGLIB和JDK都可以。SpringBoot2.x以后默认使用CGLIB代理。用
spring.aop.proxy-target-class=false
来设置JDK代理。