切面編程(三):AspectJ與Shiro不兼容和Spring二次代理錯誤分析

切面編程(三):AspectJ與Shiro不兼容和Spring二次代理錯誤分析

Shiro與AspectJ的配置 Shiro的配置 根據 個人BLOG文章和官方文檔咱們能夠得知Shiro在使用註解的時的配置是html

Shiro與AspectJ的配置

Shiro的配置

根據 個人BLOG文章和官方文檔咱們能夠得知Shiro在使用註解的時的配置是java

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
 <property name="securityManager" ref="securityManager"/>
</bean>

其基於註解的權限控制功能git

  1. 根據類名能夠推斷是經過切面的Advisor來完成的(AuthorizationAttributeSourceAdvisor)
  2. 因此說它須要建立本身的動態代理類,是由Spring的DefaultAdvisorAutoProxyCreator動態代理建立的

SpringMVC中AspectJ的配置

根據上文,啓用基於註解的AspectJ就很簡單了,由於基於註解的Shiro通常也在SpringMVC的Context裏,咱們採用以下配置github

<aop:aspectj-autoproxy proxy-target-class="true"/>

在二者分別配置的時候,配置方法都是對的可是一旦公用,會發現AspectJ會失效web

失效的解決辦法

方法一

解決辦法也很簡單,註釋掉Shiro配置中的第一句spring

<!--<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>-->

註釋以後Shiro的註解權限管理功能並不會失效,具體緣由咱們來細細分析apache

方法二

給DefaultAdvisorAutoProxyCreator加入參數proxyTargetClass爲true編程

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
 <property name="proxyTargetClass" value="true"/>
</bean>

配置失效的緣由

緣由在於二次代理api

  1. 因爲使用了aop:aspectj-autoproxy強制了proxy-target-class
  2. 也就是說對Web層的Class(主要是Controller)使用了CGLib代理
  3. 而後在Shiro進行代理時使用DefaultAdvisorAutoProxyCreator
  4. 本來應該判斷Controller,發現沒有任何接口,因此使用CGLib來代理
  5. 可是因爲Controller已經被CGLib代理過一次了
  6. DefaultAdvisorAutoProxyCreator拿到對不是Contoller自己,而是CGLib的代理結果
  7. CGLib的代理結果自己是有接口的,干擾了DefaultAdvisorAutoProxyCreator的內部判斷
  8. 使用JDK去代理CGLib的代理結果
  9. 結果Controller的函數時去了CGLib的接口中找方法名,發現方法不存在,致使代理失敗

爲何會肯定是這個緣由,廢了我好大功夫,詳情請看代碼追蹤。。。。沒精力就別看了網絡

代碼的追蹤(能夠不看)

aspectj-autoproxy

在配置文件中的 aop:aspectj-autoproxy會最終交給名爲 AopNamespaceHandler的類進行處理,進入該類(直接在工程全局搜)咱們能夠看到

public class AopNamespaceHandler extends NamespaceHandlerSupport {
 public AopNamespaceHandler() {
 }

 public void init() {
 this.registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
 this.registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
 this.registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
 this.registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
 }
}

aspectj-autoproxy 是由 AspectJAutoProxyBeanDefinitionParser 註冊的,而後根據 該Blog提出的說法

  1. 進入AspectJAutoProxyBeanDefinitionParser
  2. 找到parse函數,看到其調用registerAspectJAnnotationAutoProxyCreatorIfNecessary來註冊BeanDefinition
  3. 進入registerAspectJAnnotationAutoProxyCreatorIfNecessary能夠看到內部的三個register方法
  4. 三個register方法分別對應上述代碼中第一個參數的輸入,可是均調用了AopConfigUtils
  5. 點擊AopConfigUtils,能夠發現全部的方法都最終調用registerOrEscalateApcAsRequired
  6. registerOrEscalateApcAsRequired中有個簡單的if…else判斷
  7. 主要控制了邏輯,若是存在internalAutoProxyCreator則不進行建立新的

