動態AOP自定義標籤
前言
之前的文章講述過自定義註解,如果聲明瞭自定義的註解,那麼就一定會在程式的某個地方註冊了對應的解析器。我們發現在AopNamespaceHandler中的init函式:
public class AopNamespaceHandler extends NamespaceHandlerSupport { /** * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the * '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}' * and '{@code scoped-proxy}' tags. */ @Override public void init() { // In 2.0 XSD as well as in 2.1 XSD. registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser()); registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser()); registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator()); // Only in 2.0 XSD: moved to context namespace as of 2.1 registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); } }
從上述的程式碼中可以看出,在解析配置檔案的時候,一旦遇到aspectj-autoproxy註解時就會使用解析器AspectJAutoProxyBeanDefinitionParser進行解析,那麼我們來看一下這個的內部實現。
註冊AnnotationAwareAspectJAutoProxyCreator
所有解析器,因為是對BeanDefinitionParser介面的統一實現,入口都是從parse函式開始的,AspectJAutoProxyBeanDefinitionParser的parse函式如下:
public BeanDefinition parse(Element element, ParserContext parserContext) { //註冊AnnotationAwareAspectJAutoProxyCreator AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element); //對於註解中的子類的處理 extendBeanDefinition(element, parserContext); return null; }
其中registerAspectJAnnotationAutoProxyCreatorIfNecessary函式是我們比較關心的,也是關鍵邏輯的實現:
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary( ParserContext parserContext, Element sourceElement) { //註冊或者升級AutoProxy定義beanName為org.SpringFramework.aop.config.internalAutoProxyCreator的BeanDefinition BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary( parserContext.getRegistry(), parserContext.extractSource(sourceElement)); //對於proxy-target-class以及expose-proxy屬性的處理 useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement); //註冊元件並通知,便於監聽器做進一步處理,其中,BeanDefinition的className為AnnotationAwareAspectJAutoProxyCreator registerComponentIfNecessary(beanDefinition, parserContext); }
在這個函式中主要完成了三件事情,基本上述程式碼每一行就是一個完整的邏輯。
1.註冊或者升級AnnotationAwareAspectJAutoProxyCreator
對於AOP的實現,基本上都是靠AnnotationAwareAspectJAutoProxyCreator去完成,它可以根據@Point註解定義的切點來自動代理相匹配的bean。但是為了配置簡單,Spring使用了自定義配置來幫助我們自動註冊AnnotationAwareAspectJAutoProxyCreator,其註冊過程就是在以下方法實現的:
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary( BeanDefinitionRegistry registry, @Nullable Object source) { return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source); }
private static BeanDefinition registerOrEscalateApcAsRequired( Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); //如果存在自動代理建立器且存在的自動代理建立器與現在的不一致,那麼需要根據優先順序來判斷到底需要使用哪個 if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); if (!cls.getName().equals(apcDefinition.getBeanClassName())) { int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); int requiredPriority = findPriorityForClass(cls); if (currentPriority < requiredPriority) { //改變bean最重要的就是改變bean所對應的className屬性 apcDefinition.setBeanClassName(cls.getName()); } } //如果已經存在自動代理建立器並且與將要建立的一致,那麼無需再建立 return null; } RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); beanDefinition.setSource(source); beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); return beanDefinition; }
以上程式碼實現了自動註冊AnnotationAwareAspectJAutoProxyCreator類的功能,同時這裡還涉及了一個優先順序的問題,如果已經存在了自動代理建立器,而且存在的自動代理建立器與現在的不一致,那麼要根據優先順序來判斷到底需要使用哪個。
2.處理proxy-target-class以及expose-proxy屬性
useClassProxyingIfNecessary實現了proxy-target-class屬性以及expose-proxy屬性的處理。
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) { if (sourceElement != null) { //對於proxy-target-class屬性的處理 boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE)); if (proxyTargetClass) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } //對於expose-proxy屬性的處理 boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE)); if (exposeProxy) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } } }
//強制使用過程其實也是一個屬性設定的過程 public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) { if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); definition.getPropertyValues().add("exposeProxy", Boolean.TRUE); } }
❤ proxy-target-class:SpringAOP部分使用JDK動態代理或者CGLIB來為目標物件建立代理(建議儘量使用JDK的動態代理)。如果被代理的目標物件實現了至少一個介面,則會使用JDK動態代理,並且所有該目標型別實現的介面都將被代理。 若該目標物件沒有實現任何介面,則會建立一個CGLIB代理。如果你希望強制使用CGLIB代理(例如希望代理目標物件的所有方法,而不是隻是實現自介面的方法),那麼也可以,但是需要考慮下面兩個問題:
1.無法通知(advise)Final方法,因為它們不能被覆寫;
2.你需要將CGLIB二進位制發行包放在classpath下面;
與之相較,JDK本身就提供了動態代理,強制使用CGLIB代理需要將<aop:config>的proxy-target-class屬性設為true:
<aop:config proxy-target-class="true">...</aop:config>
當需要使用CGLIB代理和@AspectJ自動代理支援,可以按照下面的方式設定<aop:aspectj-autoproxy>的proxy-target-class屬性:
<aop:aspectj-autoproxy proxy-target-class="true"/>
而實際的使用的過程中才會發現細節問題的差別。
❤ JDK動態代理:其代理物件必須是某個介面的實現,它是通過在執行期間建立一個介面的實現類來完成對目標物件的代理。
❤ CGLIB代理:實現原理類似於JDK動態代理,只是它在執行期間生成的代理物件是針對目標類擴充套件的子類。CGLIB是高效的程式碼生成包,底層是依靠ASM(開源的Java位元組碼編輯類庫)操作位元組碼實現的,效能比JDK強。
❤ expose-proxy:有時候目標物件內部的自我呼叫將無法實施切面中的增強,如下所示:
public interface AService { public void a(); public void b(); } @Service() public class AServicelmpll implements AService { @Transactional(propagation = Propagation.REQUIRED) public void a() { this.b{); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void b() { } }
此處的this指向目標物件,因此呼叫this.b()將不會執行b事物切面,即不會執行事物增強,因此b方法的事物定義“@Transactional(propagation = Propagation.REQUIRES_NEW)”將不會實施,為了解決這個問題,我們可以這樣做:
<aop:aspectj-autoproxy expose-proxy="true"/>
然後將以上程式碼中的“this.b()”修改為“((AService)AopContext.currentProxy()).b();”,即可。通過以上的修改便可以完成對a和b方法的同時增強。
3.註冊元件並通知,便於監聽器做進一步的處理
private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) { if (beanDefinition != null) { parserContext.registerComponent( new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME)); } }
參考:《Spring原始碼深度解析》 郝佳 編著: