说明:本系列mvc相关代码为spring boot web内置,版本为2.1.1.RELEASE,spring mvc的版本为5.1.3.RELEASE。
MVC,M为模型,存储的是数据,V为视图,即用户看到的页面,C为控制器,即url对应的方法,MVC核心是DispatcherServlet
映射的查找与注册
在实例化beanName为requestMappingHandlerMapping的时候,其对应类为RequestMappingHandlerMapping,它实现了InitializingBean接口,因此会执行afterPropertiesSet()方法,最终走入下面的方法中
AbstractHandlerMethodMapping->initHandlerMethods():
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
AbstractHandlerMethodMapping->processCandidateBean():
// 如果类含有@Controller或@RequestMapping注解,那么if为true
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
detectHandlerMethods(beanName)方法中,会遍历给定类的所有方法,如果有@RequestMapping注解,会读取该方法的@RequestMapping属性值,然后封装成RequestMappingInfo对象
RequestMappingHandlerMapping->getMappingForMethod():
// 方法的@RequestMapping信息封装成RequestMappingInfo对象
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
// 类的@RequestMapping信息封装成RequestMappingInfo对象
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
// 合并成一个,在这里做了路径的拼接
info = typeInfo.combine(info);
}
}
return info;
MethodIntrospector->selectMethods():
// 这里的result就是上面返回的info
T result = metadataLookup.inspect(specificMethod);
if (result != null) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
// 只要不是桥接方法,就添加到methodMap中 key是方法,value是info
methodMap.put(specificMethod, result);
}
}
AbstractHandlerMethodMapping->detectHandlerMethods():
// 这里的methods就是上面返回的methodMap
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
注册的过程,在RequestMappingHandlerMapping的mappingRegistry变量体现,具体为:
- mappingLookup key为RequestMappingInfo对象,value为处理过的方法,若key重复,会报错,url不能重复就是在此处限制的(代码位于
AbstractHandlerMethodMapping->MappingRegistry->register()) - urlLookup key为不带
*和?的url(这两个符号在请求解析的时候会做特殊处理),value为为RequestMappingInfo对象 - registry key为RequestMappingInfo对象,value封装了mappingLookup和urlLookup
这里的/error是404或者接口请求出问题时默认返回的错误页面
附录
由此可见,想要通过url请求到接口,控制器类必须满足以下条件
- 能够被扫描成bean
- 类必须有@Controller或@RequestMapping注解
- 方法必须有@RequestMapping注解
在SpringBoot中,使用@RestController注解,这是因为其内部有@ResponseBody注解,当返回类的时候,Content-Type为application/json