該Blog做者提出,因爲

<aop:aspectj-autoproxy/> 方式對應的註冊AutoProxyCreator 的方法是:registerAspectJAnnotationAutoProxyCreatorIfNecessary
DefaultAdvisorAutoProxyCreator 方式對應的註冊AutoProxyCreator 的方法是:registerAutoProxyCreatorIfNecessary;

最終會在第七步驟的函數裏,因爲這個if…else判斷致使不能存在兩個代理,因此不能混合使用 DefaultAdvisorAutoProxyCreator與 aop:aspectj-autoproxy

這個結論是錯誤的,是由於DefaultAdvisorAutoProxyCreator不會調用registerAutoProxyCreatorIfNecessary,產生錯誤的緣由是 二次代理 做者追蹤錯了代碼,只是恰巧改對了

DefaultAdvisorAutoProxyCreator

來,跟着我找 DefaultAdvisorAutoProxyCreator 如何建立代理的代碼

  1. 進入DefaultAdvisorAutoProxyCreator(直接Command或者Ctrl+點擊進入)
  2. 看到其繼承於AbstractAdvisorAutoProxyCreator,進入
  3. 能夠看到AbstractAdvisorAutoProxyCreator繼承於AbstractAutoProxyCreator繼續進入
  4. AbstractAutoProxyCreator中有一個方法叫作createProxy,名字太直接了,叫作建立代理
  5. 在createProxy中有一個ProxyFactory對象,就是代理的工廠模式(工廠模式請自行學習)
  6. ProxyFactory對象繼承於ProxyCreatorSupport
  7. ProxyCreatorSupport中有對象aopProxyFactory用來建立AOP代理(就是切面代理,注意切面代理是屬於動態代理的一種)
  8. ProxyCreatorSupport在構造函數中new了一個DefaultAopProxyFactory給aopProxyFactory賦值
  9. 進入DefaultAopProxyFactory能夠看到切面代理建立方法createAopProxy
  10. 在判斷條件包含 !config.isProxyTargetClass() 時,也就是不使用針對Class的代理的時候,看下一句
  11. return new JdkDynamicAopProxy(config) 根據config配置返回JDK動態代理

這也是咱們在文章一中所說的,一般狀況下Spring使用針對接口的JDK代理進行動態代理,繞了這麼久,咱們回到第五步,看 createProxy方法如何使用 ProxyFactory對象

  1. 能夠看到createProxy 返回值 return proxyFactory.getProxy(this.getProxyClassLoader())
  2. 進入ProxyFactory對象的getProxy方法
  3. 能夠看到 return this.createAopProxy().getProxy(classLoader)
  4. 首先調用了createAopProxy,發現ProxyFactory沒有這個方法,因此來自父類 ProxyCreatorSupport
  5. 點擊進入 看到createAopProxy 內包含this.getAopProxyFactory().createAopProxy(this)
  6. 首先拿到了上文第九步建立的DefaultAopProxyFactory,而後調用了其createAopProxy

以上完成了 DefaultAdvisorAutoProxyCreator 動態代理的建立

兩次代理的內部細節

有一些文章分析給出 沒法肯定二次代理的狀況下哪一個代理成功,可是根據我自己追蹤日誌發現,在Spring啓動時,個人切面類日誌輸出成功

[2016-10-23 14:00:13][qtp1938298155-28][INFO ][] c.b.psas.web.aspect.ControllerLogAspect 28 -- 網絡請求開始 RequestURL: / RequestVO參數: null
[2016-10-23 14:00:13][qtp1938298155-28][INFO ][] c.b.psas.web.aspect.ControllerLogAspect 33 -- 網絡請求結束 RequestURL: / RequestVO參數: null

可是在調用接口時,開啓DEBUG日誌,會發現Shiro拋出的異常爲

