mini-spring 学习笔记—AOP
最近在学习 mini-spring 项目,记录笔记以总结心得
IoC篇:mini-spring 学习笔记—IoC
AOP 中有一些很重要的概念:切点、切面、连接点等等。如果对这些概念不熟悉可能会难以理解本篇内容,不熟悉的朋友可以看看这篇博客
切点表达式
ClassFilter 和 MethodMatcher
这两个接口都定义了一个叫做 mathes
的方法,用于匹配
ClassFilter
接口规范了类匹配器的行为
boolean matches(Class<?> clazz);
MethodMatcher
接口规范了方法匹配器的行为
boolean matches(Method method, Class<?> targetClass);
Pointcut 和 AspectJExpressionPointcut
Pointcut
接口是对切点的抽象,包含类型匹配器和方法匹配器
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
AspectJExpressionPointcut
类用于完成切点表达式的匹配任务,实现了 Pointcut
、ClassFilter
和 MethodMatcher
三个接口
其中使用 Set
来存储支持的切入点原语,在项目中只支持 EXECUTION
原语
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
使用 PointcutExpression
类型变量存储解析后的切入点表达式
private final PointcutExpression pointcutExpression;
带参构造函数对传入的字符串表达式进行解析,将解析好的结果赋值给 pointcutExpression
// AspectJExpressionPointcut 类
// 构造方法,接受一个表示 AspectJ 表达式的字符串参数
public AspectJExpressionPointcut(String expression) {
// 创建一个 PointcutParser 对象,用于解析 AspectJ 表达式
PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());
// 解析传入的表达式并将结果赋给 pointcutExpression
pointcutExpression = pointcutParser.parsePointcutExpression(expression);
}
两个 matches
方法用于判断解析结果是否准确
public boolean matches(Class<?> clazz) {
return pointcutExpression.couldMatchJoinPointsInType(clazz);
}
public boolean matches(Method method, Class<?> targetClass) {
// 使用 AspectJ 表达式判断方法是否匹配,并始终返回 true
return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
}
基于 JDK 的动态代理
动态代理是代理模式在 Spring 中的应用,对代理模式不了解的朋友可以看看这篇博客
TargetSource 和 AdvisedSupport
TargetSource
类用于封装被代理的目标对象,只有一个成员变量 target
private final Object target;
AdvisedSupport
类用于保存与切面相关的配置,有三个成员变量
private TargetSource targetSource;
// 保存方法拦截器的实例
// 方法拦截器是在目标方法执行前后添加额外逻辑的组件
private MethodInterceptor methodInterceptor;
// 保存方法匹配器的实例
private MethodMatcher methodMatcher;
ReflectiveMethodInvocation
该类实现了 MethodInvocation
具有了执行目标方法的功能,有三个成员变量
private final Object target;
private final Method method;
private final Object[] arguments;
关键的方法为 proceed
方法,调用 invoke
方法执行 target
对象具有的方法
public Object proceed() throws Throwable {
return method.invoke(target, arguments);
}
AopProxy 和 JdkDynamicAopProxy
AopProxy
接口是 AOP 代理的抽象,提供了 getProxy
方法用于获得一个新的代理对象
Object getProxy();
JdkDynamicAopProxy
类是 JDK 动态代理的实现类,它实现了 AopProxy
和 InvocationHandler
接口,具有新建代理对象和通过代理对象调用方法的功能。只有一个 AdvisedSupport
类型的成员变量
private final AdvisedSupport advised;
getProxy
使用 java 中的 Proxy
类进行实现,返回一个新建的代理对象
public Object getProxy() {
return Proxy.newProxyInstance(getClass().getClassLoader(), advised.getTargetSource().getTargetClass(), this);
}
invoke
函数被用于委托调用被代理对象的方法。首先使用切点表达式的 mathes
方法对被调用的方法进行匹配,如果是切点表达式所记录的方法,就交给代理对象的方法拦截器执行(第一个 return
),否则就直接执行(第二个 return
)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
//代理方法
MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), method, args));
}
return method.invoke(advised.getTargetSource().getTarget(), args);
}
这里说一下为什么要进行判断,因为切点表达式中所记录的方法是需要进行特殊处理的 methodInterceptor.invoke
是调用的具体类型的拦截器,这个类型需要实现 MethodInterceptor
接口
// WorldServiceInterceptor 类
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Do something before the earth explodes");
Object result = invocation.proceed();
System.out.println("Do something after the earth explodes");
return result;
}
可以看到,在被切点表达式记录的方法调用时,需要进行调用前与调用后的处理。调用底层方法的语句为上面的 Object result = invocation.proceed();
public Object proceed() throws Throwable {
return method.invoke(target, arguments);
}
可以看到,最终也是调用的 method.invoke
测试
这里有一点困惑了我很久,在 DynamicProxyTest
中没有调用 JdkDynamicAopProxy
的 invoke
方法,那是怎么进入到 invoke
中的?
经过调试得知,调用被代理对象的方法时,会自动进入到 invoke
方法中
被代理对象方法调用
自动进入到 invoke
中
基于 CGLIB 的动态代理
基于 jdk 的动态代理有一个最大的缺点:被代理类必须为接口实现类,这就给项目开发带来了不便。
关于为什么 jdk 的动态代理对象必须为接口实现类,可以看这篇文章
而基于 CGLIB 的动态代理则不要求被代理对象为接口实现类
CglibAopProxy
该类也实现了 AopProxy
接口,使用 getProxy
方法创建代理对象。
可以观察到,首先创建了增强类 enhancer
,将被代理对设置为代理对象的父类,同时继承了被代理对象的接口
public Object getProxy() {
Enhancer enhancer = new Enhancer();
// 设置被代理对象的类型为父类型
enhancer.setSuperclass(advised.getTargetSource().getTarget().getClass());
// 设置被代理对象的接口为接口
enhancer.setInterfaces(advised.getTargetSource().getTargetClass());
// 设置回调类
enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
// 创建代理对象
return enhancer.create();
}
DynamicAdvisedInterceptor
类实现了 CGLIB 的 MethodInterceptor
接口,用于方法的拦截,它仅仅包含被代理对象一个成员变量
private final AdvisedSupport advised;
实现了 intercept
方法用于拦截函数调用(类似于 jdk 动态代理中的 invoke
函数),当代理对象调用被代理对象的函数时,自动进入此方法。同样也对方法进行了匹配检查。
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
CglibMethodInvocation methodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, objects, methodProxy);
if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
//代理方法
return advised.getMethodInterceptor().invoke(methodInvocation);
}
return methodInvocation.proceed();
}
CglibMethodInvocation
类为 ReflectiveMethodInvocation
的子类,只有一个成员变量 methodProxy
private final MethodProxy methodProxy;
实现了 proceed
方法
public Object proceed() throws Throwable {
return this.methodProxy.invoke(this.target, this.arguments);
}
AOP 代理工厂
AdvisedSupport
AdvisedSupport
类增添属性成员 proxyTargetClass
,用于判断是否使用 CGLIB 代理
private boolean proxyTargetClass = false;
ProxyFactory
该类仅包含成员属性 advisedSupport
表示被代理对象
private AdvisedSupport advisedSupport;
使用 createAopProsy
返回动态代理类
private AopProxy createAopProxy() {
// 判断用哪种代理
if (advisedSupport.isProxyTargetClass()) {
return new CglibAopProxy(advisedSupport);
}
return new JdkDynamicAopProxy(advisedSupport);
}
几种常用的 Advice
advice 表示建言,即在方法执行的(之前/之后/之前与之后)执行的方法。Spring 有多种建言,本章仅仅简单实现了 before 建言(有空的话我会补上)
BeforeAdvice 和 MethodBeforeAdvice
BeforeAdvice
继承了 Advice
接口,目前是个空接口,用于规范 before 建言的行为
public interface BeforeAdvice extends Advice {}
MethodBeforeAdvice
继承了 BeforeAdvice
接口,定义了 before
方法,即方法调用前执行的方法
void before(Method method, Object[] args, Object target) throws Throwable;
MethodBeforeAdviceInterceptor
该类为 before 建言的拦截器,实现了 MethodInterceptor
接口,仅有一个成员变量 advice
private MethodBeforeAdvice advice;
实现了 invoke
方法
public Object invoke(MethodInvocation invocation) throws Throwable {
// 在执行被代理方法之前,先执行before advice操作
this.advice.before(invocation.getMethod(), invocation.getArguments(), invocation.getThis());
return invocation.proceed();
}
测试
要提供 before 建言,需要实现 MethodBeforeAdvice
接口,重写 before
方法
public class WorldServiceBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("BeforeAdvice: do something before the earth explodes");
}
}
PointcutAdvisor:Pointcut 和 Advice 的组合
Advisor 和 PointcutAdvisor
Advisor
接口规范了 getAdvice
方法,用于获取建言
Advice getAdvice();
PointcutAdvisor
接口规范了 getPointcut
方法同时继承了 Advisor
接口,能够获取建言和切点
Pointcut getPointcut();
MethodBeforeAdviceInterceptor
添加了无参构造函数和 advice
的 set
方法,能够修改建言
public MethodBeforeAdviceInterceptor() {}
public void setAdvice(MethodBeforeAdvice advice) {this.advice = advice;}
AspectJExpressionPointcutAdvisor
实现了 PointcutAdvisor
接口,包含三个成员变量
private AspectJExpressionPointcut pointcut;
private Advice advice;
private String expression;
动态代理融入 bean 生命周期
AspectJExpressionPointcutAdvisor
增加 getPoingcut
方法
public Pointcut getPointcut() {
if (pointcut == null) {
pointcut = new AspectJExpressionPointcut(expression);
}
return pointcut;
}
InstantiationAwareBeanPostProcessor
InstantiationAwareBeanPostProcessor
接口为 BeanPostProcessor
的子接口,用于规范 bean 实例化前返回代理对象的行为,仅定义了 postProcessBeforeInstantiation
一个方法
Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;
DefaultAdvisorAutoProxyCreator
DefaultAdvisorAutoProxyCreator
实现了 InstantiationAwareBeanPostProcessor
接口,用于对 bean 创建代理对象,主要方法为 postProcessBeforeInstantiation
方法,因为篇幅限制,这里就不放该方法的完整代码了,只粘贴其核心部分
// 避免死循环
if (isInfrastructureClass(beanClass)) {
return null;
}
Collection<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values();
try {
// 这一步就使用代理对象包裹 bean
for (AspectJExpressionPointcutAdvisor advisor : advisors) {
ClassFilter classFilter = advisor.getPointcut().getClassFilter();
// 判断是不是表达式要拦截的类
if (classFilter.matches(beanClass)) {
AdvisedSupport advisedSupport = new AdvisedSupport();
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
Object bean = beanFactory.getInstantiationStrategy().instantiate(beanDefinition);
TargetSource targetSource = new TargetSource(bean);
advisedSupport.setTargetSource(targetSource);
advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());
//返回代理对象
return new ProxyFactory(advisedSupport).getProxy();
}
}
其中 isInfrastructureClass
方法实现如下
private boolean isInfrastructureClass(Class<?> beanClass) {
return Advice.class.isAssignableFrom(beanClass)
|| Pointcut.class.isAssignableFrom(beanClass)
|| Advisor.class.isAssignableFrom(beanClass);
}
为什么这能够防止死循环呢?
AbstractAutowireCapableBeanFactory
在 createBean
方法中加入判断需不需要代理 bean
// 如果bean需要代理,则直接返回代理对象
Object bean = resolveBeforeInstantiation(beanName, beanDefinition);
if (bean != null) {
return bean;
}
resolveBeforeInstantiation
方法如下,主要调用 bean 初始化前后的 BeanProcessor
protected Object resolveBeforeInstantiation(String beanName, BeanDefinition beanDefinition) {
Object bean = applyBeanPostProcessorsBeforeInstantiation(beanDefinition.getBeanClass(), beanName);
if (bean != null) {
bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
}
return bean;
}
applyBeanPostProcessorsBeforeInstantiation
方法通过判断 beanPostProcessor
有没有实现 InstantiationAwareBeanPostProcessor
接口来判断用不用代理封装 bean,否则返回 null
if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {
Object result = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessBeforeInstantiation(beanClass, beanName);
if (result != null) {
return result;
}
}
本章小结
至此,bean 的生命周期如下
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」