SpringBoot MVC(1)映射的查找与注册

549 阅读2分钟

说明:本系列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);
    });

注册的过程,在RequestMappingHandlerMappingmappingRegistry变量体现,具体为:

  • mappingLookup key为RequestMappingInfo对象,value为处理过的方法,若key重复,会报错,url不能重复就是在此处限制的(代码位于AbstractHandlerMethodMapping->MappingRegistry->register()
  • urlLookup key为不带*?的url(这两个符号在请求解析的时候会做特殊处理),value为为RequestMappingInfo对象
  • registry key为RequestMappingInfo对象,value封装了mappingLookup和urlLookup 这里的/error是404或者接口请求出问题时默认返回的错误页面

附录

由此可见,想要通过url请求到接口,控制器类必须满足以下条件

  1. 能够被扫描成bean
  2. 类必须有@Controller或@RequestMapping注解
  3. 方法必须有@RequestMapping注解

在SpringBoot中,使用@RestController注解,这是因为其内部有@ResponseBody注解,当返回类的时候,Content-Typeapplication/json