Spring-AOP分析

Spring分析-AOP

1.案例引入

在上一篇文章中,【Spring–IOC】【https://www.cnblogs.com/jackjavacpp/p/18829545】,我們了解到了IOC容器的創建過程,在文末也提到了AOP相關,但是沒有作細致分析,這篇文章就結合示例,來詳細分析一下Spring-AOP。

本文章示例代碼見該倉庫:【spring】中的“spring”模塊。

倉庫地址:https://gitee.com/quercus-sp204/sourcecode-and-demos

本節AOP示例代碼如下:[ 在springaop 包下、然后測試類就是Main類里面的aop()方法 ]

@Component("dog")
public class Dog {public Dog() {    }public void wangwang() {System.out.println("wangwang --- 狗");}
}public interface Life {void create();void wangwang();
}@Component(value = "lifeImpl")
public class LifeImpl implements Life {@Overridepublic void create() {System.out.println("[]--生命創建");}@Overridepublic void wangwang() {System.out.println("[]--生命 汪汪汪");}public void wangwang( String msg ) {System.out.println("=========== " + msg);create(); wangwang();System.out.println("===========");}
}
// advice.java
@Component
@Aspect
public class MyAdvice {private static final String dogExpression = "execution(* com.feng.springaop.*.wangwang*(..))";@Around(dogExpression)public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("##########【DOG-環繞通知中的前置通知】##########");Object returnVale = joinPoint.proceed();System.out.println("##########【DOG-環繞通知中的后置通知】##########");return returnVale;}
}
// 一個普通的,沒有配置任何通知
@Component
public class Cat {public Cat() {   }public void miaomiao() {System.out.println("貓 喵喵");}
}
//Main.java
@Configuration
// @ComponentScan("com.feng.springioc") // 循環依賴分析 && IOC分析
@ComponentScan("com.feng.springaop") // spring-aop分析
@EnableAspectJAutoProxy
//@EnableAspectJAutoProxy(proxyTargetClass = true) // cglib
public class Main {public static void main(String[] args) {// xunhuan();aop();}public static void aop() {ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);Dog dog = context.getBean("dog", Dog.class);dog.wangwang();System.out.println(dog.getClass().getName());Life impl = context.getBean("lifeImpl", Life.class);impl.wangwang();System.out.println(impl.getClass().getName());Cat cat = context.getBean("cat", Cat.class);cat.miaomiao();System.out.println(cat.getClass().getName());}
}

上面的示例代碼挺簡單的,運行上面的測試代碼,我們可以得到如下輸出:

##########【DOG-環繞通知中的前置通知】##########
wangwang --- 狗
##########【DOG-環繞通知中的后置通知】##########
com.feng.springaop.Dog$$EnhancerBySpringCGLIB$$563f1145
##########【DOG-環繞通知中的前置通知】##########
[]--生命 汪汪汪
##########【DOG-環繞通知中的后置通知】##########
com.sun.proxy.$Proxy17
貓 喵喵
com.feng.springaop.Cat

從輸出內容可以看到,dog對象的bean是走的cglib的動態代理,由于lifeImpl實現了接口,故其采用的是jdk動態代理,但是貓貓確實是一個實打實的我們的對象。

動態代理不知道的可以看這篇文章:【動態代理】:https://blog.csdn.net/okok__TXF/article/details/144191784

可以得知是創建了代理對象,然后執行就是將“通知”和實際“執行的邏輯”組合在一起了,那么我們就從SpringAOP 是如何創建代理對象、執行過程是什么樣子這兩個方面來分析一下其AOP。

2.代理對象的創建

在前一篇文章中,我們得知,代理對象的創建是在initializeBean(xx)方法里面進行的,我們來驗證一下:

首先debug到cat對象的初始化:如下圖【在return的時候貓對象仍然是Cat類型的】

在這里插入圖片描述

然后再看dog對象的初始化:如下圖 【經過了圖中綠色下劃線之后,類型發生了變化CGLIB$$xxx的了】

在這里插入圖片描述

在看一下lifeImpl的初始化:如下圖 【經過了圖中綠色下劃線之后,類型發生了變化$Proxy的了】

在這里插入圖片描述

經過上面的對比:我們可以得知,代理對象的生成是在wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);里面的,此外如果實現了接口,那么就是jdk動態代理生成的代理對象、如果沒有實現接口,那么就是走的CGLIB生成的代理對象。

目標很明確了,我們分析一下applyBeanPostProcessorsAfterInitialization方法就可以了。

進入到這個方法里面,debug調試過后

在這里插入圖片描述

