Spring核心原理:MVC处理过程

一,初探SpringMVC请求处理流程

SpringMVC相对比较简单,先使用一张图来了解SpringMVC的核心组件和大致处理流程。

从图中可以查看到:

  • DispatcherServlet是SpringMVC中的前端接收器,负责接受Request并将Request转发给相应的处理组件。

  • HandlerMapping是Spring中完成URL到controller映射的组件。DispatcherServlet接受Request然后从HandlerMapping查找处理Request的controller。

  • Controller处理Request,并返回ModelAndView对象。Controller是SpringMVC中负责处理Request的组件(类似于Status2中的Action)ModelAndView是封装结果试图的组件。

  • 4,5,6是视图解析器,解析ModelAndView对象并返回对应的视图给客户端的过程。

容器初始化时会建立所有URL和Controller中方法的对应关系,保存到Handler Mapping中,用户请求时根据请求的 URL 快速定位到Controller 中的某个方法。在 Spring 中先将 URL 和Controller的对应关系保存到Map<url,Controller>中。Web容器启动时会通知Spring初始化容器(加载Bean的定义信息和初始化所有单例Bean),然后SpringMVC会遍历容器中的Bean,获取每一个Controller中的所有方法访问的URL,将URL和Controller保存到一个Map中。这样就可以根据请求快速定位到Controller,因为最终处理请求的是Controller中的方法,Map中只保留了URL和Controller的对应关系,所以要根据请求的URL进一步确认Controller中的方法。其原理就是拼接 Controller 的 URL (Controller 上@RequestMapping 的值)和方法的 URL (Method上@RequestMapping的值),与请求的URL进行匹配,找到匹配的方法。确定处理请求的方法后,接下来的任务就是参数绑定,把请求中的参数绑定到方法的形式参数上,这是整个请求处理过程中最复杂的一步。

二,SpringMVC九大组件

  1. HandlerMapping:用来查找Handler,也就是处理器可以是类,也可以是方法。比如标注了@RequestMapping的每个方法都可以看成一个Handler。Handler负责实际的请求处理,在请求到达后,HandlerMapping的作用便是找到相应的请求器Handler和Interceptor。

  1. HanderAdapter:是一个适配器,在SpringMVC中Handler可以是任意形式,只要可以处理请求便可。但是把请求交给Servlet的时候,由于Servlet的方法结构都是doService(HttpServletRequest,HttpServletResponse)形式,要让固定的Servlet处理方法调用Handler处理,这就是它所做的事情。

  1. HandlerExceptionResolver:主要处理Handler产生的异常情况组件,具体来说,就是根据一场设置ModelAndView,之后交给渲染方法进行渲染,渲染方法会将ModelAndView渲染称页面。需要注意的是HandlerExceptionResolver只用于解析对请求处理阶段的异常,渲染阶段不归他管。

  • ViewResolver:视图解析器,SpringMVC配置文件中都会配置一个实现类进行视图解析。这个组件作用是将String类型的视图名和Locale,解析为view类型的视图,只有一个resolveViewName()方法,Controller层返回String类型的视图名viewName最终会在这里被解析称view,view用来渲染页面,它将程序返回的参数和数据填入模板中,生成HTML文件。ViewResolver主要做两件事 查找到要渲染的模板和所用的技术(技术就是指找到视图类型)。

  1. RequestToViewNameTranslator:从请求中获取ViewName。因为ViewResolver根据ViewName查找View,但有的Handler处理后没有设置View和ViewName。就要用这个组件从请求中查找到ViewName。

  1. LocalleResolver:ViewResolver组件中resolveViewName()方法必要的两个参数一个为视图名,一个为Locale。而Locale从哪里来,这个就是LocaleResolver组件要做的事情。LocalResolver用于从请求中解析除Locale。

  1. ThemeResolver:ThemeResolver组件是用来解析主题的,主题就是样式,图片,及他们所形成的显示要过的集合。SpringMVC中一套主题对应一个properties文件,文件中放了与主题相关的所有资源。ThemeResolver负责从请求中解析出主题名,ThemeSource根据主题名来找到具体的主题,抽象也就是Theme可以通过Theme来获取主题和具体的资源。

  1. MultipartResolver:封装普通请求,使其具有文件上传的功能。处理上传请求时,通过将普通的请求包装成一个MultipartHttpServletRequest来实现。可以通过getFile()获取到文件,如果多个文件可以调取getFileMap() 方法得到Ma<fileName,file>的结构集合。

  1. FlashMapManager: 先说FlashMap用于重定向时的参数传递,就是避免重复提交请求,可以处理完post请求后重定向到一个get请求,这个get请求可以用来显示订单详情之类的信息。但是重定向无法传递参数当然你可以拼接URL,此时可以用flashMap传递。而FlashMapManager就是管理FlashMap的。

