Ⅰ. AOP 概述
什么是AOP?
- Aspect Oriented Programming(面向切面编程)
什么是面向切面编程呢?
- 切面就是指某一类特定问题 ,所以 AOP 也可以理解为面向特定方法编程。
什么是面向特定方法编程呢?
比如上个章节学习的 "登录校验",就是一类特定问题。登录校验拦截器,就是对 "登录校验" 这类问题的统一处理。所以,拦截器也是 AOP 的一种应用。AOP 是一种思想,拦截器是 AOP 思想的一种实现。Spring 框架实现了这种思想,提供了拦截器技术的相关接口。
同样的,统一数据返回格式和统一异常处理,也是 AOP 思想的一种实现。
简单来说:AOP 是一种思想,是对某一类事情的集中处理 。
而 Spring AOP 是 Spring 框架提供的基于代理的面向切面编程实现,用于在不修改源代码的前提下对程序进行增强 。比如说统一管理日志打印、权限控制、事务管理等重复、与业务无关的逻辑。
Ⅱ. Spring AOP 快速入门
需求:统计图书系统各个接口方法的执行时间。
1. 引入依赖
首先在 pom.xml 文件中添加配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>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;
}
}-
@Aspect:标识这是一个切面类 -
@Around:环绕通知,在目标方法的前后都会被执行,后面的表达式表示对哪些方法进行增强。 -
ProceedingJoinPoint.proceed():执行原始方法,也就是指定要运行的那些方法
💥注意事项:
如果没有返回
pjp.proceed()的结果对象result,则会导致程序死循环或功能异常 。在使用
@Around时,必须调用pjp.proceed()方法 。这里是为了举例子,通常不能直接拦截所有
controller,否则浏览器访问的时候可能携带了其他访问资源,而后端因为拦截所有资源路径而导致不断重定向尝试,最后缓存炸了。
整个代码划分为三部分:

通过上面的程序,也可以感受到 AOP 面向切面编程的一些优势:
-
代码无侵入 :不修改原始的业务方法,就可以对原始的业务方法进行了功能的增强或者是功能的改变
-
减少了重复代码
-
提高开发效率
-
维护方便
Ⅲ. Spring AOP 详解
一、Spring AOP 核心概念
| 概念 | 含义 |
|---|---|
| 切面(Aspect) | 横切逻辑的模块(一个类 ),切面 = 切点 + 通知 + 连接点 |
| 通知(Advice) | 就是具体要做的工作 ,比如上面的 "记录代码耗时" |
| 切点(Pointcut) | 指定哪些方法要被拦截 比如 execution(* com.example.demo.controller.*.*(..)) 就是切点表达式 |
| 连接点(JoinPoint) | 满足切点表达式规则的方法,就是连接点 比如切点表达式指定的 com.example.demo.controller 路径下的方法,都是连接点 |

切点和连接点的关系:
连接点是满足切点表达式的元素
切点可以看做是保存了众多连接点的一个集合
比如:
切点表达式:学校全体教师
连接点:张三、李四等各个老师
二、通知类型
| 注解 | 含义 |
|---|---|
| @Before | 在目标方法执行前 执行 |
| @After | 在目标方法执行后 (有异常也会执行) |
| @AfterReturning | 在方法返回后执行 (有异常不会执行) |
| @AfterThrowing | 在方法抛出异常时执行 |
| @Around | 环绕通知,可控制方法执行前后时机,最强大 |
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class AspectDemo {
// 前置通知
@Before("execution(* com.example.demo.controller.*.*(..))")
public void doBefore() {
log.info("执行 Before 方法");
}
// 后置通知
@After("execution(* com.example.demo.controller.*.*(..))")
public void doAfter() {
log.info("执行 After 方法");
}
// 返回后通知
@AfterReturning("execution(* com.example.demo.controller.*.*(..))")
public void doAfterReturning() {
log.info("执行 AfterReturning 方法");
}
// 抛出异常后通知
@AfterThrowing("execution(* com.example.demo.controller.*.*(..))")
public void doAfterThrowing() {
log.info("执行 doAfterThrowing 方法");
}
// 添加环绕通知
@Around("execution(* com.example.demo.controller.*.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Around 方法开始执行");
Object result = joinPoint.proceed();
log.info("Around 方法结束执行");
return result;
}
}写一些测试程序:
package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/test")
@RestController
public class TestController {
@RequestMapping("/t1")
public String t1() {
return "t1";
}
@RequestMapping("/t2")
public boolean t2() {
int a = 10 / 0;
return true;
}
}- 正常运行的情况:http://127.0.0.1:8080/test/t1
观察日志:

-
程序正常运行的情况下,
@AfterThrowing标识的通知方法不会执行 ! -
@Around标识的通知方法包含两部分,一个 "前置逻辑",一个 "后置逻辑"。其中 "前置逻辑" 先于@Before标识的通知方法执行 ,"后置逻辑" 晚于@After标识的通知方法执行 !

观察日志:

程序发生异常的情况下:
-
@AfterReturning标识的通知方法在出现异常时候不会执行 。 -
@Around环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会在执行了(因为原始方法调用出异常了)

💥注意事项:
-
@Around环绕通知需要调用ProceedingJoinPoint.proceed()来让原始方法执行 ,而其它通知类型不需要考虑目标方法的执行。 -
@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的。 -
一个切面类可以有多个切点。
三、@PointCut
上面代码存在一个问题,就是存在大量重复的切点表达式 execution(* com.example.demo.controller.*.*(..)),Spring 提供了 @PointCut 注解,把公共的切点表达式提取出来,需要用到时引用该切入点表达式即可 。
上述代码就可以修改为:
@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 方法"); } }
四、切面优先级 @Order
当我们在一个项目中,定义了多个切面类时,并且这些切面类的多个切入点都匹配到了同一个目标方法。当目标方法运行的时候,这些切面类中的通知方法都会执行,那么这几个通知方法的执行顺序是什么样的呢?
AspectDemo2:
@Component
public class AspectDemo2 {
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
log.info("执行 AspectDemo2 -> Before 方法");
}
//后置通知
@After("pt()")
public void doAfter() {
log.info("执行 AspectDemo2 -> After 方法");
}
}AspectDemo3:
@Component
public class AspectDemo3 {
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
log.info("执行 AspectDemo3 -> Before 方法");
}
//后置通知
@After("pt()")
public void doAfter() {
log.info("执行 AspectDemo3 -> After 方法");
}
}AspectDemo4:
@Component
public class AspectDemo4 {
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
log.info("执行 AspectDemo4 -> Before 方法");
}
//后置通知
@After("pt()")
public void doAfter() {
log.info("执行 AspectDemo4 -> After 方法");
}
}运行程序,访问接口:http://127.0.0.1:8080/test/t1
观察日志:

通过上述程序的运行结果,可以看出:
存在多个切面类时,默认按照切面类的类名字母排序 :
-
@Before通知:字母排名靠前的先执行 -
@After通知:字母排名靠前的后执行
但这种方式不方便管理,我们的类名更多还是具备一定含义的。
所以 Spring 提供了一个新的注解,控制这些切面通知的执行顺序 :@Order
@Aspect
@Component
@Order(2)
public class AspectDemo2 {
//...代码省略
}
@Aspect
@Component
@Order(1)
public class AspectDemo3 {
//...代码省略
}
@Aspect
@Component
@Order(3)
public class AspectDemo4 {
//...代码省略
}重新运行程序,访问接口:http://127.0.0.1:8080/test/t1
观察日志:

@Order 注解标识的切面类,执行顺序如下:
-
@Before通知:数字越小先执行 -
@After通知:数字越大先执行
总结: @Order控制切面的优先级,先执行优先级较高的切面,再执行优先级较低的切面,最终执行目标方法 。
五、切点表达式
切点表达式常见有两种表达方式
-
execution():根据方法的签名来匹配 -
@annotation():根据注解匹配
1. execution表达式
execution() 是最常用的切点表达式,用来匹配方法,语法为:
execution(<访问修饰符> <返回类型> <包名.类名.方法(方法参数)> <异常>)其中【访问修饰符 】和【异常 】可以省略!

