Spring AOP造成的@Controller注冊失敗

昨天在重構(gòu)項(xiàng)目時(shí) ,抽取了Controller的公共方法到AbstractController中, 調(diào)整了原有Controller的路徑 (包名) . 然后啟動(dòng)時(shí)發(fā)現(xiàn)幾乎所有移動(dòng)過包的Controller都失效了.

主要對象

  • ControllerLogInterceptor : aop攔截器 ,攔截目標(biāo)路徑下所有Controller
@Pointcut("execution(public * com.eddy..*.*Controller.*(..))")
public void handleController() {
}
  • AppController : 一個(gè)接口
public interface AppController {
    String getAppName();
}
  • AbstractController : 抽象的Controller ,包含一些公用方法 ;實(shí)現(xiàn)了AppController接口
@Slf4j
public abstract class AbstractController implements AppController {
    @Autowired
    protected HttpSession session;

    @Override
    public String getAppName() {
        return OSC_APPNAME;
    }
}
  • ContactController : 業(yè)務(wù)邏輯的Controller
@RestController
@RequestMapping("/eddy/contact")
public class ContactController extends AbstractOSCController {
...
}

原因分析

  • 因?yàn)槭且苿?dòng)路徑過后出現(xiàn)的 ,首先考慮路徑影響
    • 使用的SpringBoot ,全注解配置的Controller ,路徑并不會(huì)影響B(tài)ean的解析
    • 同路徑下某一Controller可用 ,該Controller沒有繼承AbstractController ,繼承了另一個(gè)類
  • 考慮AbstractController的影響
    • 改變路徑前也是使用繼承Controller的做法 ,沒有出現(xiàn)問題
    • 這個(gè)類也在AOP的匹配范圍內(nèi)
  • 修改了AbstractController->AbstractAction ,依舊無效
    • AOP主要是代理原有Bean ,Abstract不會(huì)生成Spring Bean ,不會(huì)產(chǎn)生作用
  • 終極大招 ,源碼斷點(diǎn):
    • 打印Debug Log : ContactController注冊為Bean ,但沒有掃描到mapping路徑
    • 斷點(diǎn)查看Spring如何掃描Controller并添加mapping的

RequestMapping

通過日志 ,找到掃描Mapping的目標(biāo)類RequestMappingHandlerMapping (父對象AbstractHandlerMethodMapping#initHandlerMethods方法)

//AbstractHandlerMethodMapping.class
protected void initHandlerMethods() {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for request mappings in application context: " + getApplicationContext());
        }
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));

        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                Class<?> beanType = null;
                try {
                    beanType = getApplicationContext().getType(beanName);
                }
                catch (Throwable ex) {
                    // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                    }
                }
                if (beanType != null && isHandler(beanType)) {
                    detectHandlerMethods(beanName);
                }
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

可以看到 ,通過循環(huán)已加載的Bean ,然后通過相應(yīng)Handler (這里是RequestMappingHandler) 判斷是否應(yīng)該處理.

//RequestMappingHandlerMapping.class
@Override
protected boolean isHandler(Class<?> beanType) {
    //判斷對應(yīng)class類是否 有Controller注解或繼承自Controller注解 (RestController) 
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

這里斷點(diǎn)調(diào)試后 ,發(fā)現(xiàn)ContactController直接跳過 ,它沒有@Controller注解!!!! 但代碼里確實(shí)進(jìn)行了配置 ,在經(jīng)過多次重編譯過后 ... 發(fā)現(xiàn)ContactController對應(yīng)Bean的classType是 com.sun.Proxy...

也就是說SpringAOP代理將這個(gè)Bean本有的信息抹除了 ,所以我修改了SpringAOP的匹配路徑 ,果然Controller可用了.

但是AOP部分的代碼也很重要 ,我不希望丟掉.同時(shí)我發(fā)現(xiàn)

  • 單一的Controller(無繼承關(guān)系) 和 AOP 工作的很好
  • 繼承自其他類的Controller也工作的很好
    那么 ,Spring的AOP應(yīng)該是不會(huì)破壞Bean的類型判斷的 : 他們的類型都是 com.eddy.ContactController$Spring..Cglib...這樣的 ,是能夠通過SpringMVC的檢查的

結(jié)論

最后的不同就在于那個(gè)簡單的接口...

  • Spring默認(rèn)使用JDK ,對接口進(jìn)行動(dòng)態(tài)代理 : ContactController extends AbstractController implement AppController ,所以將與接口有關(guān)的類都代理成了Proxy .
  • 而繼承自單一類/沒有接口的 ,使用cglib代理 ,就不存在這個(gè)問題 ,他依舊保留了原有class的信息.

增加配置 spring.aop.proxy-target-class=true ,默認(rèn)統(tǒng)一使用cglib即可

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,680評論 19 139
  • 什么是Spring Spring是一個(gè)開源的Java EE開發(fā)框架。Spring框架的核心功能可以應(yīng)用在任何Jav...
    jemmm閱讀 16,780評論 1 133
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,283評論 6 342
  • 文章作者:Tyan博客:noahsnail.com 3.5 Bean scopes When you create...
    SnailTyan閱讀 2,008評論 0 1
  • 第一章 畢業(yè)了 A市 某高校又迎來了讓人悲喜交加的畢業(yè)季。 “你們選好學(xué)校沒?不知道是哪所學(xué)校這...
    路人有意閱讀 219評論 0 2

友情鏈接更多精彩內(nèi)容