javascript
Spring Aop 组件概述
AOP(Aspect-Oriented Programming) 面向切面編程, 這種編程模型是在 OOP(Object-Oriented Programming) 的基礎上誕生的一種編程模型, 我們可以簡單的將其理解為: 在程序中具有公共特性的某些類/某些方法上進行攔截, 在方法執行的前面/后面/執行結果返回后 增加執行一些方法 (PS: 對, 就這么簡單, 最起碼 Spring 中是這樣的); 在 Spring 中主要通過了CGLib|動態代理來實現 -> CGLib(通過實現一個子類來控制對原始類的方法調用) 與 Proxy(創建一個 Proxy 類, 內部通過反射來調用原始類) 來實現 <-- 這兩個實現類都可以通過 HSDB 來獲取! , 對于 aop 主要有以下組件
Pointcut: 這個類位于 “org.springframework.aop” 包中, 它的主要作用還是定義切面的匹配點, 在 Spring Aop 中匹配的點主要是 class 與 method 這兩個方面, 分別為 ClassFilter 與 MethodFilter
// 由 ClassFilter 與 MethodMatcher 組成的 pointcut public interface Pointcut {// 類過濾器, 可以知道哪些類需要攔截ClassFilter getClassFilter();// 方法匹配器, 可以知道哪些方法需要攔截MethodMatcher getMethodMatcher();// 匹配所有對象的 PointcutPointcut TRUE = TruePointcut.INSTANCE; }上面的接口中定義了 Pointcut 的主要方法, 從中我們可以看出, Pointcut 的匹配主要是根據 class 與 method 來進行匹配的; 在 Spring 中主要有以下幾類:
@Transactional 標簽來確定方法是否匹配; 而 AspectJExpressionPointcut 其實就是 AspectJ
包中的工具類來進行匹配
Advice: 建議忠告, 勸告, 通知, 這其實最開始是 aopalliance 包中的一個空接口, 接口的存在主要是為了標示對應類為 Advice; 而在Spring Aop 中 Advice 其實表示的是在 Pointcut 點上應該執行的方法, 而這個方法可以在 Pointcut 的前面/后面/包裹 Pointcut/Pointcut拋出異常時/整個 Pointcut 執行成功返回時執行 <- 有時語言不能完全解釋代碼所想表達的意思, 那我們直接來看代碼吧!
- Advice: 其主要分成兩類: 普通advice 與 Interceptor/MethodInterceptor,
- 通常Spring是將被AspectJ 標注的方法解析成各種 Advice(BeforeAdvice, AfterAdvice, ThrowingAdvice, AfterReturingAdvice 或 AspectJMethodBeforeAdvice, AspectJAfterAdvice,
AspectJAfterReturningAdvice, AspectJAfterThrowingAdvice, AspectJAroundAdvice)
上面的 Advice 類別概述, 而又有以下說明:
Advisor 其實它就是 Pointcut 與 Advice 的組合, Advice 是執行的方法, 而要知道方法何時執行, 則 Advice 必需與 Pointcut 組合在一起, 這就誕生了 Advisor 這個類(PS: 你可以嘗試這樣理解 -> Advice表示建議,Pointcut表示建議的地點, Advisor表示建議者, 建議的者擁有建議的功能, 并且知道在哪兒建議 <- 純屬于歪歪), 主要有以下幾個類:
AspectJAfterReturningAdvice, AspectJAfterThrowingAdvice,
AspectJAroundAdvice, Pointcut 則是AspectJExpressionPointcut), 對于這個類的解析是在
ConfigBeanDefinitionParser
ReflectiveAspectJAdvisorFactory 來解析生成的(與之對應的 Advice 是
AspectJMethodBeforeAdvice, AspectJAfterAdvice,
AspectJAfterReturningAdvice, AspectJAfterThrowingAdvice,
AspectJAroundAdvice, Pointcut 則是AspectJExpressionPointcut), 解析的步驟是:
AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors() ->
BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors() ->
ReflectiveAspectJAdvisorFactory.getAdvisors() ->
ReflectiveAspectJAdvisorFactory.getAdvisor() 最終生成了
InstantiationModelAwarePointcutAdvisorImpl (當然包括里面的 Pointcut 與 advice
也都是由 ReflectiveAspectJAdvisorFactory 解析生成的)
AnnotationTransactionAttributeSource, TransactionInterceptor
組合起來進行事務的操作(PS: AnnotationTransactionAttributeSource 主要是解析方法上的
@Transactional注解, TransactionInterceptor 是個 MethodInterceptor,
是正真操作事務的地方, 而BeanFactoryTransactionAttributeSourceAdvisor其實起著組合它們的作用); 與之相似的還有 BeanFactoryCacheOperationSourceAdvisor
Advised: 已經被建議的對象(主要是代理生成的對象與AdvisedSupport), 其中蘊含了:
// 這個 Advised 接口的實現著主要是代理生成的對象與AdvisedSupport (Advised的支持器) public interface Advised extends TargetClassAware {// 這個 frozen 決定是否 AdvisedSupport 里面配置的信息是否改變boolean isFrozen();// 是否代理指定的類, 而不是一些 Interfaceboolean isProxyTargetClass();// 返回代理的接口Class<?>[] getProxiedInterfaces();// 判斷這個接口是否是被代理的接口boolean isInterfaceProxied(Class<?> intf);// 設置代理的目標對象void setTargetSource(TargetSource targetSource);// 獲取代理的對象TargetSource getTargetSource();// 判斷是否需要將 代理的對象暴露到 ThreadLocal中, 而獲取對應的代理對象則通過 AopContext 獲取void setExposeProxy(boolean exposeProxy);// 返回是否應該暴露 代理對象boolean isExposeProxy();// 設置 Advisor 是否已經在前面過濾過是否匹配 Pointcut (極少用到)void setPreFiltered(boolean preFiltered);// 獲取 Advisor 是否已經在前面過濾過是否匹配 Pointcut (極少用到)boolean isPreFiltered();// 獲取所有的 AdvisorAdvisor[] getAdvisors();// 增加 Advisor 到鏈表的最后void addAdvisor(Advisor advisor) throws AopConfigException;// 在指定位置增加 Advisorvoid addAdvisor(int pos, Advisor advisor) throws AopConfigException;// 刪除指定的 Advisorboolean removeAdvisor(Advisor advisor);// 刪除指定位置的 Advisorvoid removeAdvisor(int index) throws AopConfigException;// 返回 Advisor 所在位置de indexint indexOf(Advisor advisor);// 將指定的兩個 Advisor 進行替換boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;// 增加 Advice <- 這個Advice將會包裹成 DefaultPointcutAdvisorvoid addAdvice(Advice advice) throws AopConfigException;// 在指定 index 增加 Advice <- 這個Advice將會包裹成 DefaultPointcutAdvisorvoid addAdvice(int pos, Advice advice) throws AopConfigException;// 刪除給定的 Adviceboolean removeAdvice(Advice advice);// 獲取 Advice 的索引位置int indexOf(Advice advice);// 將 ProxyConfig 通過 String 形式返回String toProxyConfigString(); }其實它主要完成的是以下幾點:
1. Proxy代理的Interface 2. 代理的對象 TargetSource 3. 包裹這個 Advised 的Advisor, 以及對它的增刪 4. 對 Advice 的增刪 而其主要的實現者是:1. AdvisedSupport: 這個類內部是 List<Advisor>, Advisor[], interfaces, DefaultAdvisorChainFactory(判斷 Advisor 是否適合當前的方法), targetSource 2. ProxyCreatorSupport: 這個類繼承 AdvisedSupport, 但是在這個類中主要還是通過 AopProxyFactory 來完成代理對象的創建, 見 ProxyCreatorSupport.createAopProxy() 3. ProxyFactory: 這個類通過構造函數中的 proxyInterface/interceptor/targetSource 來創建代理對象(這個類是編程式 AOP 中最常用的對象) 4. ProxyFactoryBean: 這個類是基于 FactoryBean 的 Proxy創建形式, 其通過代理的 Interface, targetSource 與指定的 interceptorNames 來創建對應的AopProxy, 最后生成對應的代理對象 5. AspectJProxyFactory: 將一個被 @AspectJ 注解標示的類丟入其中, 變創建了對應的代理對象 (這個類現在已經很少用了, 關于如何使用, 可以看這里(http://elim.iteye.com/blog/2397922))TargetSource: 這個類的概念其實是動態代理作用的對象, 在 Spring 中又可以分為:
1. HotSwappableTargetSource: 進行線程安全的熱切換到對另外一個對象實施動態代理操作 2. AbstractPoolingTargetSource: 每次進行生成動態代理對象時都返回一個新的對象 3. ThreadLocalTargetSource: 為每個進行請求的線程維護一個對象的 TargetSource 4. SingletonTargetSource: 最普遍最基本的單例 TargetSource, 在 Spring 中生成動態代理對象, 一般都是用這個 TargetSourceAdvisorChainFactory: 這個接口主要定義了從 Advised中獲取 Advisor 并判斷其是否與 對應的 Method 相匹配, 最終返回的是MethodInterceptor; 其中對 Advisor 轉化成 MethodInterceptor 的工作都是交由 DefaultAdvisorAdapterRegistry 來完成, 下面就是其主邏輯:
// 獲取匹配 targetClass 與 method 的所有切面的通知 @Override public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Advised config, Method method, Class<?> targetClass) {// This is somewhat tricky... We have to process introductions first,// but we need to preserve order in the ultimate list.List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length); // PS: 這里 config.getAdvisors 獲取的是 advisors 是數組Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());boolean hasIntroductions = hasMatchingIntroductions(config, actualClass); // 判斷是有 IntroductionAdvisor 匹配到// 下面這個適配器將通知 [Advice] 包裝成攔截器 [MethodInterceptor]; 而 DefaultAdvisorAdapterRegistry則是適配器的默認實現AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();for (Advisor advisor : config.getAdvisors()) { // 獲取所有的 Advisorif (advisor instanceof PointcutAdvisor) { // advisor 是 PointcutAdvisor 的子類// Add it conditionally.PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;// 判斷此切面 [advisor] 是否匹配 targetClass (PS: 這里是類級別的匹配)if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {/** 通過對適配器將通知 [Advice] 包裝成 MethodInterceptor, 這里為什么是個數組? 因為一個通知類* 可能同時實現了前置通知[MethodBeforeAdvice], 后置通知[AfterReturingAdvice], 異常通知接口[ThrowsAdvice]* 環繞通知 [MethodInterceptor], 這里會將每個通知統一包裝成 MethodInterceptor*/MethodInterceptor[] interceptors = registry.getInterceptors(advisor);MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();// 是否匹配 targetClass 類的 method 方法 (PS: 這里是方法級別的匹配)if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {if (mm.isRuntime()) { // 看了對應的所有實現類, 只有 ControlFlowPointcut 與 AspectJExpressionPointcut 有可能 返回 true// Creating a new object instance in the getInterceptors() method// isn't a problem as we normally cache created chains.// 如果需要在運行時動態攔截方法的執行則創建一個簡單的對象封裝相關的數據, 它將延時// 到方法執行的時候驗證要不要執行此通知for (MethodInterceptor interceptor : interceptors) {interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm)); // 里面裝的是 Advise 與 MethodMatcher}}else {interceptorList.addAll(Arrays.asList(interceptors));}}}}else if (advisor instanceof IntroductionAdvisor) { // 這里是 IntroductionAdvisor// 如果是引入切面的話則判斷它是否適用于目標類, Spring 中默認的引入切面實現是 DefaultIntroductionAdvisor 類// 默認的引入通知是 DelegatingIntroductionInterceptor 它實現了 MethodInterceptor 接口sIntroductionAdvisor ia = (IntroductionAdvisor) advisor;if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {Interceptor[] interceptors = registry.getInterceptors(advisor);interceptorList.addAll(Arrays.asList(interceptors));}}else {Interceptor[] interceptors = registry.getInterceptors(advisor);interceptorList.addAll(Arrays.asList(interceptors));}}return interceptorList; }DefaultAdvisorAdapterRegistry: 這里類里面存在著 MethodBeforeAdviceAdapter, AfterReturningAdviceAdapter, ThrowsAdviceAdapter 。 這三個類是將 Advice適配成MethodInterceptor 的適配類; 而其本身具有兩個重要的功能:
AopProxyFactory: 這個接口中定義了根據 AdvisedSupport 中配置的信息來生成合適的AopProxy (主要分為 基于Java 動態代理的 JdkDynamicAopProxy 與基于 Cglib 的ObjenesisCglibAopProxy), 主邏輯如下:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {// 啟用了 優化配置(原因就是 cglib 的性能比 JDK Proxy 要好) || 啟用了直接代理目標類模式 || 沒有指定要代理的接口(除了 接口SpringProxy)if (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.");}// 代理的對象是否是接口 或 targetClass 是否是 java.lang.reflect.Proxy 的子類if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config);}// 創建 cglib 代理的工廠對象return new ObjenesisCglibAopProxy(config);}else {// 返回創建 JDK 代理的工廠對象return new JdkDynamicAopProxy(config);} }PS: 這里補充個知識點 Cglib 生成代理類的速度比 Proxy 生成的慢(幾乎到10倍數), 但其執行速度是 Proxy 的 10倍 (可能基于不同版本的 jar 包測出的數據會稍有不同),而在我們日常使用中 JdkDynamicAopProxy 是最常使用的, 下面直接看一下其執行的主邏輯
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MethodInvocation invocation;Object oldProxy = null;boolean setProxyContext = false;// 目標對象的包裝類, 通過 AdvisedSupport的setTarget方法設置的會自動封閉成 TargetSource 的實現 SingletonTargetSourceTargetSource targetSource = this.advised.targetSource;Class<?> targetClass = null;Object target = null;try {// 被代理的接口中沒有定義 equals 方法且目前方法是 equals 方法, 則調用 equals 方法比較兩代對象所代理的接口if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {// The target does not implement the equals(Object) method itself.// 如果目標對象沒有實現 object 類的基礎方法 euqalsreturn equals(args[0]);}// hashCode 方法的處理else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {// The target does not implement the hashCode() method itself.// 如果目標對象沒有實現 object 類的基礎方法 hashCodereturn hashCode();}// 如果調用的方法是 DecoratingProxy聲明的else if (method.getDeclaringClass() == DecoratingProxy.class) {// There is only getDecoratedClass() declared -> dispatch to proxy config.return AopProxyUtils.ultimateTargetClass(this.advised);}/*** Class類的 isAssignableFrom(Class cls) 方法:* 自身類.class.isAssignableFrom(自身類或子類.class) 返回 true** 對 Advised 接口或者子接口中的方法的調用不經過任何攔截器, 直接委托給Advised 對象中的方法* (此 if 塊 的目的是實現將 advised 對象引入代理對象), this.advised.opaque 默認情況下是 false(它只是一個開關* 選項, 控制代理對象是否可以操作 advised)*/else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {// Service invocations on ProxyConfig with the proxy config...// 調用 advised 的method 方法return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);}Object retVal;// 當目標對象內部的自我調用無法實施切面中的增強則需要增強則需要通過此屬性暴露代理if (this.advised.exposeProxy) {// 把當前代理對象放到 AopContext 中(其內部使用 ThreadLocal 存著), 并返回上下文中原來的代理對象, 并且保留之前暴露設置的代理// Make invocation available if necessary.oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}// May be null. Get as late as possible to minimize the time we "own" the target,// in case it comes from a pool.// 得到目標對象target = targetSource.getTarget();if (target != null) {targetClass = target.getClass();}// 獲取當前方法的攔截器鏈// Get the interception chain for this method.List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);// Check whether we have any advice. If we don't, we can fallback on direct// reflective invocation of the target, and avoid creating a MethodInvocation.// 檢測是否含有 MethodInterceptorif (chain.isEmpty()) {// 沒有任何攔截器需要執行則直接執行目標對象方法// We can skip creating a MethodInvocation: just invoke the target directly// Note that the final invoker must be an InvokerInterceptor so we know it does// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);}else {/*** 如果有攔截器的設定, 那么需要調用攔截器之后才調用目標對象的相應方法* 通過 構造一個 ReflectiveMethodInvocation 來實現, 下面會看* 這個 ReflectiveMethodInvocation 類的具體實現*/// We need to create a method invocation...invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);// Proceed to the joinpoint through the interceptor chain.// 執行攔截器鏈retVal = invocation.proceed();}// Massage return value if necessary.Class<?> returnType = method.getReturnType();// 處理返回目標對象本身的情況, 也許某些方法是返回this引用, 此時需要返回代理對象而不是目標對象if (retVal != null && retVal == target &&returnType != Object.class && returnType.isInstance(proxy) &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {// Special case: it returned "this" and the return type of the method// is type-compatible. Note that we can't help if the target sets// a reference to itself in another returned object.retVal = proxy;}else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { // 從這里我們可以發現, 攔截器的返回值若設定的不是 null, 但是 你主動設置為 null, 則將會報出異常throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);}return retVal;}finally {if (target != null && !targetSource.isStatic()) {// Must have come from TargetSource.// 如果此 targetSource 不是一個靜態的 targetSource, 那么釋放此 target, 默認的 SingletonTargetSource.isStatic 方法是 true 的targetSource.releaseTarget(target);}if (setProxyContext) {// Restore old proxy.// 還原之前的代理對象AopContext.setCurrentProxy(oldProxy);}} }MethodInvocation 是進行 invoke 對應方法/MethodInterceptor的類, 其主要分成用于 Proxy 的 ReflectiveMethodInvocation, 與用于 Cglib 的 CglibMethodInvocation; ReflectiveMethodInvocation 其實就是遞歸的調用 MethodInterceptor, 當沒有 MethodInterceptor可以調用時, 則執行對應的切面點的方法, 如下:
/*** 實現前置增強在目標方法前調用, 后置增強在目標方法后調用*/ @Override public Object proceed() throws Throwable {// 執行完所有增強執行切點方法// currentInterceptorIndex 默認等于 -1 的, 它記錄著當前執行到了哪個攔截器// interceptorsAndDynamicMethodMatchers 代表著匹配了的 MethodInterceptor// We start with an index of -1 and increment early.if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {// 如果所有的 攔截器都執行完了的話, 則調用 invokeJoinPoint 方法去執行目標對象的目標方法 (反射)return invokeJoinpoint();}// 得到當前要執行的攔截器(攔截器是順序執行的 )Object interceptorOrInterceptionAdvice =this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);// 下面判斷當前攔截器是不是一個動態攔截器if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { // 其實在 Spring 中 主要的實現類是 AspectJExpressionPointcut// 動態 匹配 方法 攔截// Evaluate dynamic method matcher here: static part will already have// been evaluated and found to match.InterceptorAndDynamicMethodMatcher dm =(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;// 這里調用 MethodMatcher 類中帶3個參數的 matches 方法if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {// 匹配目標類的目標方法后執行攔截器return dm.interceptor.invoke(this);}else {// 遞歸調用, 下一個攔截器或目標類的方法// Dynamic matching failed.// Skip this interceptor and invoke the next in the chain.return proceed();}}else {// 將 this作為參數(MehtodInvocation)傳遞以保證當前實例中調用鏈的執行// It's an interceptor, so we just invoke it: The pointcut will have// been evaluated statically before this object was constructed.return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);} }AbstractAutoProxyCreator: 這個類是聲明式 Aop 編程中非常重要的一個角色, 我們可以從以下幾點來解析它:
1. AbstractAutoProxyCreator繼承了 ProxyProcessorSupport, 所以它具有了ProxyConfig中動態代理應該具有的配置屬性 2. AbstractAutoProxyCreator 實現了 SmartInstantiationAwareBeanPostProcessor(包括實例化的前后置函數, 初始化的前后置函數) 并進行了實現 3. 實現了 創建代理類的主方法 createProxy 方法 4. 定義了抽象方法 getAdvicesAndAdvisorsForBean(獲取 Bean對應的 Advisor) AbstractAutoProxyCreator 中等于是構建了創建 Aop 對象的主邏輯, 而其子類 AbstractAdvisorAutoProxyCreator 實現了getAdvicesAndAdvisorsForBean 方法, 并且通過工具類 BeanFactoryAdvisorRetrievalHelper(PS: 它的方法findAdvisorBeans中實現類獲取容器中所有 Advisor 的方法) 來獲取其對應的 Advisor;AbstractAutoProxyCreator 的子類中主要有以下幾個:
1. AspectJAwareAdvisorAutoProxyCreator: 通過解析 aop 命名空間的配置信息時生成的 AdvisorAutoProxyCreator, 主要通過ConfigBeanDefinitionParser.parse() -> ConfigBeanDefinitionParser.configureAutoProxyCreator() -> AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary() -> AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(); 與之對應的 Pointcut 是AspectJExpressionPointcut, Advisor 是 AspectJPointcutAdvisor, Advice 則是 AspectJAfterAdvice, AspectJAfterReturningAdvice, AspectJAfterThrowingAdvice, AspectJAroundAdvice 2. AnnotationAwareAspectJAutoProxyCreator: 這是基于 @AspectJ注解生成的 切面類的一個 AbstractAutoProxyCreator, 解析額工作交給了 AspectJAutoProxyBeanDefinitionParser, 步驟如下AspectJAutoProxyBeanDefinitionParser.parse() -> AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary() -> AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary() 3. DefaultAdvisorAutoProxyCreator: 這個類也是 AbstractAutoProxyCreator的子類, 它帥選作用的類時主要是根據其中的 advisorBeanNamePrefix(類名前綴)配置進行判斷 4. BeanNameAutoProxyCreator: 通過類的名字來判斷是否作用(正則匹配)本篇主要介紹了 Spring-Aop 中的主要構建, 并進行相應的說明, 單純的講 aop 這個概念其實不復雜, 而在 Spring 中, 主要表現在如下幾點:
https://www.jianshu.com/p/de624a4190c6
總結
以上是生活随笔為你收集整理的Spring Aop 组件概述的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 产品经理装逼词汇大全
- 下一篇: gradle idea java ssm