發現dog經過AnnotationAwareAspectJAutoProxyCreator.postProcessAfterInitialization(result, beanName) 方法之后,current就變成了 CGLIB的代理對象,說明這個方法大有奧秘!后面的lifeImpl對象亦是如此,就不給出圖片闡述了。那為什么cat前后還是cat呢,并沒有發生變化呢?

// 實際上是到了下面的類的postProcessAfterInitialization方法
// AbstractAutoProxyCreator.java 實現了BeanPostProcessor接口
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupportimplements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {@Overridepublic Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);// 檢查該 Bean 是否已被提前代理(如循環依賴中的早期引用)if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}
}

postProcessAfterInitialization()方法是 BeanPostProcessor 接口的實現,作用于 Bean 初始化之后(如 @PostConstruct 執行后),主要用于處理 Spring AOP 的代理邏輯。其核心目標是確保 Bean 在初始化完成后,根據需要生成代理對象,同時避免重復代理(尤其是在存在循環依賴時)。

此處的this.earlyProxyReferences.remove(cacheKey)就是從早期代理引用里面取出并移除該key的早期代理引用對象,來進行比對。這個earlyProxyReferences在哪里put呢?

在IOC那一章里面,在屬性填充之前有這樣一段代碼,在三級緩存中添加了如下對象

// 緩存早期單例,以便能夠解析循環引用
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {....addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}// 這個getEarlyBeanReference,最后是來到了
// AbstractAutoProxyCreator.java
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean); // 往早期代理引用放了一個對象--這里put的return wrapIfNecessary(bean, beanName, cacheKey);
}
// return的是wrapIfNecessary(bean, beanName, cacheKey)

此章是沒有循環引用的,只有循環依賴的時候才會用到三級緩存里面的東西,也就是循環依賴的時候才會往earlyProxyReferences中put東西。為什么?見后續文章**【spring循環依賴的解決】**。

那么這里的earlyProxyReferences就肯定一直都是空的,故在AbstractAutoProxyCreator :: postProcessAfterInitialization() 方法里面會走

if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey); // 走這里
}

這個wrapIfNecessary是什么,下面來看看

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {//1. 如果該 Bean 已經被手動指定 TargetSource(如通過自定義 Scope),直接返回原始 Beanif (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) return bean;// 2. 如果緩存中標記該 Bean 不需要代理,直接返回if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) return bean;// 3. 檢查是否為基礎設施類(如 Spring 內部類)或需要跳過代理的 Beanif (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// 4. 獲取適用于該 Bean 的增強器(Advisors/Advices)**Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {// 標記該 Bean 需要代理this.advisedBeans.put(cacheKey, Boolean.TRUE);// 5.創建代理對象 **Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());// 緩存代理類型,后續可通過 getBean 直接返回代理對象return proxy;}// 6. 無增強器,標記該 Bean 不需要代理this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}

**一、獲取適用于該 Bean 的增強器:**getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);

// AbstractAdvisorAutoProxyCreator.java
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName); // 進入if (advisors.isEmpty()) {return DO_NOT_PROXY;}return advisors.toArray();
}
// 只看比較重要的1 2兩點
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 1.獲取候選AdvisorList<Advisor> candidateAdvisors = findCandidateAdvisors();// 2.獲取適用于該bean的Advisor: 例如Pointcut匹配List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);extendAdvisors(eligibleAdvisors); // 擴展if (!eligibleAdvisors.isEmpty()) {eligibleAdvisors = sortAdvisors(eligibleAdvisors); // 排序}return eligibleAdvisors;
}
//第1點:候選Advisor AnnotationAwareAspectJAutoProxyCreator.java
@Override
protected List<Advisor> findCandidateAdvisors() {// Add all the Spring advisors found according to superclass rules.List<Advisor> advisors = super.findCandidateAdvisors();// 為 Bean Factory 中的所有 AspectJ 方面構建 Advisor。if (this.aspectJAdvisorsBuilder != null) {advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()); // 進入}return advisors;
}
//BeanFactoryAspectJAdvisorsBuilder.java
public List<Advisor> buildAspectJAdvisors() {...if (this.advisorFactory.isAspect(beanType)) { // @AspectaspectNames.add(beanName);AspectMetadata amd = new AspectMetadata(beanType, beanName);if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {MetadataAwareAspectInstanceFactory factory =new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);if (this.beanFactory.isSingleton(beanName)) {this.advisorsCache.put(beanName, classAdvisors);}else {this.aspectFactoryCache.put(beanName, factory);}advisors.addAll(classAdvisors);}else {。。。}}....
}

在這里插入圖片描述

如圖,找到了我們的MyAdvice.

// 第2點:獲取適用于該bean的Advisor -- 自己debug吧。。。
// 我這就不給了,反正就是看candidateAdvisors匹不匹配嘛
protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {ProxyCreationContext.setCurrentProxiedBeanName(beanName);try {return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);}finally {ProxyCreationContext.setCurrentProxiedBeanName(null);}
}