三,源码分析

SpringMVC处理过程基本就分为:

1,ApplicationContext初始化时Map保存所有URL和Controller类的对应关系。

2,根据请求URL找到对应的Controller,并从Controller中找到处理请求的方法。

3,将Request参数绑定到方法的形参上,执行方法处理请求,并返回结果视图。

(一),初始化阶段

首先找到DispatcherServlet类,寻找init(),在其父类HttpServletBean中。

    /**
        init()初始话发方法
     */
    @Override
    public final void init() throws ServletException {
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                // 定位资源
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                // 加载配置信息:web.xml
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }

        // 真正完成初始化容器动作的代码 由子类进行实现
        initServletBean();
    }

initServletBean()方法主要就是初始化IOC容器,最终会调用refresh()方法,该方法已经讲解过,不在过多讲解。之后又调用了onRefresh()方法,该方法由DispatcherServlet来进行实现。

// 间接调用    
protected void initStrategies(ApplicationContext context) {
        // 初始化策略
        // 多文件上传的组件
        initMultipartResolver(context);
        // 初始化本地语言环境
        initLocaleResolver(context);
        // 初始化模板处理器
        initThemeResolver(context);
        //  初始换handlMapping
        initHandlerMappings(context);
        // 初始化参数适配器
        initHandlerAdapters(context);
        // 初始化一场拦截器
        initHandlerExceptionResolvers(context);
        // 初始化视图预处理器
        initRequestToViewNameTranslator(context);
        // 初始化视图转换器
        initViewResolvers(context);
        // 初始化Flashmap管理器
        initFlashMapManager(context);
    }

initStrategies()方法完成SpringMVC的九大组件的初始化,接下来看URL与Colltroller的关系是如何建立的。HandlerMapping的子类AbstractDetectingUrlHandlerMapping实现了initApplicationContext()方法,下面查看源码:

    
// 建立当前ApplicationContext中所有Contrller和URL的对应关系
    protected void detectHandlers() throws BeansException {
        ApplicationContext applicationContext = obtainApplicationContext();
        // 获取ApplicationContext中容器所有的Bean名称。
        String[] beanNames = (this.detectHandlersInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
                applicationContext.getBeanNamesForType(Object.class));

        // 遍历Bean并找到这些Bean对应的URL
        for (String beanName : beanNames) {
            // 查找bean上所有URL(controller与方法上的URL),由子类实现。
            String[] urls = determineUrlsForHandler(beanName);
            if (!ObjectUtils.isEmpty(urls)) {
                // 保存urls和BeanName的对应关系,放入Map<urls,beanName>
                // 该方法在父类AbstractUrlHandlerMapping中实现。
                registerHandler(urls, beanName);
            }
        }

        if (mappingsLogger.isDebugEnabled()) {
            mappingsLogger.debug(formatMappingName() + " " + getHandlerMap());
        }
        else if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
            logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
        }
    }

determinUrlsForHander(String beanName)方法就是获取controller中所有的URL,不同子类有不同的实现,这是典型的模板模式。而项目中使用最多的就是用注解方式配置URL,所以BeanNameUrlHandlerMapping是AbstractDetectingUrlHandlerMapping的子类,用于处理注解形式的URL。

    // 获取Cotroller中所有的URL
    @Override
    protected String[] determineUrlsForHandler(String beanName) {
        List<String> urls = new ArrayList<>();
        if (beanName.startsWith("/")) {
            urls.add(beanName);
        }
        String[] aliases = obtainApplicationContext().getAliases(beanName);
        for (String alias : aliases) {
            if (alias.startsWith("/")) {
                urls.add(alias);
            }
        }
        return StringUtils.toStringArray(urls);
    }

