1. 程式人生 > >spring aop自動代理註解配置失效問題及原理機制整理總結

spring aop自動代理註解配置失效問題及原理機制整理總結

迭代中遇到的問題處理

問題:用自動代理註解配置攔截了PlanService類的方法testAopFace,方法testAopFace被PlanService類的方法query呼叫。http請求controller後呼叫了方法query,aop對方法testAopFace攔截失效。

 



問題原因:

aop配置攔截了方法testAopFace後,在專案執行啟動時PlanService類會被CglibAopProxy生成代理物件planServiceEhancer。訪問代理物件planServiceEhancer的每個方法(在例子中訪問了代理物件的事務方法query)都會被攔截,進入到intercept方法處理:


根據被代理的目標類和呼叫的方法去查詢攔截鏈:

List<Object> chain =this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);


說明註解配置的攔截處理並沒有出現在攔截鏈裡,因為當前的方法query並不是aop註解配置的攔截方法testAopFace。

經過一系列的攔截轉發,在進入真正的query方法體時,會利用反射呼叫methodProxy的invoke來進入真正的query方法體,第一個引數例項是被代理的目標物件,而不是代理物件就造成了query方法體裡執行testAopFace();呼叫了被代理的目標物件的方法而不是代理物件的方法導致了攔截失效。



spring aop自動代理註解例項

1.   在配置檔案中新增<aop:aspectj-autoproxy/>配置

2.   建立一個Java檔案,使用@Component 、@Aspect註解修飾該類

3.   建立一個方法,使用@Before、@After、@Around等進行修飾,在註解中寫上切入點的表示式

1. import org.aspectj.lang.ProceedingJoinPoint;  

2. import org.aspectj.lang.annotation.After;  

3. import org.aspectj.lang.annotation.AfterThrowing;  

4. import org.aspectj.lang.annotation.Around;  

5. import org.aspectj.lang.annotation.Aspect;  

6. import org.aspectj.lang.annotation.Before;  

7. import org.springframework.stereotype.Component;  

8.   

9. /** 

10. * 基於註解的AOP日誌示例 

11. * @author ZYWANG 2011-3-24 

12. */  

13.@Component  

14.@Aspect  

15.public class AopLog {  

16.      

17.    //方法執行前呼叫  

18.    @Before("execution (* com.zywang.services.impl.*.*(..))")  

19.    public void before() {  

20.        System.out.println("before");  

21.    }  

22.      

23.    //方法執行後呼叫  

24.    @After("execution (* com.zywang.services.impl.*.*(..))")  

25.    public void after() {  

26.        System.out.println("after");  

27.    }  

28.      

29.    //方法執行的前後呼叫  

30.    @Around("execution (* com.zywang.services.impl.*.*(..))")  

31.    public Object around(ProceedingJoinPoint point) throws Throwable{  

32.        System.out.println("begin around");  

33.        Object object = point.proceed();  

34.        System.out.println("end around");  

35.        return object;  

36.    }  

37.      

38.    //方法執行出現異常時呼叫  

39.    @AfterThrowing(pointcut = "execution (* com.zywang.services.impl.*.*(..))",throwing = "ex")  

40.    public void afterThrowing(Exception ex){  

41.        System.out.println("afterThrowing");  

42.        System.out.println(ex);  

43.    }  

44.}  

aop 及spring aop原理介紹

通俗簡單地理解,aop的原理是:在程式碼編譯或專案啟動執行的時候生成代理物件,代理物件糅合了被攔截方法和切面邏輯方法的執行。

AOP 實現的關鍵就在於 AOP 框架自動建立的 AOP 代理,AOP 代理主要分為靜態代理和動態代理兩大類,靜態代理為編譯時增強,動態代理為執行時增強。

靜態代理中AspectJ是一個面向切面的框架,它擴充套件了Java語言,定義了AOP語法所以它有一個專門的編譯器用來生成遵守Java位元組編碼規範的Class檔案。動態代理則不需要專門的編譯器和擴充套件語法,而是在位元組碼級別進行執行時的增強生成代理物件,常見的有jdk和cglib動態代理。

spring aop的底層是使用jdk或cglib動態代理來實現執行時的增強。

spring aop原始碼--ProxyFactory分析