在例子中,創建cat的時候,找不到合適的Advice,所以第二步就不會執行咯,故就不需要創建代理對象了

二、創建代理對象 : createProxy(xxx, xx, xxx, xxx) 【本文就以cglib創建代理對象為主,jdk動態代理創建就由讀者自行調試分析了】

createProxy 是 Spring AOP 中 創建代理對象的核心方法,位于 AbstractAutoProxyCreator 類中。它負責根據 Bean 的配置和增強器(Advice/Advisors)生成 JDK 動態代理或 CGLIB 代理對象

// specificInterceptors就是第一步找到的advice中匹配該bean的東西,這里叫做攔截器
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {...if (proxyFactory.isProxyTargetClass()) {// 強制使用 CGLIB 代理 -- if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {// 處理已被 JDK 代理的類或 Lambda 表達式for (Class<?> ifc : beanClass.getInterfaces()) {proxyFactory.addInterface(ifc); // 添加接口(確保引入增強器生效)}}} else {// 根據默認規則選擇代理類型if (shouldProxyTargetClass(beanClass, beanName)) {// 如果是true-強制使用 CGLIB,即使目標類實現了接口proxyFactory.setProxyTargetClass(true); // 強制 CGLIB} else {evaluateProxyInterfaces(beanClass, proxyFactory); // 檢查接口決定代理類型}}...//構建并添加增強器(Advisors)//將 specificInterceptors(如 MethodInterceptor)轉換為 Spring 的 Advisor 對象。Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);...//選擇類加載器并生成代理ClassLoader classLoader = getProxyClassLoader();if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();}return proxyFactory.getProxy(classLoader);
}

上面最后是return proxyFactory.getProxy(classLoader); — 下面以dog對象為例子

// CglibAopProxy.java
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {try{....// 配置 CGLIB 的 Enhancer 對象Enhancer enhancer = createEnhancer();if (classLoader != null) {// 設置類加載器enhancer.setClassLoader(classLoader);if (classLoader instanceof SmartClassLoader &&((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {enhancer.setUseCache(false);}}// 設置代理類的父類enhancer.setSuperclass(proxySuperClass);// 設置代理類要實現的接口enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));// 獲取回調函數數組---------【重點1】Callback[] callbacks = getCallbacks(rootClass);Class<?>[] types = new Class<?>[callbacks.length];for (int x = 0; x < types.length; x++) {types[x] = callbacks[x].getClass();}// 設置回調過濾器--------【重點2】enhancer.setCallbackFilter(new ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));enhancer.setCallbackTypes(types);// 生成代理類并創建代理實例 --------- 【重點3】return createProxyClassAndInstance(enhancer, callbacks);} ...
}

創建代理對象【重點1】getCallbacks(rootClass); 這個里面是什么呢?

private Callback[] getCallbacks(Class<?> rootClass) throws Exception {// Parameters used for optimization choices...boolean isFrozen = this.advised.isFrozen();boolean exposeProxy = this.advised.isExposeProxy();// 是否靜態類,這里的靜態并非指靜態類,而是每次調用返回的實例都是否是不可變的// 如單例模式的bean就是靜態,而多例模式下的bean就不是靜態boolean isStatic = this.advised.getTargetSource().isStatic();// DynamicAdvisedInterceptor:用于處理包含 AOP 通知的方法調用,//它會根據配置的切面和通知邏輯來執行相應的增強操作。//內部通過 ReflectiveMethodInvocation 鏈式調用通知邏輯。Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);....Callback[] mainCallbacks = new Callback[] {aopInterceptor,  // for normal advicetargetInterceptor,  // 目標方法直接調用攔截器new SerializableNoOp(),  // no override for methods mapped to thistargetDispatcher, this.advisedDispatcher,new EqualsInterceptor(this.advised),// 處理代理對象的 equals 方法new HashCodeInterceptor(this.advised)// 處理代理對象的 hashCode 方法};Callback[] callbacks;// 如果類是靜態 && 配置凍結。則準備做一些優化策略if (isStatic && isFrozen) {Method[] methods = rootClass.getMethods();Callback[] fixedCallbacks = new Callback[methods.length];this.fixedInterceptorMap = CollectionUtils.newHashMap(methods.length);// TODO: small memory optimization here (can skip creation for methods with no advice)for (int x = 0; x < methods.length; x++) {Method method = methods[x];List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());this.fixedInterceptorMap.put(method, x);}callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);this.fixedInterceptorOffset = mainCallbacks.length;}else {callbacks = mainCallbacks; //}return callbacks;}

創建代理對象【重點2】ProxyCallbackFilter 回調過濾器

// Constants for CGLIB callback array indices
private static final int AOP_PROXY = 0;
private static final int INVOKE_TARGET = 1;
private static final int NO_OVERRIDE = 2;
private static final int DISPATCH_TARGET = 3;
private static final int DISPATCH_ADVISED = 4;
private static final int INVOKE_EQUALS = 5;
private static final int INVOKE_HASHCODE = 6;public ProxyCallbackFilter(AdvisedSupport advised, Map<Method, Integer> fixedInterceptorMap, int fixedInterceptorOffset) {this.advised = advised;this.fixedInterceptorMap = fixedInterceptorMap;this.fixedInterceptorOffset = fixedInterceptorOffset;
}@Override
public int accept(Method method) {// 1. 如果當前方法被 final 修飾,則不代理該方法//如果method 被 final 修飾,則無法代理if (AopUtils.isFinalizeMethod(method)) {...return NO_OVERRIDE;}if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface() &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {return DISPATCH_ADVISED;}// 3. equals 方法if (AopUtils.isEqualsMethod(method)) ...// 4.hashCodeif (AopUtils.isHashCodeMethod(method)) ...Class<?> targetClass = this.advised.getTargetClass();List<?> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);boolean haveAdvice = !chain.isEmpty();boolean isFrozen = this.advised.isFrozen();boolean exposeProxy = this.advised.isExposeProxy();boolean isStatic = this.advised.getTargetSource().isStatic();if (haveAdvice || !isFrozen) {if (exposeProxy) {...return AOP_PROXY;}// Check to see if we have fixed interceptor to serve this method.// Else use the AOP_PROXY.if (isStatic && isFrozen && this.fixedInterceptorMap.containsKey(method)) {...// We know that we are optimizing so we can use the FixedStaticChainInterceptors.int index = this.fixedInterceptorMap.get(method);return (index + this.fixedInterceptorOffset);}else {return AOP_PROXY;}}else {if (exposeProxy || !isStatic) return INVOKE_TARGET;Class<?> returnType = method.getReturnType();if (targetClass != null && returnType.isAssignableFrom(targetClass)) {...return INVOKE_TARGET;}else ..return DISPATCH_TARGET;}
}

創建代理對象【重點3】生成代理類并創建代理實例 createProxyClassAndInstance(enhancer, callbacks);

// ObjenesisCglibAopProxy.java
@Override
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {Class<?> proxyClass = enhancer.createClass();Object proxyInstance = null;if (objenesis.isWorthTrying()) {try {//Objenesis 是一個專門用于繞過對象構造函數直接實例化對象的庫。proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());}.....}// 當 Objenesis 失敗時,通過反射調用默認或指定參數的構造方法。if (proxyInstance == null) {// Regular instantiation via default constructor...try {Constructor<?> ctor = (this.constructorArgs != null ?proxyClass.getDeclaredConstructor(this.constructorArgTypes) :proxyClass.getDeclaredConstructor());ReflectionUtils.makeAccessible(ctor);proxyInstance = (this.constructorArgs != null ?ctor.newInstance(this.constructorArgs) : ctor.newInstance());}....}// 回調鏈決定了代理對象的方法攔截行為(如切面增強、直接調用目標方法等)。((Factory) proxyInstance).setCallbacks(callbacks);return proxyInstance;
}

3.執行過程

執行是怎么樣的呢?

照樣從案例的執行開始看起

public static void aop() {ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);Dog dog = context.getBean("dog", Dog.class);dog.wangwang();System.out.println(dog.getClass().getName());Life impl = context.getBean("lifeImpl", Life.class);impl.wangwang();System.out.println(impl.getClass().getName());Cat cat = context.getBean("cat", Cat.class);cat.miaomiao();System.out.println(cat.getClass().getName());
}

先執行的dog的方法【cglib】、再執行impl的方法【jdk】,因為第一個沒有實現接口,第二個實現了接口。

上一節我們可以知道是proxyFactory.getProxy(classLoader);創建的代理對象,實際上getProxy是AopProxy接口的方法,那么在Spring中該接口的直接實現類只有兩個:

在這里插入圖片描述

以第一個例子開頭,dog肯定是CglibAopProxy的代理對象。【代理不會的看這里】:https://www.cnblogs.com/jackjavacpp/p/18582124

熟悉cglib的都知道,通過cglib生成的代理對象,然后使用該對象執行目標方法,會走設置的方法攔截器,上一章最后創建代理對象那一節里面的getCallbacks方法,第一個就new了DynamicAdvisedInterceptor對象,它是用于處理包含 AOP 通知的方法調用。我們點開CglibAopProxy里面的DynamicAdvisedInterceptor靜態內部類看一下,肯定重寫了intercept方法,不用想啊,這就是會代理的好處啊。

//CglibAopProxy.java的靜態內部類 DynamicAdvisedInterceptor
//proxy:CGLIB 生成的代理對象。
//method:被調用的方法(目標方法的反射對象)
//args: 方法參數。
//methodProxy:CGLIB 的 MethodProxy 對象,用于直接調用目標方法(比反射高效)
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;Object target = null;//targetSource:目標對象來源(如單例、原型或池化對象)TargetSource targetSource = this.advised.getTargetSource();try {if (this.advised.exposeProxy) { //暴露代理到當前線程上下文//允許目標對象內部方法通過 AopContext.currentProxy() 獲取代理對象,//解決自調用(如 this.method())時 AOP 失效的問題。oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}    target = targetSource.getTarget(); //獲取被代理的原始對象Class<?> targetClass = (target != null ? target.getClass() : null);List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);Object retVal;// 無攔截器,直接調用目標方法if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) {Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);retVal = invokeMethod(target, method, argsToUse, methodProxy);}else {// 有攔截器,創建方法調用鏈并執行//調用其 proceed() 方法,按順序執行攔截器鏈。retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();}retVal = processReturnType(proxy, target, method, retVal);return retVal;}finally {if (target != null && !targetSource.isStatic()) {targetSource.releaseTarget(target);}if (setProxyContext) {AopContext.setCurrentProxy(oldProxy); // 恢復原始代理上下文}}
}public boolean equals()...
public int hashCode()....
}

CglibMethodInvocation.java

private static class CglibMethodInvocation extends ReflectiveMethodInvocation {@Override@Nullablepublic Object proceed() throws Throwable {try {return super.proceed();}....}
}//ReflectiveMethodInvocation.java
@Override
@Nullable
public Object proceed() throws Throwable {//從索引-1開始if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {return invokeJoinpoint(); // 所有攔截器執行完畢,調用目標方法}// 獲取下一個攔截器Object interceptorOrInterceptionAdvice =this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);//若獲取的攔截器是 InterceptorAndDynamicMethodMatcher 類型,需進行動態方法匹配if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {// Evaluate dynamic method matcher here: static part will already have// been evaluated and found to match.InterceptorAndDynamicMethodMatcher dm =(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());//若匹配成功,調用攔截器的 invoke 方法,傳入當前 ReflectiveMethodInvocation 對象。//匹配就是在攔截器鏈執行過程中動態判斷當前攔截器是否需要應用于目標方法調用if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {return dm.interceptor.invoke(this);}else {// 動態匹配失敗,跳過當前攔截器,遞歸調用 proceed 方法執行下一個攔截器return proceed();}}else {// 不是動態方法匹配器,直接調用攔截器的 invoke 方法return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);}
}

上面一個小小的方法就包含了

  1. 攔截器鏈模式
    通過責任鏈模式按順序執行攔截器,支持靈活擴展(如事務、日志、安全等)。
  2. 性能優化
    • 無攔截器時直接調用目標方法。
    • 使用 MethodProxy 代替反射調用。
  3. 上下文管理
    • AopContext 解決自調用問題。
    • TargetSource 管理目標對象的生命周期。

4.案例分析

下面就以@Transacional注解、自定義Advice相結合為例子,分析一下代理創建及其運行過程。

首先搭建一個項目,如下:

// 1.實體類對象
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user_tab")
public class User {@TableId(type = IdType.AUTO)private Integer id;private String name;private BigDecimal account;
}
// 2.Mapper接口
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
// 3.service
@Service
public class UserService {@Resourceprivate UserMapper userMapper;@Transactionalpublic void insertUser() {userMapper.insert(new User(null, "張三", new BigDecimal(100)));System.out.println("================業務操作--執行插入");//throw new RuntimeException("插入用戶失敗");}
}
//4.配置AOP 和 數據源 、事務管理器
@Aspect
@Component
public class MyAdvice {private static final String userExpression = "execution(* com.feng.springCaseAnalysis.service.UserService.*(..))";@Before(userExpression)public void beforeAdvice() {System.out.println("Before通知 -- UserService 之前");}@Around(userExpression)public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("環繞通知 -- UserService 之前");Object result = joinPoint.proceed();System.out.println("環繞通知 -- UserService 之后");return result;}
}
// 配置類
@Configuration
@MapperScan("com.feng.springCaseAnalysis.mapper")
@EnableTransactionManagement
public class DataProjectConfig {@Beanpublic DataSource dataSource() {// 創建數據源DruidDataSource dataSource = new DruidDataSource();dataSource.setUsername("root");dataSource.setPassword("123456");dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/spring_analysis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai");dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");dataSource.setInitialSize(5);return dataSource;}@Beanpublic MybatisSqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();factoryBean.setDataSource(dataSource);return factoryBean;}// 事務管理器@Beanpublic DataSourceTransactionManager transactionManager(DataSource dataSource) {DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();transactionManager.setDataSource(dataSource);return transactionManager;}
}//5.Main測試
@Configuration
@ComponentScan("com.feng.springCaseAnalysis") // spring-- Transactional 結合 自定義 Advice 分析
@EnableAspectJAutoProxy
public class Main {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);UserService userService = context.getBean("userService", UserService.class);try {userService.insertUser();} catch ( Exception e ) {System.out.println("【Exception!!!】" + e.getMessage());}} 
}

在上面的案例搭建好之后,我們仔細分析一下UserService的代理創建過程:

在這里插入圖片描述

來到為UserService尋找advisor的這里,讀者們還記得這在哪個階段嗎?然后進入里面就是List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);

在這里插入圖片描述

如上圖所示,在候選的Advisors中,除了我們自定義的兩個Advisor,還找到了另外一個 BeanFactoryTransactionAttributeSourceAdvisor,它也是一個PointCutAdvisor。在調用canApply( )方法的時候,會解析UserService所有方法上面有沒有@Transactional注解,并解析里面的屬性,這里insertUser方法有@Transactional注解,故匹配上了。

@Override
public boolean matches(Method method, Class<?> targetClass) {/*method: com.feng.springCaseAnalysis.service.UserService.insertUsertargetClass: class com.feng.springCaseAnalysis.service.UserService*/// 這里的tas是AnnotationTransactionAttributeSourceTransactionAttributeSource tas = getTransactionAttributeSource();return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}

所以elibibAdvisors都匹配上了,此時的elibibAdvisors里面有三個Advisor。然后創建代理對象,設置CallBack那些就同理了,如下圖所示。

在這里插入圖片描述

然后就返回代理對象咯。

在這里插入圖片描述

可以看到經過applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);之后,WrapperBean變成了UserService$$EnhancerBySpringCGLIB$$a768552c@4109代理對象了。

執行就很簡單了:

在這里插入圖片描述

遞歸調用嘛。

// TransactionInterceptor.java
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {// Work out the target class: may be {@code null}.// The TransactionAttributeSource should be passed the target class// as well as the method, which may be from an interface.Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);// Adapt to TransactionAspectSupport's invokeWithinTransaction...return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {@Override@Nullablepublic Object proceedWithInvocation() throws Throwable {return invocation.proceed();}@Overridepublic Object getTarget() {return invocation.getThis();}@Overridepublic Object[] getArguments() {return invocation.getArguments();}});
}
// invocation是一個函數式接口,proceedWithInvocation實際上就是上面的invocation.proceed(),這樣就完成了遞歸調用了
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {....PlatformTransactionManager ptm = asPlatformTransactionManager(tm);final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);Object retVal;try {// 調用下一層retVal = invocation.proceedWithInvocation();}catch (Throwable ex) { // 捕獲到異常了// 在里面判斷一些條件,然后事務回滾completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...TransactionStatus status = txInfo.getTransactionStatus();if (status != null && txAttr != null) {retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}}commitTransactionAfterReturning(txInfo); // 提交事務return retVal;}

5.思考題: @Import注解

本章案例見importCase包:

為什么在第四章案例中,多了那么多和Transactional的bean?在配置類@EnableTransactionManagement這個是干嘛的?

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class) // 有這個注解
public @interface EnableTransactionManagement {....
}