此时HandlerMapping组件已经建立了所有URL和Controller的对应关系。

(二),运行调用阶段

运行调用是由请求触发的所以入口为DispatcherServlet的核心方法doService(),该方法中核心有doDispatch()实现:

    // 中央控制器,控制请求的转发
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                // 1,检查是否文件上传的请求
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                /***
                 * 2,去到处理当前请求的Controller,即Handler。
                 * 这里并不是直接返回controller而是返回HanlderExceptionChain请求处理链对象。
                 * 封装了Handler和interceptor
                 */
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // 3,获取请求处理的处理适配器HandlerAdapter
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // 处理last-modified请求头
                String method = request.getMethod();
                boolean isGet = HttpMethod.GET.matches(method);
                if (isGet || HttpMethod.HEAD.matches(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 4,返实际处理器处理请求,返回ModelAndView对象。
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                // 对ModelAndView对象的处理。
                applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                dispatchException = new ServletException("Handler dispatch failed: " + err, err);
            }
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new ServletException("Handler processing failed: " + err, err));
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    // 请求成功响应之后的方法
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // Clean up any resources used by a multipart request.
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

getHandler方法就是从HandlerMapping中找到URL和Controller的对应关系,也就是Map<url,Controller>。最终处理器请求的是Controller中的方法。但是如何确认Controller中处理请求的方法呢!?

从Map取得Controller后,经过拦截器的预处理方法,再通过反射调用方法获取ModelAndView结果视图,然后调用RequestMappingHandlerAdapter的Handle()中的核心代码handlerInternal(request,response,handler)实现。

@Override
    protected ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ModelAndView mav;
        checkRequest(request);

        // Execute invokeHandlerMethod in synchronized block if required.
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    mav = invokeHandlerMethod(request, response, handlerMethod);
                }
            }
            else {
                // No HttpSession available -> no mutex necessary
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // No synchronization on session demanded at all...
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }

        if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
            if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            }
            else {
                prepareResponse(response);
            }
        }

        return mav;
    }

整个过程就是拼接Controller的URL和方法的URL,与Request的URL进行匹配,找到匹配方法,然后找到处理请求的Controller中的方法,解析方法参数,利用反射调用该方法。

invokeAndHandler()最终要实现的目的是,完成请求参数和方法参数上的数据绑定,SpringMVC提供两种从请求参数到方法中参数的绑定方式:

  • 通过注解进行绑定@RequestParam

  • 通过参数名称进行绑定

通过注解绑定秩序在方法参数前面声明@RequestParam("name"),就可以将请求中参数name的值绑定到方法的该参数上。

通过参数名称进行绑定的前提必须获取方法中参数的名称,Java反射只提供了获取方法形参类型的方法,并没有提供获取参数名的方法,SpringMVC使用ASM框架读取字节码文件。

所以使用注解更快,无序asm读取字节码文件的操作。

1,先初始化完成九大组件的初始化操作。

2,DispatcherServlet接受到Request请求,遍历HandlerMapping集合,找到Spring容器中@Controller修饰的bean以及@RequestMapping修饰的方法,封装对象RequestMappingInfo对象。

3,找到对应的HandlerMapping并得到HandlerExceptionChain,内部包含了拦截器。使用内部封装的Handler遍历HandlerAdapter集合找到支持此Handler的HandlerAdapter,并使用处理适配器得到ModelAndView对象。如果发生异常会使用HandlerExceptionResolver策略解决异常。

4,使用ViewResolver来对ModelAndView进行解析得到view后进行返回。

(三),SpringMVC优化建议

1,Controller尽可能使用的单例模式,减少创建对象和回收对象的开销。这样可以减小创建对象和回收对象的开销。也就是说,如果Controller的类变量和实例变量可以以方法形参声明就尽量以方法形参声

明,不要以类变量和实例变量声明,这样可以避免线程安全问题。

2,处理请求的方法尽可能使用@RequestParam注解避免使用asm框架读取字节码文件。

3,源码没有缓存URL,每次都需要根据请求URL去匹配Controller中的方法URL,若进行缓存性能会不会提高?但是负责解析URL和方法对应关系的ServletHandlerMethodResolver是一个私有内部类,无法继承来对其增强,必须在代码后重新编译。当然若URL缓存起来要考虑线程安全问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值