spring 提供的程式設計式aop實現,即通過 ProxyFactory類完成的。 另外AnnotationAwareAspectJAutoProxyCreator BeanNameAutoProxyCreator  DefaultAdvisorAutoProxyCreator 等自動aop代理建立器都是通過在其父類AbstractAutoProxyCreator中通過ProxyFactory 來實現aop邏輯的植入。所以理解ProxyFactory 的使用對理解spring aop 至關重要。

分析ProxyFactory 可以從兩條線來分析:

1、代理物件的建立

2、method的呼叫以及攔截器(Advice)的織入。

執行時選擇動態代理的方式:

DefaultAopProxyFactory型別的createAopProxy 方法:

1.  /** 預設的代理工廠實現 

2.   optimize =trueproxyTargetClass=ture  no proxy interfaces 指定,使用CGLIB 來生成代理,否則jdk代理 

3.  */  

4.  public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {  

5.    

6.      /** 檢查CGLIB2 相關類是否存在 */  

7.      private static final boolean cglibAvailable =  

8.              ClassUtils.isPresent("net.sf.cglib.proxy.Enhancer", DefaultAopProxyFactory.class.getClassLoader());  

9.    

10.   

11.     public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {  

12.         //optimize =trueproxyTargetClass=ture  no proxy interfaces 指定,使用CGLIB 來生成代理  

13.         //optimize 做優化,早期jdk的代理的生成相當慢,不過現在基本差異不大  

14.         if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {  

15.             Class targetClass = config.getTargetClass();  

16.             if (targetClass == null) { //被代理的物件不能為空  

17.                 throw new AopConfigException("TargetSource cannot determine target class: " +  

18.                         "Either an interface or a target is required for proxy creation.");  

19.             }  

20.             if (targetClass.isInterface()) { //雖然符合以上要求,但是如果代理物件是藉口,那麼繼續使用jdk  

21.                 return new JdkDynamicAopProxy(config);  

22.             }  

23.             if (!cglibAvailable) {  

24.                 throw new AopConfigException(  

25.                         "Cannot proxy target class because CGLIB2 is not available. " +  

26.                         "Add CGLIB to the class path or specify proxy interfaces.");  

27.             }  

28.             return CglibProxyFactory.createCglibProxy(config); // Cglib2 代理  

29.         }  

30.         else {  

31.             return new JdkDynamicAopProxy(config); //jdk 代理  

32.         }  

33.     }  

34.   

35.     /** 

36.      * 判斷是否有介面 

37.      */  

38.     private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {  

39.         Class[] interfaces = config.getProxiedInterfaces();  

40.         return (interfaces.length == 0 || (interfaces.length == 1 && SpringProxy.class.equals(interfaces[0])));  

41.     }  

42.   

43.     /** 

44.      * 建立Cglib2AopProxy 

45.      */  

46.     private static class CglibProxyFactory {  

47.         public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {  

48.             return new Cglib2AopProxy(advisedSupport);  

49.         }  

50.     }  

51. }  

spring aop原始碼--JDK動態代理分析

Ø  /** 

Ø   * 基於JDK動態代理Aop代理實現 

Ø  通過JDK代理的方法呼叫只對介面中的方法做攔截 

Ø  即使真實物件不是執行緒安全的,通過JdkDynamicAopProxy 產生的物件依然是執行緒安全的。 

Ø   */  

Ø  final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {  

Ø    

Ø      private static final long serialVersionUID = 5531744639992436476L;  

Ø    

Ø      /** We use a static Log to avoid serialization issues */  

Ø      private static Log logger = LogFactory.getLog(JdkDynamicAopProxy.class);  

Ø    

Ø      /** aop的配置資訊(增強邏輯,真是物件,切點資訊) */  

Ø      private final AdvisedSupport advised;  

Ø    

Ø      /** 代理介面是否有equals方法 

Ø       */  

Ø      private boolean equalsDefined;  

Ø    

Ø      /** 代理藉口是否有hashCode方法 

Ø       */  

Ø      private boolean hashCodeDefined;  

Ø    

Ø    

Ø      public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {  

Ø          Assert.notNull(config, "AdvisedSupport must not be null");  

Ø          if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {  

Ø              throw new AopConfigException("No advisors and no TargetSource specified");  

Ø          }  

Ø          this.advised = config;  

Ø      }  

Ø    

Ø    /** 獲取代理*/  

Ø      public Object getProxy() {  

Ø          return getProxy(ClassUtils.getDefaultClassLoader());  

Ø      }  

Ø    

Ø  /**  至此方法結束,通過jdk生成代理物件已經完成*/  

Ø      public Object getProxy(ClassLoader classLoader) {  

Ø          if (logger.isDebugEnabled()) {  

Ø              logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());  

Ø          }  

Ø          Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); //找到所有藉口  

