Spring Aop
1. 什么是 AOP?
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,用于将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。横切关注点是指那些在应用程序中多个模块或方法中重复出现的行为,比如日志记录、安全检查、事务管理等。
Spring AOP 是 Spring 框架中实现面向切面编程的模块,通过 AOP,可以在不改变核心业务代码的情况下动态地增强某些行为。
##2.AOP 的核心概念
在深入 Spring AOP 实现之前,先了解几个核心概念:
-
切面(Aspect :切面是横切关注点的模块化,包含特定功能的代码,如日志记录。
-
连接点(Join Point:程序执行过程中的某个点,例如方法调用或异常抛出。
-
通知(Advice) :通知是切面中的具体操作,它定义了切面的执行时机。常见的通知类型有:
- 前置通知(Before Advice):在目标方法执行前执行。 - 后置通知(After Advice):在目标方法执行后执行。 - 环绕通知(Around Advice):在目标方法执行前后都执行。 - 异常通知(After Throwing Advice):在目标方法抛出异常时执行。 - 返回通知(After Returning Advice):在目标方法成功执行后执行。
-
切点(Pointcut):切点定义了在哪些连接点上应用通知。
-
目标对象(Target Object):被增强的对象。
-
代理(Proxy):AOP 框架创建的对象,它是对目标对象的增强。
-
织入(Weaving):将切面应用到目标对象,创建代理对象的过程。
##3.AOP 应用场景
AOP 在开发中有着广泛的应用场景,常见的有:
- 日志记录:在方法调用前后自动记录日志,方便跟踪和调试。
- 性能监控:对某些关键方法进行性能监控,统计执行时间。
- 安全检查:在进入某些方法之前进行权限验证,确保用户有权访问该功能。
- 事务管理:通过 AOP 可以实现事务的自动开启、提交和回滚。
- 异常处理:在方法执行过程中统一捕获异常,减少重复代码。
##4. Spring AOP 实现
Spring AOP 通过代理模式实现,其中有两种代理方式:
- JDK 动态代理:适用于实现接口的类。
- CGLIB 代理:适用于没有实现接口的类,它通过生成目标类的子类来实现代理。
在实际应用中,Spring 会根据目标类的情况,自动选择合适的代理方式。
Spring Aop 配置方式
Spring AOP 提供了基于 XML 配置 和 注解 两种方式来定义切面。
1. 注解方式
最常见的 Spring AOP 实现方式是通过注解。下面是一个简单的例子,展示如何通过注解定义一个切面。
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBeforeMethod(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
@After("execution(* com.example.service.*.*(..))")
public void logAfterMethod(JoinPoint joinPoint) {
System.out.println("After method: " + joinPoint.getSignature().getName());
}
@Around("execution(* com.example.service.*.*(..))")
public Object logAroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Before around method: " + joinPoint.getSignature().getName());
Object result = joinPoint.proceed(); // 执行目标方法
System.out.println("After around method: " + joinPoint.getSignature().getName());
return result;
}
}
@Aspect
:声明一个类为切面类。
@Before
:前置通知,在目标方法执行之前调用。
@After
:后置通知,在目标方法执行之后调用,无论是否发生异常,都会执行。
@Around
:环绕通知,可以在目标方法执行前后进行操作。
@AfterThrowing :在目标方法抛出异常时执行。
@AfterReturning :在目标方法成功执行后执行的通知。
#####2. XML 配置方式
虽然注解方式更简洁,但在某些场景中,开发者可能更喜欢使用 XML 配置。以下是一个通过 XML 配置的示例:
<aop:config>
<aop:aspect ref="loggingAspect">
<aop:pointcut expression="execution(* com.example.service.*.*(..))" id="serviceMethods"/>
<aop:before pointcut-ref="serviceMethods" method="logBeforeMethod"/>
<aop:after pointcut-ref="serviceMethods" method="logAfterMethod"/>
<aop:around pointcut-ref="serviceMethods" method="logAroundMethod"/>
</aop:aspect>
</aop:config>
在这个配置中,aop:aspect
定义了切面,并且通过 aop:before
、aop:after
和 aop:around
指定了相应的通知。
####AOP 实战示例
为了更好地理解 AOP,假设我们有一个简单的用户服务类 UserService
,我们希望在每次用户登录时记录日志,并且在方法执行后监控执行时间。
@Service
public class UserService {
public User findUser(Long id) {
// 模拟查询用户
return new User(id, "Tom");
}
}
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.UserService.findUser(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
@AfterReturning(pointcut = "execution(* com.example.service.UserService.findUser(..))", returning = "result")
public void logAfterReturning(Object result) {
System.out.println("Method returned: " + result);
}
@AfterThrowing(pointcut = "execution(* com.example.service.UserService.findUser(..))", throwing = "error")
public void logAfterThrowing(Throwable error) {
System.out.println("Method threw exception: " + error);
}
}
//执行结果
Before method: findUser
Method returned: User{id=1, name='Tom'}
//如果出现异常:
Before method: findUser
Method threw exception: java.lang.NullPointerException
EL 表达式语法
1.执行某个包下的所有方法:
@Pointcut("execution(* com.example.service..*(..))")
匹配 com.example.service
包及其子包中的所有方法。
2.匹配某个注解:
@Pointcut("@annotation(com.example.annotation.Loggable)")
匹配带有 Loggable
注解的方法。
3.传递参数到通知方法:
你可以使用 args()
来匹配方法参数并将其传递给通知方法
@Pointcut("execution(* com.example.service.UserService.findUser(..)) && args(userId,..)")
public void findUserPointcut(Long userId) {}
4.访问返回值:
通过 @AfterReturning
注解,结合 returning
属性可以捕获方法的返回值。
@AfterReturning(pointcut = "execution(* com.example.service.UserService.*(..))", returning = "result")
public void logAfterReturning(Object result) {
System.out.println("返回结果:" + result);
}
5.匹配返回类型为 void
的所有方法:
@Pointcut("execution(void *..*.*(..))")
public void voidReturnMethods() {}
6.根据方法参数类型匹配:
@Pointcut("execution(* *(String, ..))")
public void methodsWithStringArg() {}
7.访问注解中的属性值:
@Pointcut("@annotation(com.example.MyAnnotation) && @annotation(myAnnotation)")
public void annotatedMethod(MyAnnotation myAnnotation) {}
@Before("annotatedMethod(myAnnotation)")
public void logAnnotation(MyAnnotation myAnnotation) {
System.out.println("注解的值是: " + myAnnotation.value());
}
####AOP 原理深度解析
Spring AOP 是基于代理模式实现的。其原理主要包括以下步骤:
-
解析配置:Spring 容器通过 XML 或注解形式解析出 AOP 配置。
-
创建代理对象
:当应用程序运行时,Spring 会为标注了 AOP 配置的对象创建代理对象。
- 如果目标类实现了接口,使用 JDK 动态代理。
- 如果没有实现接口,使用 CGLIB 创建代理类的子类。
-
方法拦截:代理对象拦截目标对象的方法调用,将调用委托给通知处理器执行。
-
通知执行:根据 AOP 配置的通知顺序(前置通知、后置通知、环绕通知等)执行相应的逻辑。
代理类生成:
-
JDK 动态代理:实现接口的类会通过
java.lang.reflect.Proxy
生成代理类,它将所有方法调用转InvocationHandler
。#####JDK 动态代理的主要特点是:
- 代理对象必须实现目标对象的接口。
- 在运行时动态生成代理类,而不是编译时生成
JDK 动态代理的具体代码示例
public interface UserService {
void addUser(String userName);
void deleteUser(String userName);
}
public class UserServiceImpl implements UserService {
@Override
public void addUser(String userName) {
System.out.println("Adding user: " + userName);
}
@Override
public void deleteUser(String userName) {
System.out.println("Deleting user: " + userName);
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//创建一个 InvocationHandler 实现类,用于定义代理类在执行目标对象方法时的增强逻辑:
public class UserServiceInvocationHandler implements InvocationHandler {
// 目标对象
private Object target;
// 构造方法传入目标对象
public UserServiceInvocationHandler(Object target) {
this.target = target;
}
// 代理对象执行的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法前增强处理
System.out.println("Before method: " + method.getName());
// 调用目标对象的方法
Object result = method.invoke(target, args);
// 方法后增强处理
System.out.println("After method: " + method.getName());
return result;
}
}
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
// 创建目标对象
UserService target = new UserServiceImpl();
// 创建 InvocationHandler 实例
UserServiceInvocationHandler handler = new UserServiceInvocationHandler(target);
// 创建代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), //目标对象的类加载器,用于加载代理类
target.getClass().getInterfaces(), //目标对象实现的接口,用于生成代理类时需要实现的接口
handler //InvocationHandler 实例,用于定义代理对象执行方法时的增强逻辑
);
// 调用代理对象的方法
proxy.addUser("Alice");
proxy.deleteUser("Bob");
}
}
//执行结果
Before method: addUser
Adding user: Alice
After method: addUser
Before method: deleteUser
Deleting user: Bob
After method: deleteUser
缺点:
- 只能代理接口:JDK 动态代理要求目标对象必须实现接口,如果没有接口,则不能使用 JDK 动态代理。
- 性能问题:由于 JDK 动态代理基于反射机制,每次方法调用时都需要通过反射执行目标对象的方法,性能较低。
既然说JDK动态代理是通过Proxy实现的,我就模仿一下实现。
public interface People {
void say();
void run();
int num();
}
//代理类以$命名
public class $Proxy extends Proxy implements People {
static Method say;
static Method run;
static {
try {
say = Man.class.getMethod("say");
run = Man.class.getMethod("run");
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
public $Proxy(InvocationHandler h) {
super(h);
}
@Override
public void say() {
try {
h.invoke(this,say,new Object[0]);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public void run() {
try {
h.invoke(this,run,new Object[0]);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public int num() {
Object object = null;
try {
Method num = Man.class.getMethod("num");
object = h.invoke( this,num, new Object[0]);
} catch (Throwable e) {
throw new RuntimeException(e);
}
return (int)object;
}
}
public static void main(String[] args) {
$Proxy proxy = new $Proxy(new InvocationHandler() {
@Override
public Object invoke(Object proxy,Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("before");
return method.invoke(new Man(),args);
}
});
proxy.say();
proxy.run();
proxy.num();
}
before
man say
before
man run
before
man num
JDK 动态代理的实现依赖于 Java 反射机制,它的原理包括以下几个步骤:
-
代理类生成:
- 代理对象并不是在编译时生成的,而是在运行时通过
Proxy
类动态生成的。 Proxy.newProxyInstance()
方法会根据目标对象实现的接口,在运行时生成一个代理类,该代理类实现了目标对象的接口。
- 代理对象并不是在编译时生成的,而是在运行时通过
-
代理类字节码生成:
- 在运行时,JDK 通过
Proxy
类生成的代理类字节码中,每一个接口方法都会被重写,重写后的方法内部会调用InvocationHandler
的invoke()
方法。
- 在运行时,JDK 通过
-
方法调用的动态分派:
- 代理类中的方法调用(如
addUser()
)会被转发给InvocationHandler
,并由其invoke()
方法统一处理。 - 在
invoke()
方法中,开发者可以通过Method
对象调用目标对象的实际方法,或根据需要选择不调用目标方法而直接返回其他结果。
- 代理类中的方法调用(如
-
反射调用目标方法:
-
通过
method.invoke(target, args)
,InvocationHandler
可以通过反射调用目标对象的实际方法。 -
反射允许在运行时动态调用方法,而不需要在编译时明确指定要调用的具体方法。
动态代理的调用流程图
-
调用
Proxy.newProxyInstance()
方法创建代理对象。 -
代理对象调用某个方法。
-
JDK 生成的代理类捕获方法调用,并将调用委托给
InvocationHandler
。 -
InvocationHandler
的invoke()
方法被调用。 -
invoke()
方法根据Method
对象,通过反射调用目标对象的方法。 -
方法执行结束后,返回结果给代理对象。
-
-
-
CGLIB 动态代理:对于没有实现接口的类,Spring 使用 CGLIB 库生成代理对象,实际上是生成目标类的子类并覆盖其方法.CGLIB 代理的实现基于 ASM(Java 的字节码操作库),它在运行时生成字节码,创建目标类的子类,然后重写目标类的方法,加入增强逻辑。
CGLIB 动态代理的具体代码示例
public class UserService {
public void addUser() {
System.out.println("Adding a user...");
}
public void deleteUser() {
System.out.println("Deleting a user...");
}
}
static class CustomMethodInterceptor implements MethodInterceptor{
private Object object;
public CustomMethodInterceptor(Object object){
this.object=object;
}
/**
Object obj:代理对象的实例,也就是生成的代理类的对象。在 CGLIB 动态代理中,代理类是目标类的子类,因此这个 obj 是代理类的实例。
Method method:代表目标类中被代理的方法的 java.lang.reflect.Method 对象。它封装了目标类中某个方法的元信息(如方法名、参数类型等),可以用来获取方法信息或通过反射来调用该方法。
Object[] args:目标方法的参数列表,传递给代理方法的实际参数。这个数组包含了代理方法在执行时传入的参数,方法执行时会使用这个参数列表来调用方法。
MethodProxy proxy:MethodProxy 是 CGLIB 提供的用于调用父类方法的对象,它比 Method 更高效,因为它是直接操作字节码生成的方法调用。proxy 可以用于调用代理类的父类(即目标类)的原始方法
**/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
//代理类是目标类的一个子类,代理类会继承目标类的方法,但是不会继承父类的方法
Object invoke = method.invoke(object, objects);
System.out.println("after");
return invoke;
}
}
public static void main(String[] args) {
UserService proxy = (UserService) Enhancer.create(UserService.class, new CustomMethodInterceptor(new UserService()));
proxy.addUser();
}
before
Adding a user...
after
CGLIB 的工作流程
- 字节码生成: CGLIB 使用 ASM 框架在运行时生成目标类的子类,代理类会继承目标类,并重写其非
final
的方法。对于每个被代理的方法,CGLIB 会通过代理类中的MethodInterceptor
拦截方法调用。 - 方法拦截: 当代理类的方法被调用时,CGLIB 会触发
MethodInterceptor
的intercept()
方法,该方法接收代理对象、被调用的方法、方法参数以及MethodProxy
对象。MethodProxy.invokeSuper()
:通过代理对象调用目标类的原始方法。Method
:代表被代理的方法,可以通过反射调用该方法。
- 代理类创建:
Enhancer
负责在运行时动态生成代理类,create()
方法返回生成的代理对象。代理类继承了目标类,并添加了增强逻辑
Method
和 MethodProxy
的区别
Method
(反射机制)
- 定义:
java.lang.reflect.Method
是 Java 反射 API 中的类,表示类或接口的方法。 - 功能:可以用于获取方法的信息(如方法名、参数类型等),也可以通过反射机制调用目标类的方法。
- 使用方式:通过
method.invoke(obj, args)
调用目标方法。 - 效率:由于
Method
是基于反射的,在调用方法时需要检查权限、类型转换等操作,因此性能较低。 - 调用原理:反射调用,动态解析方法和参数类型后执行。
- 适用场景:反射主要用于动态操作方法,适用于需要在运行时灵活调用目标对象的方法的场景。
Object result = method.invoke(obj, args);
MethodProxy
(CGLIB 字节码机制)
- 定义:
net.sf.cglib.proxy.MethodProxy
是 CGLIB 提供的类,用于更高效地调用父类(目标类)的原始方法。 - 功能:通过字节码操作直接调用目标类的原始方法,不需要通过反射来完成。这使得
MethodProxy
的调用比Method
更加高效。 - 使用方式:通过
proxy.invokeSuper(obj, args)
调用目标类的原始方法。 - 效率:
MethodProxy
直接使用字节码生成的方法调用机制,性能更高,避免了反射调用的开销。 - 调用原理:通过代理类生成的字节码直接访问目标类的方法,不经过反射。
- 适用场景:用于高性能需求的场景,例如大量代理方法调用时,通过
MethodProxy
可以获得更好的性能。
Object result = proxy.invokeSuper(obj, args);
Method
与 MethodProxy
的选择
- 使用
Method
:如果你只想简单地通过反射调用方法,可以使用Method
对象的invoke()
方法来调用目标对象的方法。但这种方式性能相对较低,适合不频繁的动态方法调用。 - 使用
MethodProxy
:如果你希望在大量的代理对象或频繁的代理方法调用中获得更高的性能,应该使用MethodProxy
的invokeSuper()
,因为它不经过反射,而是通过字节码生成直接访问目标类的方法。CGLIB 动态代理推荐使用MethodProxy
,因为它的调用方式效率更高。
public class Proxy extends Man {
private MethodInterceptor methodInterceptor;
public Proxy(MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}
static Method method1;
static Method method2;
static Method method3;
static MethodProxy methodProxy1;
static MethodProxy methodProxy2;
static MethodProxy methodProxy3;
static {
try {
method1 = Man.class.getMethod("say", null);
method2 = Man.class.getMethod("run", null);
method3 = Man.class.getMethod("test", null);
methodProxy1 = MethodProxy.create(Man.class, Proxy.class,"()v","say","saySuper");
methodProxy2 = MethodProxy.create(Man.class, Proxy.class,"()v","run","runSuper");
methodProxy3 = MethodProxy.create(Man.class, Proxy.class,"()v","test","testSuper");
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
public void testSuper(){
super.test();
}
public void saySuper() {
super.say();
}
public void runSuper() {
super.run();
}
@Override
public void test(){
try {
methodInterceptor.intercept(this, method3, null, methodProxy3);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public void say() {
try {
methodInterceptor.intercept(this, method1, null, methodProxy1);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public void run() {
try {
methodInterceptor.intercept(this, method2, null, methodProxy2);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
MethodInterceptor interceptor = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method: " + method.getName());
// 调用父类方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method: " + method.getName());
return result;
}
};
// 创建代理对象
Proxy proxy = new Proxy(interceptor);
// 调用代理方法
proxy.say(); // 输出:Before method: say, After method: say
proxy.run(); // 输出:Before method: run, After method: run
proxy.test(); // 输出:Before method: test, After method: test
JDK 动态代理与 CGLIB 代理的比较
比较点 | JDK 动态代理 | CGLIB 动态代理 |
---|---|---|
代理目标 | 必须实现接口 | 可以代理没有接口的类 |
性能 | 较低,因为每次方法调用都使用反射机制 | 较高,因为它直接生成目标类的子类 |
代理方式 | 基于接口 | 基于类的继承机制 |
适用场景 | 接口代理,适合服务层、DAO 层的代理 | 没有实现接口的目标类,适合工具类代理 |
使用场景 | Spring AOP 默认使用 | 如果没有接口,Spring 使用 CGLIB |
@Nullable
public Object proceed() throws Throwable {
// 判断是否已经到了拦截器链的最后一个
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
// 如果是最后一个拦截器,就直接调用目标方法(即你真正要执行的那个方法)
return this.invokeJoinpoint();
} else {
// 还没到最后,取出下一个拦截器
Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
// 如果这个拦截器带有动态匹配逻辑
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
// 动态检查一下,当前的方法和参数是否符合拦截条件
Class<?> targetClass = this.targetClass != null ? this.targetClass : this.method.getDeclaringClass();
// 如果符合条件,执行拦截器里的逻辑
return dm.matcher().matches(this.method, targetClass, this.arguments)
? dm.interceptor().invoke(this) // 如果匹配,调用拦截器的逻辑
: this.proceed(); // 如果不匹配,继续下一个拦截器
} else {
// 如果这个拦截器没有动态匹配,直接调用拦截器的逻辑
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
}
Spring AOP 核心类:
ProxyFactory
: 负责创建代理对象的工厂类。了解它如何通过不同的代理机制(如 JDK 动态代理和 CGLIB)生成代理对象。
AdvisedSupport
: 封装了代理配置、拦截器链等信息,核心的数据结构。
AopProxy
: 是创建代理的接口。常见实现是 JdkDynamicAopProxy
和 CglibAopProxy
,分别用于 JDK 动态代理和 CGLIB 代理。
MethodInterceptor
: 拦截器,用于在方法执行前后进行增强逻辑。
MethodInvocation
: 用于封装对目标方法的调用,proceed()
方法调用实际的目标方法或下一个拦截器。
首先看一下ProxyFactory的源码:
ProxyFactory的全类图:
//通过这个方法得到代理
public Object getProxy() {
return this.createAopProxy().getProxy();
}
//父类ProxyCreatorSuppot创建aop代理
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
this.activate();
}
//用于创建 AOP 代理对象,并根据目标对象是否实现了接口,选择使用 JDK 动态代理或 CGLIB 动态代理
return this.getAopProxyFactory().createAopProxy(this);
}
//AdvisedSupport config:封装了 Spring AOP 的代理配置,包括目标对象、拦截器、通知等信息。这个对象会在创建代理时提供给代理类,用于配置代理行为。
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
//!config.isOptimize():检查是否启用了优化,isOptimize() 通常用于判断是否有特殊优化需求。
//!config.isProxyTargetClass():检查是否强制使用类代理。isProxyTargetClass() 返回 false 时,意味着优先使用接口代理,而不是基于目标类的代理(即优先使用 JDK 动态代理)。
//!this.hasNoUserSuppliedProxyInterfaces(config):检查是否有用户提供的接口。hasNoUserSuppliedProxyInterfaces() 方法返回 false 时,表示存在用户定义的接口。
if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
return new JdkDynamicAopProxy(config);
} else {
//获取目标类。如果目标类为空(即没有提供目标对象或接口),抛出 AopConfigException 异常,因为代理的创建至少需要一个目标类或接口。
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.");
} else {
//!targetClass.isInterface():检查目标类是否是一个接口。如果目标类是接口,则可能不需要 CGLIB。
//!Proxy.isProxyClass(targetClass):检查目标类是否已经是一个代理类。如果目标类已经是代理类(比如 JDK 动态代理创建的类),则不需要再用 CGLIB 代理。
//!ClassUtils.isLambdaClass(targetClass):检查目标类是否是 Lambda 表达式生成的类。Lambda 类不适合使用 CGLIB 代理,因为它们的结构和普通类不同。
return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) && !ClassUtils.isLambdaClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
}
}
}
现在已经创建了一个ProxyFactory。再看看父类AdvisedSupport,用于管理代理对象的相关配置信息。它是所有 AOP 配置的核心抽象,为代理类的创建提供支持和配置数据。AdvisedSupport
继承了多个接口,主要负责保存代理目标对象、拦截器链、代理的接口和各种代理相关的配置信息。这个类直接参与了代理的创建与执行。
AdvisedSupport
类的作用:
- 配置代理对象的行为:包括目标对象、目标类、拦截器链、通知、是否使用 CGLIB 代理等。
- 管理切面和通知:它包含了代理方法的通知、切面以及拦截链的配置信息。
- 决定代理的生成策略:根据配置信息,选择是使用 JDK 动态代理还是 CGLIB 代理。
AdvisedSupport
类的字段:
TargetSource
targetSource:表示代理的目标对象,保存了被代理对象的元数据和对象引用,代理调用的方法最终会委托给该对象。
List<Advisor> advisors
:保存通知(Advice
)和切面(Pointcut
)的集合,通知可以在目标方法执行的前后或者抛出异常时执行。
boolean proxyTargetClass
:决定是否强制使用 CGLIB 来生成代理。true
表示强制代理目标类(即使用 CGLIB),false
则根据情况选择使用 JDK 动态代理或 CGLIB。
List<Class<?>> interfaces
:代理类需要实现的接口。如果目标类实现了接口,JDK 动态代理就会实现这些接口。
boolean optimize
:是否启用特殊优化,一般情况下不用修改。
boolean exposeProxy
:是否允许通过 AopContext.currentProxy()
获取当前的代理对象。
boolean frozen
:如果为 true
,表示当前代理配置被冻结,不能再修改。
AdvisedSupport
主要方法
TargetSource getTargetSource()
这个方法返回代理的目标对象,它从 targetSource
字段中获取对象实例。代理方法最终会被委托给这个目标对象。
void setTargetSource(TargetSource targetSource)
用于设置目标对象的 TargetSource
。当代理调用目标方法时,会从 targetSource
中获取目标实例。
void addAdvisor(Advisor advisor)
向 advisors
列表中添加一个 Advisor
,Advisor
是切面和通知的抽象封装,包含 Pointcut
和 Advice
。
boolean isProxyTargetClass()
判断是否强制使用 CGLIB 来代理目标类。返回 true
时强制代理目标类,而不管是否有接口。
void setExposeProxy(boolean exposeProxy)
设置是否允许通过 AopContext.currentProxy()
获取当前代理对象。如果需要在被代理对象的内部方法中调用代理对象的方法,可以设置为 true
。
boolean isFrozen()
检查代理配置是否被冻结。冻结意味着代理配置不允许再被修改。
List<Advisor> getAdvisors()
获取代理对象的 Advisor
列表。这个方法用于返回切面和通知的集合,供代理执行时使用。
AdvisedSupport
的工作原理
- 代理目标对象的设置:通过
TargetSource
来管理代理目标对象。Spring AOP 中,代理对象的所有方法调用最终都会被委托给TargetSource
中的目标对象。 - 代理的拦截链管理:
AdvisedSupport
保存了代理对象的所有Advisor
,这些Advisor
包含了通知(Advice
)和切面(Pointcut
)。代理类在执行方法时,会按照这些通知定义的顺序执行。 - 选择代理方式:
AdvisedSupport
会根据配置决定使用 JDK 动态代理还是 CGLIB 代理。JDK 动态代理适用于目标类实现了接口的情况,而 CGLIB 则会创建目标类的子类来实现代理。 - 控制代理对象的行为:例如,
exposeProxy
控制是否暴露当前代理对象;frozen
决定代理配置是否可以修改。 - 执行拦截器链:当目标方法被调用时,代理类会执行
AdvisedSupport
中维护的拦截器链,按顺序执行通知和目标方法。
//1.切点
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* say())");
//2.通知
MethodInterceptor advice = invocation -> {
System.out.println("before");
Object result = invocation.proceed();
System.out.println("after");
return result;
};
//3.创建Advisor切面
DefaultPointcutAdvisor Advisor = new DefaultPointcutAdvisor(pointcut, advice);
//4.创建代理
/*
proxyTargetClass=false 目标类实现了接口用jdk
proxyTargetClass=false 目标类没有实现接口用cglib
proxyTargetClass=true 用cglib
*/
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(Advisor);
proxyFactory.setTarget(new Man());
// proxyFactory.setProxyTargetClass(false);
// proxyFactory.setInterfaces(Man.class.getInterfaces());
Man proxy = (Man) proxyFactory.getProxy();
System.out.println(proxy.getClass());
proxy.say();
class com.example.springstudydemo.Service.Man$$EnhancerBySpringCGLIB$$e8168781
before
man say
after
static class Target{
public void coo(int n){
System.out.printf("coo(%d)%n",n);
}
}
@Aspect
static class MyAspect{
@Before("execution(* coo(..))")
public void before1(){
System.out.println("before1");
}
@Before("execution(* coo(..))&&args(n)")
public void before2(int n){
System.out.printf("before2(%d)%n",n);
}
}
@Configuration
static class MyConfig{
@Bean
AnnotationAwareAspectJAutoProxyCreator proxyCreator(){
return new AnnotationAwareAspectJAutoProxyCreator();
}
@Bean
MyAspect myAspect(){return new MyAspect();}
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(MyConfig.class);
context.registerBean(ConfigurationClassPostProcessor.class);
context.refresh();
System.out.println(context.getBean("myAspect"));
System.out.println(context.getBean(MyConfig.class).getClass());
AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
Method candidateAdvisors = AnnotationAwareAspectJAutoProxyCreator.class.getDeclaredMethod("findCandidateAdvisors");
candidateAdvisors.setAccessible(true);
List<Advisor> advisors = (List<Advisor>) candidateAdvisors.invoke(creator, new Object[]{});
for (Advisor advisor : advisors) {
System.out.println(advisor);
}
Target target = new Target();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE);
proxyFactory.addAdvisors(advisors);
Target proxy = (Target) proxyFactory.getProxy();
System.out.println(proxy.getClass());
proxy.coo(1);
AOP 切面与 Bean 生命周期的关系:
- AOP 切面是在 Spring 容器加载时初始化的。
- 代理对象会在 Bean 被加载到容器时创建,当该 Bean 的方法被调用时,代理对象通过反射机制控制方法的执行。