这里列出5种组合情况:
@Controller
+@RequestMapping
@Controller
@Component
+@RequestMapping
@Component
- 类上什么也不加,方法上只有
@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了那我们就去看一下这个类中的属性有没有我们的方法就知道了。
我们直接来看一下:
我们可以看到,这里是没有我们的/getName
的请求的,我还专门写了一个正常的接口,是被注册进去了的。所以这就是我们访问/getName
404的原因。那为啥没被注册进去呢?这就是个有意思的问题了
那个/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));
}
流程:
obtainApplicationContext().getType(beanName)
:根据Bean的名字获取到Bean的类型beanType != null && isHandler(beanType)
:Bean的类型不是null,并且此Bean被@Controller
或@RequestMapping
注解修饰(这里就解释了前面的问题)detectHandlerMethods(beanName)
:根据Bean的名字查找Bean中的HandlerMethod
,然后注册进MappingRegistry
到这里我想大家就知道前几个问题的原因与答案了。总的来说还是挺有意思的