這個@Import注解的作用:@Import只能用在類上 ,@Import通過快速導入的方式實現把實例加入spring的IOC容器中。

①主要用法:

1.直接填class數組: bean名稱是該類的全類名

// 主測試類
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
Student bean = context.getBean(Student.class);
for (String name : context.getBeanDefinitionNames()) {System.out.println(name);
}
System.out.println("========");
System.out.println(bean);
// 配置類
@Configuration
@Import({Student.class})
public class Config {
}
// Student并沒有被Spring管理,
// 但是通過@Import導進去了
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {private String name = "asd";private Integer age = 10;
}

運行結果如下:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
main
config
com.feng.importCase.Student //看這里!!!!
org.springframework.aop.config.internalAutoProxyCreator
========
Student(name=asd, age=10)

2.ImportSelector方式【SpringBoot中用的很多】

@Configuration
//@Import({Student.class})
@Import({MyStudentImportSelector.class})
public class Config {
}//MyStudentImportSelector.java
public class MyStudentImportSelector implements ImportSelector {//參數: AnnotationMetadata表示當前被@Import注解給標注的所有注解信息@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{"com.feng.importCase.Student"};}
}

配置類改成導入Selector,需要導入的類,在該Selector中給出來,這樣就可以把我的Student類導進容器里面了。

