我们在学习 SpringAOP 的时候一直听说是面向切面编程,还有经常听到说 aop 可以用来做日志、做接口调用统计、做分库分表动态切换等等,觉得很神奇,明明我们只使用 @Aspect 注解了一个独立的 class,怎么能够这么强大呢?
本篇试着从源码角度分析一下 aop 的原理,在开始之前我们提出几个问题,一边学习一边解答。
我假定看本文的读者已经有基本的 springioc 相关的基础,最好也了解过 springaop 的源码,当然没有阅读过就需要大家更加集中精神。
SpringIOC 是对类的解耦,SpringAOP 就是对方法的解耦。牢记这句话
本人的 Spring 源码注释项目:https://github.com/Lingouzi/spring-framework.git
老规矩,上 demo
// 来个接口
public interface Calculate {
int div(int numA, int numB);
}
// 实现类
public class LybqCalculate implements Calculate {
@Override
public int div(int numA, int numB) {
System.out.println("执行目标方法:div");
return numA / numB;
}
}
/**
* 被 @Aspect 注解的 bean 就是一个切面,就是一个 Aspect
*/
@Aspect
@Order
public class LogAspect {
/**
* 被 @Pointcut 注解的,就是一个切点,这个决定了我们要增强哪个方法。
* 【springaop 只能增强 springioc 容器管理下的 bean 中的方法。它和 aspectj 还是有区别的,springaop 只是实现了 aspectj 的部分思想】
*/
@Pointcut("execution(* top.ybq87.LybqCalculate.*(..))")
public void pointCut() {
}
/**
* 各种通知,Advise,我们前面定义了切点,就拦截了 bean 的方法,那么要对这些方法做什么呢?就在通知方法这里进行
* @param joinPoint
* @throws Throwable
*/
@Before(value = "pointCut()")
public void methodBefore(JoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【" + methodName + "】的<前置通知>,入参" + Arrays.asList(joinPoint.getArgs()));
}
@After(value = "pointCut()")
public void methodAfter(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【" + methodName + "】的<后置通知>,入参" + Arrays.asList(joinPoint.getArgs()));
}
@AfterReturning(value = "pointCut()", returning = "result")
public void methodReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【" + methodName + "】的<返回通知>,入参" + Arrays.asList(joinPoint.getArgs()) + ";返回值:" + result);
}
@AfterThrowing(value = "pointCut()")
public void methodAfterThrowing(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【" + methodName + "】的<异常通知>,入参" + Arrays.asList(joinPoint.getArgs()));
}
// 还有个 Around 方法,留给大家自己研究了
}
// 配置类,注入下实现类和我们的切面方法
@Configuration
@EnableAspectJAutoProxy
public class MainConfig {
@Bean
public Calculate calculate() {
return new LybqCalculate();
}
@Bean
public LogAspect logAspect() {
return new LogAspect();
}
}
// main 方法
public class MainClass {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
Calculate calculate = (Calculate) ac.getBean("calculate");
calculate.div(6, 2);
}
}
// 跑起来的打印结果
执行目标方法【div】的<前置通知>,入参[6, 2]
执行目标方法:div
执行目标方法【div】的<后置通知>,入参[6, 2]
执行目标方法【div】的<返回通知>,入参[6, 2];返回值:3
如上,代码比较简单,完整的 demo 见我的spring 源码解析项目。
这个问题大家一定很奇怪,这里我再具体描述下:我们 mian 方法中使用的 calculate
对象还是你以为的那个calculate
对象嘛?
断点打印看看
这里我们看到这个类被标注为了 $Proxy说明这是一个代理类,也就是被增强的类了,不是原来的狗子了。
SpringAOP 通过动态代理技术,对类和方法进行增强,其中主要有 2 种动态代理方式
JDK 动态代理、cglib 动态代理
既然它变强了,那么一定是因为它秃了,明明这个方法只能打印执行目标方法:div
,但是实际上却被一众小弟环绕,那么它怎么变秃的呢?
抄作业开始,【所以我说希望读者有一定的源码基础才好嘛】,看过源码的应该了解,bean 的实例化和初始化都是在finishBeanFactoryInitialization
,进行的,而更进一步的初始化是在doCreateBean
中
AbstractApplicationContext#refresh
--AbstractApplicationContext#finishBeanFactoryInitialization
----DefaultListableBeanFactory#preInstantiateSingletons
------AbstractBeanFactory#doGetBean
--------AbstractAutowireCapableBeanFactory#createBean
----------AbstractAutowireCapableBeanFactory#doCreateBean
跟进到AbstractAutowireCapableBeanFactory#doCreateBean
在exposedObject = initializeBean(beanName, exposedObject, mbd);
打个断点,跳过其他 bean 的初始化,等到我们的calculate
。
目前发现我们的calculate
还是原来的狗子,而exposedObject
也还是我们期待的那个对象。
step over
明显看到这之后它变了。
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}
else {
// bean 如果实现了 xxxAware 接口,那么这里进行方法的回调,不是本文重点
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
// 后置处理器,@PostConstuct 注解的方法
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
/**
* 调用 bean 配置中的 init-method="xxx"
*/
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null