来和大家聊个有意思的事情:在接口上标注@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

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

相关推荐
独立开阀者_FwtCoder2 分钟前
深入解密Node共享内存:这个原生模块让你的多进程应用性能翻倍
前端·javascript·后端
Asthenia04124 分钟前
深入剖析 Spring Cloud Feign 的 Contract 组件:设计与哲学
后端
严文文-Chris12 分钟前
【MVC简介-产生原因、演变历史、核心思想、组成部分、使用场景】
mvc
Asthenia041213 分钟前
Feign 原理:Client 的实现与选型(ApacheHttpClient/OkHttp)/Feign超时控制
后端
开心就好202513 分钟前
Flutter实战】文本组件及五大案例
后端
Ai 编码助手17 分钟前
Golang并发编程:Data Race检测与解决方案
开发语言·后端·golang
宦如云23 分钟前
Assembly语言的嵌入式调试
开发语言·后端·golang
Gvemis⁹41 分钟前
Scala总结(三)
开发语言·后端·scala
一只小闪闪43 分钟前
langchain4j搭建失物招领系统(五)---实现失物登记功能-大模型流式输出
java·人工智能·后端
SimonKing1 小时前
Kafka 4.0.0震撼来袭,彻底摒弃Zookeeper
java·后端·架构