切点表达式支持通配符表达:
-
*:匹配任意字符, 只匹配一个元素 (返回类型、包、类名、方法、方法参数)-
包名使用
*表示任意包(一层包使用一个*) -
类名使用
*表示任意类 -
返回值使用
*表示任意返回值类型 -
方法名使用
*表示任意方法 -
参数使用
*表示一个任意类型的参数
-
-
..:匹配多个连续的任意符号 ,可以通配任意层级的包,或任意类型,任意个数的参数-
使用
..配置包名,标识此包以及此包下的所有子包 -
可以使用
..配置参数,任意个任意类型的参数
-
切点表达式示例:
TestController 下的 public 修饰,返回类型为 String 方法名为 t1 的无参方法:
execution(public String com.example.demo.controller.TestController.t1())省略访问修饰符:
execution(String com.example.demo.controller.TestController.t1())匹配所有返回类型:
execution(* com.example.demo.controller.TestController.t1())匹配 TestController 下的所有无参方法:
execution(* com.example.demo.controller.TestController.*())匹配 TestController 下的所有方法:
execution(* com.example.demo.controller.TestController.*(..))匹配 controller 包下所有的类的所有方法:
execution(* com.example.demo.controller.*.*(..))匹配所有包下面的 TestController:
execution(* com..TestController.*(..))匹配 com.example.demo 包下,子孙包下的所有类的所有方法:
execution(* com.example.demo..*(..))2. @annotation自定义注解
execution() 更适用有规则的,如果我们要匹配多个无规则的方法呢,比如:TestController 中的 t1() 和 UserController 中的 u1() 两个方法,这个时候使用 execution() 这种切点表达式来描述就不是很方便了.
我们可以借助自定义注解的方式以及另一种切点表达式 @annotation 来描述这一类的切点!
实现步骤:
-
自定义注解
-
使用
@annotation表达式来描述切点 -
在连接点的方法上添加自定义注解
首先提供测试用例:
@RequestMapping("/test")
@RestController
public class **TestController ** {
@RequestMapping("/t1")
public String t1() {
return "t1";
}
@RequestMapping("/t2")
public boolean t2() {
return true;
}
}@RequestMapping("/user")
@RestController
public class **UserController ** {
@RequestMapping("/u1")
public String u1(){
return "u1";
}
@RequestMapping("/u2")
public String u2(){
return "u2";
}
}① 自定义注解
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** MyAspect {
}@Target:标识Annotation所修饰的对象范围,即该注解可以用在什么地方。
常用取值:
ElementType.``TYPE:用于描述类、接口或 enum 声明
ElementType.``METHOD:描述方法
ElementType.``PARAMETER:描述参数
ElementType.``TYPE_USE:可以标注任意类型
@Retention:标明注解的生命周期
有三种取值:
RetentionPolicy.``SOURCE:表示注解仅存在于源代码中,编译成字节码后会被丢弃。这意味着在运行时无法获取到该注解的信息,只能在编译时使用。比如@SuppressWarnings,以及 lombok 提供的注解@Data、@Slf4j等。
RetentionPolicy.``CLASS:编译时注解。表示注解存在于源代码和字节码中,但在运行时会被丢弃。这意味着在编译时和字节码中可以通过反射获取到该注解的信息,但在实际运行时无法获取。通常用于一些框架和工具的注解。
RetentionPolicy.``RUNTIME:运行时注解。表示注解存在于源代码,字节码和运行时中。这意味着在编译时,字节码中和实际运行时都可以通过反射获取到该注解的信息。通常用于一些需要在运行时处理的注解,如 Spring 的@Controller、@ResponseBody等。
② 切面类
使用 @annotation 切点表达式定义切点,只对自定义注解 @MyAspect 生效:
@Slf4j
@Component
@Aspect
public class MyAspectDemo {
// 前置通知
@Before("@annotation(com.example.demo.aspect.MyAspect)")
public void before(){
log.info("MyAspect -> before ...");
}
// 后置通知
@After("@annotation(com.example.demo.aspect.MyAspect)")
public void after(){
log.info("MyAspect -> after ...");
}
}③ 添加自定义注解
在 TestController 中的 t1() 和 UserController 中的 u1() 这两个方法上添加自定义注解 @MyAspect,其他方法不添加:
@MyAspect
@RequestMapping("/t1")
public String t1() {
return "t1";
}@MyAspect
@RequestMapping("/u1")
public String u1(){
return "u1";
}运行程序,测试接口
观察日志:

可以看到切面通知被执行了。
继续测试:
http://127.0.0.1:8080/test/t2,切面通知未执行
http://127.0.0.1:8080/user/u1,切面通知执行
Spring AOP的实现方式(常见面试题)
基于注解@Aspect
基于自定义注解
基于Spring API (通过 xml 配置的方式,自从 SpringBoot 广泛使用之后,这种方法几乎看不到了,了解即可)
基于代理来实现 (更加久远的一种实现方式,写法笨重,不建议使用)
Ⅳ. Spring AOP 原理
一、代理模式
定义:通过一个代理对象 控制对目标对象 的访问,并在不修改目标类的前提下,对其功能进行增强或控制 。

生活中的代理
艺人经纪人:广告商找艺人拍广告,需要经过经纪人,由经纪人来和艺人进行沟通
房屋中介:房屋进行租赁时,卖方会把房屋授权给中介,由中介来代理看房,房屋咨询等服务
经销商:厂商不直接对外销售产品,由经销商负责代理销售
秘书/助理:合作伙伴找老板谈合作,需要先经过秘书/助理预约
代理模式的主要角色
-
Subject:业务接口类。可以是抽象类或者接口
-
RealSubject:业务实现类。具体的业务执行,也就是被代理对象
-
Proxy:代理类。RealSubject的代理
比如房屋租赁:
Subject:就是提前定义了房东做的事情,交给中介代理,也是中介要做的事情
RealSubject:房东
Proxy:中介
根据代理的创建时期 ,代理模式分为静态代理和动态代理。
-
静态代理 :由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的
.class文件就已经存在了。 -
动态代理 :在程序运行时,运用反射机制动态创建而成。
1. 静态代理
静态代理:在程序运行前,代理类的 .class 文件就已经存在了。
即在出租房子之前,中介已经做好了相关的工作,就等租户来租房子了
-
定义接口(定义房东要做的事情,也是中介需要做的事情)
public interface HouseSubject { void rentHouse(); } -
实现接口(房东出租房子)
public class RealHouseSubject implements HouseSubject{ @Override public void rentHouse() { System.out.println("我是房东, 我出租房子"); } } -
代理(中介,帮房东出租房子)
public class HouseProxy implements HouseSubject{ // 将被代理对象声明为成员变量 private HouseSubject houseSubject; public HouseProxy(HouseSubject houseSubject) { this.houseSubject = houseSubject; } @Override public void rentHouse() { //开始代理 System.out.println("我是中介, 开始代理"); //代理房东出租房子 houseSubject.rentHouse(); //代理结束 System.out.println("我是中介, 代理结束"); } } -
使用
public class StaticMain { public static void main(String[] args) { HouseSubject subject = new RealHouseSubject(); // 创建代理类 HouseProxy proxy = new HouseProxy(subject); // 通过代理类访问目标方法 proxy.rentHouse(); } }
运行结果:

上面这个代理实现方式就是静态代理。
从上述程序可以看出,虽然静态代理也完成了对目标对象的代理,但是由于代码都写死了,对目标对象的每个方法的增强都是手动完成的,非常不灵活,所以日常开发几乎看不到静态代理的场景。
2. 动态代理
相比于静态代理来说,动态代理更加灵活。
我们不需要针对每个目标对象都单独创建一个代理对象,而是把这个创建代理对象的工作推迟到程序运行时由 JVM 来实现。
也就是说动态代理在程序运行时,根据需要动态创建生成 。
比如房屋中介,我不需要提前预测都有哪些业务,而是业务来了我再根据情况创建。
Java 对动态代理进行了实现,并给我们提供了一些 API,常见的实现方式有两种:
-
JDK动态代理 -
CGLIB动态代理
① JDK 动态代理
实现步骤:
-
定义一个接口及其实现类。
-
自定义
InvocationHandler并重写invoke方法,在invoke方法中我们会调用目标方法(被代理类的方法)并自定义一些处理逻辑。 -
通过
Proxy.newProxyInstance``(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法创建代理对象
实现 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 Throwable {
// 执行代理的事情
System.out.println("我是中介, 开始代理");
// 执行房东的事情
Object retVal = method.invoke(target, args);
// 执行代理的事情
System.out.println("我是中介, 代理结束");
return retVal;
}
}创建一个代理对象并使用:
public class DynamicMain {
public static void main(String[] args) {
HouseSubject target= new RealHouseSubject(); // 创建房东(得实现接口才行)
// 创建一个代理类:通过被代理类、被代理实现的接口、方法调用处理器来创建
HouseSubject proxy = (HouseSubject) **Proxy.newProxyInstance** (
target.getClass().getClassLoader(), // 类加载器
new Class[]{HouseSubject.class}, // 代理要实现的接口
new JDKInvocationHandler(target) // 调用处理器
);
proxy.rentHouse();
}
}顺序解析:
-
业务层 调用
proxy.rentHouse() -
JVM 调用动态生成的代理类
$Proxy... -
代理类把方法调用委托给
invoke() -
invoke()中:-
前置逻辑(中介的事)
-
调用
method.invoke(target, args)执行房东的真实方法 -
后置逻辑(中介的事)
-
-
返回结果给业务层
注意事项:
-
JDK 动态代理必须有接口
- 如果
RealHouseSubject没有实现HouseSubject,会报错IllegalArgumentException
- 如果
-
代理对象类型
HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(...);
-
返回类型是接口类型,不是目标类
-
不能直接强转成
RealHouseSubject
-
每次方法调用都会经过 invoke()
-
前置、后置、环绕逻辑全部在这里
-
适合做 事务、日志、权限、缓存等横切逻辑
-
-
性能问题
-
JDK 动态代理内部用反射调用
method.invoke(),有一点性能开销 -
如果没有接口,Spring 会选择 CGLIB 代理
-
② CGLIB 动态代理
JDK 动态代理有一个最致命的问题是只能代理实现了接口的类 。
有些场景下,我们的业务代码是直接实现的,并没有接口定义。为了解决这个问题,可以用 CGLIB 动态代理机制来解决。
CGLIB(Code Generation Library)是一个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理,很多知名的开源框架都使用到了 CGLIB。例如 Spring 中 AOP 模块:如果目标对象实现了接口,则默认采用 JDK动态代理,否则采用 CGLIB动态代理 。
注意:需要添加依赖
和 JDK 动态代理不同,CGLIB 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
实现步骤:
-
定义一个类(被代理类,即房东)
-
自定义
MethodInterceptor并重写intercept方法,intercept用于增强目标方法,和 JDK 动态代理中的invoke方法类似 -
通过
Enhancer类的create()创建代理类
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 1. 目标类
class RealHouseSubject {
public void rentHouse() {
System.out.println("房东租房子");
}
}
// 2. 代理拦截器
class CGLIBInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("我是中介, 开始代理");
// 执行父类方法(目标方法)
Object retVal = methodProxy.invokeSuper(o, args);
System.out.println("我是中介, 代理结束");
return retVal;
}
}
// 3. 测试
public class DynamicMain {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealHouseSubject.class); // 继承目标类
enhancer.setCallback(new CGLIBInterceptor()); // 设置拦截器
RealHouseSubject proxy = (RealHouseSubject) **enhancer.create()** ;
proxy.rentHouse();
}
}两者区别
| 特性 | JDK 动态代理 | CGLIB |
|---|---|---|
| 接口 | 必须 | 不需要 |
| 继承 | 无 | 通过继承目标类 |
| 方法限制 | 无 final 限制 | 不能代理 final 方法 |
| 生成代理 | Runtime Proxy Class | 子类继承生成 |
| Spring 默认 | 优先 | 仅无接口或 proxy-target-class=true |
二、 Spring AOP源码剖析(了解)
Spring AOP主要基于两种方式实现的:JDK及CGLIB的方式
Spring源码过于复杂,我们只摘出一些主要内容,以了解为主
Spring对于AOP的实现,基本上都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成
生成代理对象的逻辑在父类 AbstractAutoProxyCreator 中
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
//创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
/**
* 检查proxyTargetClass属性值,spring默认为false
* proxyTargetClass 检查接口是否对类代理, 而不是对接口代理
* 如果代理对象为类, 设置为true, 使用cglib代理
*/
if (!proxyFactory.isProxyTargetClass()) {
//是否有设置cglib代理
if (shouldProxyTargetClass(beanClass, beanName)) {
//设置proxyTargetClass为true,使用cglib代理
proxyFactory.setProxyTargetClass(true);
} else {
/**
* 如果beanClass实现了接口,且接口至少有一个自定义方法,则使用JDK代理
* 否则CGLIB代理(设置ProxyTargetClass为true )
* 即使我们配置了proxyTargetClass=false, 经过这里的一些判断还是可能会将其设为true
*/
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
// Use original ClassLoader if bean class not locally loaded in overriding class loader
ClassLoader classLoader = getProxyClassLoader();
if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
}
//从代理工厂中获取代理
return proxyFactory.getProxy(classLoader);
}代理工厂有一个重要的属性:proxyTargetClass,默认值为false.也可以通过程序设置
| proxyTargetClass | 目标对象 | 代理方式 |
|---|---|---|
| false | 实现了接口 | jdk代理 |
| false | 未实现接口(只有实现类) | cglib代理 |
| true | 实现了接口 | cglib代理 |
| true | 未实现接口(只有实现类) | cglib代理 |
可以通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 来设置
注意:
Spring Boot 2.X开始,默认使用CGLIB代理
可以通过配置项 spring.aop.proxy-target-class=false 来进行修改,设置默认为jdk代理
Spring Boot设置 @EnableAspectJAutoProxy 无效,因为Spring Boot默认使用AopAutoConfiguration进行装配
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
/**
* HouseProxy houseProxy = context.getBean(HouseProxy.class);
* 设置spring.aop.proxy-target-class=true cglib代理, 运行成功
* 设置spring.aop.proxy-target-class=false jdk代理, 运行失败, 不能代理类
* 因为 HouseProxy 是一个类, 而不是接口, 需要修改为
* HouseSubject houseProxy = (HouseSubject) context.getBean("realHouseSubject")
*
*/
HouseProxy houseProxy = context.getBean(HouseProxy.class);
//HouseSubject houseProxy = (HouseSubject) context.getBean("realHouseSubject");//正确运行
System.out.println(houseProxy.getClass().toString());
}
}使用context.getBean()需要添加注解,使HouseProxy,RealHouseSubject被Spring管理
测试AOP代理,需要把这些类交给AOP管理(自定义注解或使用@Aspect)
我看点进去看代理工厂的代码
public class ProxyFactory extends ProxyCreatorSupport {
//...代码省略
//获取代理
public Object getProxy(@Nullable ClassLoader classLoader) {
//分两步 先createAopProxy,后getProxy
return createAopProxy().getProxy(classLoader);
}
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
}
//...代码省略
}createAopProxy的实现在DefaultAopProxyFactory中
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
//...代码省略
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
/**
* 根据proxyTargetClass判断
* 如果目标类是接口, 使用JDK动态代理
* 否则使用cglib动态代理
*/
if (!NativeDetector.inNativeImage() &&
(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
//...代码省略
}接下来就是创建代理了
JDK动态代理
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
//...代码省略
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
return Proxy.newProxyInstance(determineClassLoader(classLoader), this.proxiedInterfaces, this);
}
//...代码省略
}CGLIB动态代理
class CglibAopProxy implements AopProxy, Serializable {
//...代码省略
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
//...代码省略
// Configure CGLIB Enhancer...
Enhancer enhancer = createEnhancer();
// Generate the proxy class and create a proxy instance.
return createProxyClassAndInstance(enhancer, callbacks);
}
//...代码省略
}