Spring AOP造成的@Controller注册失败

昨天在重构项目时 ,抽取了Controller的公共方法到AbstractController中, 调整了原有Controller的路径 (包名) . 然后启动时发现几乎所有移动过包的Controller都失效了.

主要对象

  • ControllerLogInterceptor : aop拦截器 ,拦截目标路径下所有Controller
@Pointcut("execution(public * com.eddy..*.*Controller.*(..))")
public void handleController() {
}
  • AppController : 一个接口
public interface AppController {
    String getAppName();
}
  • AbstractController : 抽象的Controller ,包含一些公用方法 ;实现了AppController接口
@Slf4j
public abstract class AbstractController implements AppController {
    @Autowired
    protected HttpSession session;

    @Override
    public String getAppName() {
        return OSC_APPNAME;
    }
}
  • ContactController : 业务逻辑的Controller
@RestController
@RequestMapping("/eddy/contact")
public class ContactController extends AbstractOSCController {
...
}

原因分析

  • 因为是移动路径过后出现的 ,首先考虑路径影响
    • 使用的SpringBoot ,全注解配置的Controller ,路径并不会影响Bean的解析
    • 同路径下某一Controller可用 ,该Controller没有继承AbstractController ,继承了另一个类
  • 考虑AbstractController的影响
    • 改变路径前也是使用继承Controller的做法 ,没有出现问题
    • 这个类也在AOP的匹配范围内
  • 修改了AbstractController->AbstractAction ,依旧无效
    • AOP主要是代理原有Bean ,Abstract不会生成Spring Bean ,不会产生作用
  • 终极大招 ,源码断点:
    • 打印Debug Log : ContactController注册为Bean ,但没有扫描到mapping路径
    • 断点查看Spring如何扫描Controller并添加mapping的

RequestMapping

通过日志 ,找到扫描Mapping的目标类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());
    }

可以看到 ,通过循环已加载的Bean ,然后通过相应Handler (这里是RequestMappingHandler) 判断是否应该处理.

//RequestMappingHandlerMapping.class
@Override
protected boolean isHandler(Class<?> beanType) {
    //判断对应class类是否 有Controller注解或继承自Controller注解 (RestController) 
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

这里断点调试后 ,发现ContactController直接跳过 ,它没有@Controller注解!!!! 但代码里确实进行了配置 ,在经过多次重编译过后 ... 发现ContactController对应Bean的classType是 com.sun.Proxy...

也就是说SpringAOP代理将这个Bean本有的信息抹除了 ,所以我修改了SpringAOP的匹配路径 ,果然Controller可用了.

但是AOP部分的代码也很重要 ,我不希望丢掉.同时我发现

  • 单一的Controller(无继承关系) 和 AOP 工作的很好
  • 继承自其他类的Controller也工作的很好
    那么 ,Spring的AOP应该是不会破坏Bean的类型判断的 : 他们的类型都是 com.eddy.ContactController$Spring..Cglib...这样的 ,是能够通过SpringMVC的检查的

结论

最后的不同就在于那个简单的接口...

  • Spring默认使用JDK ,对接口进行动态代理 : ContactController extends AbstractController implement AppController ,所以将与接口有关的类都代理成了Proxy .
  • 而继承自单一类/没有接口的 ,使用cglib代理 ,就不存在这个问题 ,他依旧保留了原有class的信息.

增加配置 spring.aop.proxy-target-class=true ,默认统一使用cglib即可

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • 什么是Spring Spring是一个开源的Java EE开发框架。Spring框架的核心功能可以应用在任何Jav...
    jemmm阅读 16,445评论 1 133
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,778评论 6 342
  • 文章作者:Tyan博客:noahsnail.com 3.5 Bean scopes When you create...
    SnailTyan阅读 1,882评论 0 1
  • 第一章 毕业了 A市 某高校又迎来了让人悲喜交加的毕业季。 “你们选好学校没?不知道是哪所学校这...
    路人有意阅读 179评论 0 2