Ø          findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);  

Ø          return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); //生成代理  

Ø      }  

Ø    

Ø      /** 

Ø       * 檢查是否定義了hashCode  equals 方法 

Ø       */  

Ø      private void findDefinedEqualsAndHashCodeMethods(Class[] proxiedInterfaces) {  

Ø          for (Class proxiedInterface : proxiedInterfaces) {  

Ø              Method[] methods = proxiedInterface.getDeclaredMethods();  

Ø              for (Method method : methods) {  

Ø                  if (AopUtils.isEqualsMethod(method)) {  

Ø                      this.equalsDefined = true;  

Ø                  }  

Ø                  if (AopUtils.isHashCodeMethod(method)) {  

Ø                      this.hashCodeDefined = true;  

Ø                  }  

Ø                  if (this.equalsDefined && this.hashCodeDefined) {  

Ø                      return;  

Ø                  }  

Ø              }  

Ø          }  

Ø      }  

Ø    

Ø      /** 

Ø       * 這裡是代理物件呼叫,也是jdk代理實現的核心邏輯 

Ø       */  

Ø      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  

Ø          MethodInvocation invocation;  

Ø          Object oldProxy = null;  

Ø          boolean setProxyContext = false;  

Ø    

Ø          TargetSource targetSource = this.advised.targetSource;  

Ø          Class targetClass = null;  

Ø          Object target = null;  

Ø    

Ø          try {  

Ø              if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { //如果代理的是equlas 方法  

Ø                  return equals(args[0]);  

Ø              }  

Ø              if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { //如果代理的是hashCode方法  

Ø                  // The target does not implement the hashCode() method itself.  

Ø                  return hashCode();  

Ø              }  

Ø              if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&  

Ø                      method.getDeclaringClass().isAssignableFrom(Advised.class)) {  

Ø                  // Service invocations on ProxyConfig with the proxy config...  

Ø                  return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);  

Ø              }  

Ø    

Ø              Object retVal;  

Ø    

Ø              if (this.advised.exposeProxy) { // 是否允許把當前代理放到AopContext中,這樣可以儲存在ThreadLocal中,在程式碼裡使用AopContext.currentProxy() 獲取當前代理  

Ø                  // Make invocation available if necessary.  

Ø                  oldProxy = AopContext.setCurrentProxy(proxy);  

Ø                  setProxyContext = true;  

Ø              }  

Ø    

Ø              // 獲取目標物件以及Class型別  

Ø              target = targetSource.getTarget();  

Ø              if (target != null) {  

Ø                  targetClass = target.getClass();  

Ø              }  

Ø    

Ø              // 根據Method  targetClass 獲取對應的攔截器(Advice增強封裝)這裡是獲取攔截邏輯的地方。(MethodMatcher  ClassFilter 在此處做匹配)  

Ø              List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);  

Ø    

Ø              // 如果沒有配置,或者沒有匹配到任何方法,那麼直接呼叫當前例項的方法即可  

Ø              if (chain.isEmpty()) {  

Ø                  retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);  

Ø              }  

Ø              else {  

Ø                  // ReflectiveMethodInvocation 類封裝了增強和例項方法的呼叫  

Ø                  invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);  

Ø                  //aop的增強的植入精華全在於此  

Ø                  retVal = invocation.proceed();  

Ø              }  

Ø    

Ø              // Massage return value if necessary.  

Ø              if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy) &&  

Ø                      !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {  

Ø                  retVal = proxy;  

Ø              }  

Ø              return retVal;  

Ø          }  

Ø          finally {  

Ø              if (target != null && !targetSource.isStatic()) {  

Ø                  targetSource.releaseTarget(target);  

Ø              }  

Ø              if (setProxyContext) {  

Ø                  AopContext.setCurrentProxy(oldProxy); //恢復currentProxy  

Ø              }  

Ø          }  

Ø      }  

Ø    

Ø  }  

spring aop原始碼--cglib動態代理分析

JDK動態代理和CGLIB位元組碼生成的區別?
 * JDK動態代理只能對實現了介面的類生成代理,而不能針對類
 * CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法
   因為是繼承,所以該類或方法最好不要宣告成final