3.ImportBeanDefinitionRegistrar方式

同樣是一個接口,類似于第二種ImportSelector用法,只不過這種用法更加地自定義化注冊

public class StudentImportRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {RootBeanDefinition beanDefinition = new RootBeanDefinition(Student.class);registry.registerBeanDefinition("haha-student", beanDefinition);}
}@Configuration
//@Import({Student.class})
//@Import({MyStudentImportSelector.class})
@Import({StudentImportRegistrar.class})
public class Config {
}

②源碼解析

@Import是在下面這一步起作用的。

 refresh()里面往下 ---->
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory); ---->
|    
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
|
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());

待讀者自行分析把。嘿嘿嘿

end.參考

  1. https://blog.csdn.net/qq_36882793/article/details/119823785 【cglib 的代理過程】
  2. https://www.cnblogs.com/yichunguo/p/12122598.html 【import】

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/76641.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/76641.shtml
英文地址,請注明出處:http://en.pswp.cn/web/76641.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Python網絡爬蟲開發】從基礎到實戰的完整指南

目錄 前言&#xff1a;技術背景與價值當前技術痛點解決方案概述目標讀者說明 一、技術原理剖析核心概念圖解核心作用講解關鍵技術模塊技術選型對比 二、實戰演示環境配置要求核心代碼實現&#xff08;10個案例&#xff09;案例1&#xff1a;基礎靜態頁面抓取案例2&#xff1a;動…

服務器監控軟件推薦

以下是幾款常用的服務器監控軟件推薦&#xff0c;涵蓋開源和商業方案&#xff0c;適用于不同規模和需求&#xff1a; 一、開源免費方案 Prometheus Grafana 特點&#xff1a;時序數據庫 可視化儀表盤&#xff0c;支持多維度監控和告警。適用場景&#xff1a;云原生、Kubernet…

編譯原理實驗(四)———— LR(1)分析法

一、實驗目的 掌握LR(1)分析法的基本原理與實現流程。通過構造LR(1)分析表&#xff0c;驗證符號串是否符合給定文法規則。理解LR(1)分析中向前搜索符&#xff08;Lookahead Symbol&#xff09;的作用&#xff0c;解決移進-歸約沖突。 二、實驗題目 1.對下列文法&#xff0c;用…

vue3 主題模式 結合 element-plus的主題

vue3 主題模式 結合 element-plus的主題 npm i element-plus --save-dev在 Vue 3 中&#xff0c;實現主題模式主要有以下幾種方式 1.使用 CSS 變量&#xff08;自定義屬性&#xff09; CSS 變量是一種在 CSS 中定義可重用值的方式。在主題模式中&#xff0c;可以將顏色、字體…

科大訊飛Q1營收46.6億同比增長27.7%,扣非凈利同比增長48.3%

4月21日盤后&#xff0c;AI龍頭科大訊飛&#xff08;002230.SZ&#xff09;發布2024年報&#xff0c;公司全年實現營業收入233.43億元&#xff0c;同比增長18.79%&#xff0c;同期歸母凈利潤為5.6億元。 公司核心賽道業務保持快速增長&#xff0c;消費者、教育、汽車、醫療業務…

Day5-UFS總結

UFS 傳輸協議的本質&#xff1a;兩個收發器件&#xff0c;對需要傳輸的數據&#xff0c;一層一層的封裝和解析&#xff0c;利用封裝增加的額外信息&#xff0c;做一些數據處理&#xff0c;完成源地址到目標地址的數據傳輸功能。 應用協議的本質&#xff1a;基于某種傳輸協議之…

嵌入式工程師( C / C++ )筆試面試題匯總

注&#xff1a;本文為 “嵌入式工程師筆試面試題” 相關文章合輯。 未整理去重。 如有內容異常&#xff0c;請看原文。 嵌入式必會 C 語言筆試題匯總 Z 沉浮 嵌入式之旅 2021 年 01 月 19 日 00:00 用預處理指令 #define 聲明一個常數&#xff0c;用以表明 1 年中有多少秒&a…

29-JavaScript基礎語法(函數)

知識目標 理解函數的基本概念&#xff1b;掌握函數的定義和調用&#xff1b;理解函數參數和返回值及作用域&#xff1b;掌握函數高階用法。 1. 理解函數的基本概念 明確函數在 JavaScript 里是一段可重復使用的代碼塊&#xff0c;它能接收輸入參數&#xff0c;執行特定任務&…

AI答題pk機器人來襲

AI答題PK機器人是一種具備知識問答競賽功能的人工智能程序。以下為您詳細介紹&#xff1a; 一、實時對戰&#xff1a;能在答題排位PK升級賽中&#xff0c;與用戶進行1V1在線實時PK答題 。比如在一些知識競賽類APP中&#xff0c;用戶可匹配到AI機器人對手&#xff0c;在規定時…

