• spring源码 — 四、MVC


    spring mvc是怎么实现的?为什么我们只需要在方法上写一个注解,就可以通过http访问这个接口?下面我们分3部分来解答这两个问题

    注意:本文是基于spring4.3.2的

    • spring mvc整体流程
    • HandlerMapping
    • HandlerAdapter

    spring mvc整体流程

    我们通过看一下spring处理一个http请求的过程来大概了解下

    spring_mvc

    Spring mvc的入口就是DispatcherServlet,请求交给这个servlet之后,通过调用doDispatch来分发这个请求,主要做了一下几件事情

    • 处理multipart请求,如果有文件上传等请求,先封装为DefaultMultipartHttpServletRequest
    • 从HandlerMapping中获取处理该请求的handler,并构造HandlerExecutionChain,将入所有的interceptor
    • 根据handler从所有的HandlerAdapter中找到可以处理这个handler的adapter
    • 执行所有interceptor.prehandle方法
    • 调用写有RequestMapping注解的方法,返回ModelAndView
    • 执行所有interceptor.postHandle方法
    • 解析view
    • render,将model转化为response返回
    • 执行所有interceptor.afterCompletion
    • cleanupMultipart

    从上面的流程中可以看出,在处理过程中主要是一些关键组件完成了对应的逻辑,下面我们看下其中的组件。

    HandlerMapping

    作为DispatcherServlet组件之一,就是其中一个field

    org.springframework.web.servlet.DispatcherServlet#handlerMappings
    

    主要作用是维护从url到Controller的映射,根据url找到对应的Controller。

    有哪些HandlerMapping

    spring是怎么查找所有的HandlerMapping的呢?是在DispatcherServlet初始化的时候查找并初始化这个字段的

    // org.springframework.web.servlet.DispatcherServlet#initHandlerMappings
    private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;
    
        if (this.detectAllHandlerMappings) {
            // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
            // 从容器中查找所有HandlerMapping类型的bean
            Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }
    
        // Ensure we have at least one HandlerMapping, by registering
        // a default HandlerMapping if no other mappings are found.
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isDebugEnabled()) {
                logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
            }
        }
    }
    

    那么容器中有哪些HandlerMapping类型的bean呢?如果我们使用RequestMapping注解的话需要在xml中进行以下配置

    <mvc:annotation-driven />
    

    spring解析这个标签使用的是

    org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser
    

    这个类解析xml标签的时候会向容器中注册RequestMappingHandlerMapping,这个类继承自HandlerMapping

    如果我们进行了一下配置

    <mvc:default-servlet-handler />
    

    spring解析这个标签的使用的是

    org.springframework.web.servlet.config.DefaultServletHandlerBeanDefinitionParser
    

    解析的时候spring会向容器注入DefaultServletHttpRequestHandler和SimpleUrlHandlerMapping

    那么

    • BeanNameHandlerMapping
    • DefaultAnnotationHandlerMapping

    所以这种情况下会有这三个HandlerMapping类型的bean,spring遍历handlerMappings来根据request path查找对应的handler。

    HandlerMapping怎么管理url到handler的映射关系

    HandlerMapping抽象类AbstractHandlerMethodMapping中有一个field

    // org.springframework.web.servlet.handler.AbstractHandlerMethodMapping
    private final MappingRegistry mappingRegistry = new MappingRegistry();
    

    这个类就是用来维护url到handler的映射,看下这个类有哪些数据结构

    // org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry
    // 所有的mapping都会加入这个map中
    private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>();
    // 只有具体的(没有通配符org.springframework.util.AntPathMatcher#isPattern)的才会加入这个map中
    private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>();
    // 根据name找handler
    private final Map<String, List<HandlerMethod>> nameLookup =
        new ConcurrentHashMap<String, List<HandlerMethod>>();
    // cors mapping
    private final Map<HandlerMethod, CorsConfiguration> corsLookup =
    

    注册mapping的过程就是讲找到的mapping添加到上面对应的数据结构中,以RequestMappingHandlerMapping为例,具体注册的时机是在RequestMappingHandlerMapping bean初始化的时候,spring容器初始化完成以后会将容器中eagere init的bean进行初始化,构造bean的时候会调用afterPropertiesSet,最后会调用AbstractHandlerMethodMapping#initHandlerMethods来查找容器中所有的bean

    1. 找到容器中所有的bean
    2. 针对每个bean查找里面有没有mapping

    在调用到initHandlerMethods方法的时候会拿出容器中所有的bean,用isHandler判断是否是handler,其实就是判断是否有Controller或RequestMapping的注解

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

    找到类后,再找里面的方法,方法的查找逻辑就是看看方法上有没有RequestMapping,

    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        // 查找方法上的RequestMapping注解
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        RequestCondition<?> condition = (element instanceof Class<?> ?
                                         getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
        return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
    }
    

    找到对应的方法之后构造成mapping注册到MappingRegistry

    public void register(T mapping, Object handler, Method method) {
        this.readWriteLock.writeLock().lock();
        try {
            HandlerMethod handlerMethod = createHandlerMethod(handler, method);
            assertUniqueMethodMapping(handlerMethod, mapping);
    
            if (logger.isInfoEnabled()) {
                logger.info("Mapped "" + mapping + "" onto " + handlerMethod);
            }
            // 所有的mapping都会加入mappingLookup
            this.mappingLookup.put(mapping, handlerMethod);
    
            List<String> directUrls = getDirectUrls(mapping);
            // 找到所有具体的(直接的,就是不包含通配符)的url添加到urlLookup
            for (String url : directUrls) {
                this.urlLookup.add(url, mapping);
            }
    
            String name = null;
            if (getNamingStrategy() != null) {
                name = getNamingStrategy().getName(handlerMethod, mapping);
                addMappingName(name, handlerMethod);
            }
    
            CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
            if (corsConfig != null) {
                this.corsLookup.put(handlerMethod, corsConfig);
            }
    
            this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
        }
        finally {
            this.readWriteLock.writeLock().unlock();
        }
    }
    

    在根据url查找handler的时候优先查找urlLookup,看看没有通配符的能不能匹配,如果没有直接匹配的则再从mappingLookup里面找

    HandlerAdapter

    HandlerAdapter是为了将invoke过程的细节对于DispatcherServlet屏蔽,比如参数解析。

    在DispatcherServlet初始化的时候调用inithandlerAdapters,在里面找容器中所有的handlerAdapter实现类,容器里面的handlerAdapter是在解析mvc标签的时候加入容器的

    总结

    这篇文章通过spring处理一次请求的过程了解了springmvc怎么工作,以及其中两个关键组件,HandlerMapping和HandlerAdapter。

  • 相关阅读:
    实验 6:OpenDaylight 实验——OpenDaylight 及 Postman 实现流表下发
    实验 4:Open vSwitch 实验——Mininet 中使用 OVS 命令
    实验 3:Mininet 实验——测量路径的损耗率
    第一次个人编程作业
    实验 2:Mininet 实验——拓扑的命令脚本生成
    实验 1:Mininet 源码安装和可视化拓扑工具
    第一次博客作业
    第一次个人编程作业
    第一次博客作业
    需求分析报告
  • 原文地址:https://www.cnblogs.com/sunshine-2015/p/10555748.html
Copyright © 2020-2023  润新知