来和大家聊个有意思的事情:在接口上标注@Component和@Controller有什么区别?

这里列出5种组合情况:

  1. @Controller+@RequestMapping
  2. @Controller
  3. @Component+@RequestMapping
  4. @Component
  5. 类上什么也不加,方法上只有@xxxMapping+@ResponseBody注解

那我想问一个问题,我来分别测试这5种情况后:

  • 哪一种我们在访问接口的时候能正常访问?
  • 而哪一种无法访问

话不多说,直接上代码

@Controller+@RequestMapping

java 复制代码
@Controller
@RequestMapping("/test")
public class TestController {
    @GetMapping("/getName")
    @ResponseBody
    public String getName() {
        return "lizhi";
    }
}

结果:毋庸置疑,可以正常访问

@Controller

java 复制代码
@Controller
//@RequestMapping("/test")
public class TestController {
    @GetMapping("/getName")
    @ResponseBody
    public String getName() {
        return "lizhi";
    }
}

结果:同样也可以正常访问到

前面两种的答案我想大家都知道,肯定是能访问到的,只是一个需要加上/test前缀,一个不需要。那下面的两种情况呢?

@Component+@RequestMapping

java 复制代码
@Component
@RequestMapping("/test")
public class TestController {
    @GetMapping("/getName")
    @ResponseBody
    public String getName() {
        return "lizhi";
    }
}

结果:同样也可以访问到

那我就有疑惑了,@Component@Controller貌似都可以访问到,那我为啥要用@Controller呢?而不用@Component呢?咱们暂时先打个问号,先把最后一种情况看完

@Component

java 复制代码
@Controller
//@RequestMapping("/test")
public class TestController {
    @GetMapping("/getName")
    @ResponseBody
    public String getName() {
        return "lizhi";
    }
}

结果404

这个时候事情就变得有意思起来了。单个@Controller可以访问到,而单个@Component访问不到,@Component+@RequestMapping却又能访问到。这是什么情况,让我们来看一下源码就知道了

最后一种情况@xxxMapping+@ResponseBody的方式,只要看了源码就明白了,我就不测试了。

源码

404原因

看过我前面文章的兄弟都知道,我们编写的接口都被保存在了MappingRegistry类的属性中,所以404了那我们就去看一下这个类中的属性有没有我们的方法就知道了。

SpringMVC原理(2)-目标方法是怎么被找到的]

我们直接来看一下:

我们可以看到,这里是没有我们的/getName的请求的,我还专门写了一个正常的接口,是被注册进去了的。所以这就是我们访问/getName404的原因。那为啥没被注册进去呢?这就是个有意思的问题了

那个/error是什么也在之前的文章里大抵说过:SpringMVC原理(9)-带你深入了解SpringMVC的异常处理原理(HandlerExceptionResolver)

让我们来一起看一下

没有注册进去的原因

我们注册这个路径映射是在AbstractHandlerMethodMapping里,这个类实现了InitializingBean,所以会在这个Bean初始化 的时候调用(这里涉及到Spring Bean的生命周期了

java 复制代码
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
    private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
    
    public void afterPropertiesSet() {
        // 初始化所有的 HandlerMethod
        initHandlerMethods();
    }
    
    protected void initHandlerMethods() {
        // 获取所有候选的Bean,并进行遍历
        for (String beanName : getCandidateBeanNames()) {
            // 过滤掉以scopedTarget.开头的Bean
             if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                    // 核心:这里开始处理
                    processCandidateBean(beanName);
            }
        }
        // 处理HandlerMethod初始化完成后
        handlerMethodsInitialized(getHandlerMethods());
	}
}

处理候选的Bean-processCandidateBean()

java 复制代码
protected void processCandidateBean(String beanName) {
    Class<?> beanType = null;
    try {
        // 1、根据Bean的名字获取到Bean的类型
        beanType = obtainApplicationContext().getType(beanName);
    }
    catch (Throwable ex) {
        // An unresolvable bean type, probably from a lazy bean - let's ignore it.
        if (logger.isTraceEnabled()) {
            logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
        }
    }
    // 2、Bean的类型不是null,并且此Bean被@Controller或@RequestMapping注解修饰
    if (beanType != null && isHandler(beanType)) {
        // 3、查找Bean中的HandlerMethod,然后注册进`MappingRegistry`
        detectHandlerMethods(beanName);
    }
}

// 判断
protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

流程:

  1. obtainApplicationContext().getType(beanName):根据Bean的名字获取到Bean的类型
  2. beanType != null && isHandler(beanType):Bean的类型不是null,并且此Bean被@Controller@RequestMapping注解修饰(这里就解释了前面的问题)
  3. detectHandlerMethods(beanName):根据Bean的名字查找Bean中的HandlerMethod,然后注册进MappingRegistry

到这里我想大家就知道前几个问题的原因与答案了。总的来说还是挺有意思的

相关推荐
空中湖1 小时前
Spring AI Agent 编排:ReAct 模式 + 多 Agent 协作实战
人工智能·spring·react.js
止语Lab2 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活2 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困2 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn2 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530142 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908402 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金2 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛2 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构
IT_陈寒3 小时前
Vue的响应式让我加班到凌晨3点,原来问题出在这
前端·人工智能·后端