PclSharp ——pcl的c#nuget包

簡介&#xff1a; NuGet Gallery | PclSharp 1.8.1.20180820-beta07 下載.NET Framework 4.5.2 Developer Pack&#xff1a; 下載 .NET Framework 4.5.2 Developer Pack Offline Installer 離線安裝nupkg&#xff1a; nupkg是visual studio 的NuGet Package的一個包文件 安…

【Unity筆記】Unity音視頻播放監聽器封裝筆記:VideoPlayer + AudioSource事件觸發與編輯器擴展

關鍵點 Unity VideoPlayer 播放結束事件Unity AudioSource 播放檢測 Unity音視頻播放監聽器封裝筆記&#xff1a;VideoPlayer AudioSource事件觸發與編輯器擴展 在 Unity 的多媒體開發中&#xff0c;我們經常需要監聽 VideoPlayer 或 AudioSource 的播放狀態&#xff0c;以便…

WPF常用技巧匯總

主要用于記錄工作中發現的一些問題和常見的解決方法。 此文會持續更新。 >abp new Evan.MyWpfApp -t wpf --old --framework .net8 1. 解決不同屏幕分辨率下的鋸齒問題 UseLayoutRounding"True" <Grid UseLayoutRounding"True"><Border Mar…

分數線降低,25西電馬克思主義學院(考研錄取情況)

1、馬克思主義學院各個方向 2、馬克思主義學院近三年復試分數線對比 學長、學姐分析 由表可看出&#xff1a; 1、馬克思主義理論25年相較于24年下降10分&#xff0c;為355分 3、25vs24推免/統招人數對比 學長、學姐分析 由表可看出&#xff1a; 1、 馬克思主義學院25年共接…

【Linux網絡】構建UDP服務器與字典翻譯系統

&#x1f4e2;博客主頁&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客倉庫&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;歡迎點贊 &#x1f44d; 收藏 ?留言 &#x1f4dd; 如有錯誤敬請指正&#xff01; &…

【項目管理】成本類計算 筆記

項目管理-相關文檔&#xff0c;希望互相學習&#xff0c;共同進步 風123456789&#xff5e;-CSDN博客 &#xff08;一&#xff09;知識總覽 項目管理知識域 知識點&#xff1a; &#xff08;項目管理概論、立項管理、十大知識域、配置與變更管理、績效域&#xff09; 對應&…

div(HTML標準元素)和view(微信小程序專用組件)的主要區別體

div&#xff08;HTML標準元素&#xff09;和view&#xff08;微信小程序專用組件&#xff09;的主要區別體現在以下方面&#xff1a; 一、應用場景與開發框架 ?適用平臺不同? div是HTML/CSS開發中通用的塊級元素&#xff0c;用于Web頁面布局?&#xff1b;view是微信小程序專…

【C++軟件實戰問題排查經驗分享】UI界面卡頓 | CPU占用高 | GDI對象泄漏 | 線程堵塞 系列問題排查總結

目錄 1、UI界面卡頓問題排查 2、軟件CPU占用高問題排查 3、UI界面顯示異常&#xff08;GDI對象泄漏導致窗口繪制異常&#xff09;問題排查 4、軟件線程堵塞&#xff08;包含線程死鎖&#xff09;問題排查 5、最后 C軟件異常排查從入門到精通系列教程&#xff08;核心精品專…

管理雜談——采石磯大捷的傳奇與啟示

南宋抗金史上&#xff0c;岳飛與岳家軍的鐵血傳奇家喻戶曉&#xff0c;但另一位力挽狂瀾的“文官戰神”卻常被忽視——他從未掌兵&#xff0c;卻在南宋存亡之際整合潰軍&#xff0c;以少勝多&#xff0c;締造采石磯大捷。此人正是虞允文。一介書生何以扭轉乾坤&#xff1f;他的…

動態規劃-零錢兌換

332.零錢兌換 給你一個整數數組 coins &#xff0c;表示不同面額的硬幣&#xff1b;以及一個整數 amount &#xff0c;表示總金額。計算并返回可以湊成總金額所需的 最少的硬幣個數 。如果沒有任何一種硬幣組合能組成總金額&#xff0c;返回 -1 。你可以認為每種硬幣的數量是無…

SpringAI+DeepSeek大模型應用開發——4 對話機器人

目錄??????? ??????????????項目初始化 pom文件 配置模型 ChatClient 同步調用 流式調用 日志功能 對接前端 解決跨域 會話記憶功能 ChatMemory 添加會話記憶功能 會話歷史 管理會話id 保存會話id 查詢會話歷史 完善會話記憶 定義可序列…