[2016-10-23 13:38:52][qtp1386909980-27][DEBUG][] o.s.web.servlet.DispatcherServlet 1197 -- Handler execution resulted in exception - forwarding to resolved error view: ModelAndView: reference to view with name '/errors/500'; model is {exception=java.lang.IllegalStateException: The mapped controller method class 'com.bestpay.psas.web.controller.manage.LoginController' is not an instance of the actual controller bean class 'com.sun.proxy.$Proxy103'. If the controller requires proxying (e.g. due to @Transactional), please use class-based proxying.
HandlerMethod details:
Controller [com.bestpay.psas.web.controller.manage.LoginController]
Method [public java.lang.String com.bestpay.psas.web.controller.manage.LoginController.login()]
Resolved arguments:
}
java.lang.IllegalStateException: The mapped controller method class 'com.bestpay.psas.web.controller.manage.LoginController' is not an instance of the actual controller bean class 'com.sun.proxy.$Proxy103'. If the controller requires proxying (e.g. due to @Transactional), please use class-based proxying.
HandlerMethod details:
Controller [com.bestpay.psas.web.controller.manage.LoginController]
Method [public java.lang.String com.bestpay.psas.web.controller.manage.LoginController.login()]
Resolved arguments:

 at org.springframework.web.method.support.InvocableHandlerMethod.assertTargetBean(InvocableHandlerMethod.java:262) ~[spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]
 at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:225) ~[spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]
 at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137) ~[spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]
 at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110) ~[spring-webmvc-4.1.9.RELEASE.jar:4.1.9.RELEASE]
 at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:775) ~[spring-webmvc-4.1.9.RELEASE.jar:4.1.9.RELEASE]
 at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705) ~[spring-webmvc-4.1.9.RELEASE.jar:4.1.9.RELEASE]
 at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.1.9.RELEASE.jar:4.1.9.RELEASE]
 at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959) [spring-webmvc-4.1.9.RELEASE.jar:4.1.9.RELEASE]
 at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) [spring-webmvc-4.1.9.RELEASE.jar:4.1.9.RELEASE]
 at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:965) [spring-webmvc-4.1.9.RELEASE.jar:4.1.9.RELEASE]
 at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:856) [spring-webmvc-4.1.9.RELEASE.jar:4.1.9.RELEASE]
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:735) [servlet-api-3.0.jar:na]
 at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:841) [spring-webmvc-4.1.9.RELEASE.jar:4.1.9.RELEASE]
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:848) [servlet-api-3.0.jar:na]
 at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:684) [jetty-servlet-8.1.17.v20150415.jar:8.1.17.v20150415]
 at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1496) [jetty-servlet-8.1.17.v20150415.jar:8.1.17.v20150415]
 at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449) [shiro-web-1.2.4.jar:1.2.4]
 at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365) [shiro-web-1.2.4.jar:1.2.4]
 at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90) [shiro-core-1.2.4.jar:1.2.4]
 at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83) [shiro-core-1.2.4.jar:1.2.4]
 at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383) [shiro-core-1.2.4.jar:1.2.4]
 at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362) [shiro-web-1.2.4.jar:1.2.4]
 at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) [shiro-web-1.2.4.jar:1.2.4]
 at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344) [spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]
 at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261) [spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]

能夠在日誌中發現幾個關鍵字

  1. org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain
  2. com.sun.proxy.$Proxy103
  3. InvocableHandlerMethod.doInvoke

三個關鍵字分別說明了

  1. 是Shiro產生了異常,而不是AOP
  2. Shiro嘗試去採用了JDK代理
  3. 報錯在Invoke方法時

解決方法生效的緣由

方法一

由於Shiro也是基於Spring的AOP類的,若是找不到合適的配置,就是默認採用同一個Context下的AOP代理配置,咱們給了其proxy-target-class爲true,天然就在第二次代理的時候找獲得方法

方法二

方法二就更直接了,告訴DefaultAdvisorAutoProxyCreator爲True就好

雜想

我之前寫過另外一個文章,討論了 SpringMVC和Spring公用的狀況下Transactional失效的問題頗有可能底層緣由也是二次代理

相關文章
相關標籤/搜索