SpringMVC应用、理解及SSM框架整合

2,499 阅读19分钟

题记: 本文对Spring MVC相关知识点做了归纳整理,针对其工作流程及主要组件做了简单说明,也介绍了基本使用及其常用技术;之后,从源码角度对 DispatcherServlet 类继承结构及其中主要方法做了说明,辅助理解 SpringMVC 接收请求后的行为, 最后,简单对 SSM 框架进行整合。

  • 文章内容输出来源:拉勾教育Java高薪训练营;

Spring MVC 介绍

MVC 体系结构

三层架构

在 B/S 架构中,系统标准的三层架构包括:

  • 持久层[dao层]

    负责数据持久化,

    包括数据库和数据访问层。

  • 业务层[service层]

    负责业务逻辑处理,

    依赖持久层

  • 表现层[web层]

    负责接收客户端请求,向客户端响应结果,

    包括展示层和控制层,

    表现层依赖业务层;

    表现层的设计一般使用MVC模型(MVC 是表现层的设计模型,和其他层没有关系)

MVC 设计模式

MVC:Model View Controller,分层是为了解耦,方便维护和分工协作

  • Model(模型)
    • 业务模型:处理业务
    • 数据模型:封装数据
  • View(视图)
    • jsp/html,展示数据
  • Controller(控制器)
    • 处理用户交互的部分,一般做处理程序逻辑

Spring MVC

Spring MVC 全名为 Spring Web MVC,是一种基于 Java 的实现 MVC 设计模式和请求驱动类型的轻量级 Web 框架,属于 SpringFrameWork 的后续产品。是最优秀的 MVC 框架。Spring MVC 本质可以认为是对 servlet 的封装,简化了我们 servlet 的开发。

Spring MVC 工作流程及基本使用

Spring MVC 请求处理流程

SpringMVC请求流程

Spring MVC 主要组件说明

  • HandlerMapping

    作用:找到请求对应的处理器(Handler)和处理器拦截器(HandlerInterceptor)

  • HandlerAdapter

    Spring MVC 中的 Handler 可以是任意形式的(比如标注了@RequestMapping 的每个方法都是一个Handler),但是把请求交给 Servlet 的时候,由于 Servlet 的方法结构都是 doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理方法调用 Handler 来处理,便是 HandlerAdapter 的职责。

  • HandlerExceptionResolver

    处理 Handler 产生的异常情况,会根据异常设置 ModelAndView,之后交给渲染方法渲染为页面

  • ViewResolver

    将 String 类型的视图名和 Locale 解析为 View 类型的视图。Spring MVC 默认配置针对 JSP 类型视图的InternalResourceViewResolver

  • RequestToViewNameTranslator

    从请求中获取 ViewName

  • LocaleResolver

    ViewResolver 组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale。 LocaleResolver ⽤于从请求中解析出 Locale,⽐如中国 Locale 是 zh-CN,⽤来表示⼀个区域。

  • ThemeResolver

    ThemeResolver 组件是⽤来解析主题的

  • MultipartResolver

    MultipartResolver ⽤于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实 现。MultipartHttpServletRequest 可以通过 getFile() ⽅法 直接获得⽂件。如果上传多个⽂件,还 可以调⽤ getFileMap()⽅法得到Map<FileName,File>这样的结构,MultipartResolver 的作⽤就 是封装普通的请求,使其拥有⽂件上传的功能。

  • FlashMapManager

    FlashMap ⽤于重定向时的参数传递

Spring MVC 拦截 url 请求规则设定

  • 方式一:带后缀

    <!-- 举例 -->
    <url-pattern>*.do</url-pattern>
    
  • 方式二:/,/ 不会拦截 .jsp,但是会拦截静态资源(.html等),因为此时覆盖了tomcat 容器中父 web.xml 的 DefaultServlet 的默认规则。

    <!-- 举例 -->
    <url-pattern>/</url-pattern>
    

    解决方法:

    1. 在 springmvc 配置文件中增加<mvc:dafault-servlet-handler> 标签,会在 SpringMVC 上下文中定义 DefaultServletHttpRequestHandler 对象,会对进入 DispatchServlet 的 url 进行过滤,把静态资源直接交给 web 容器去处理。
    2. 配置 <mvc:resources> 标签,当访问规则指定映射下的资源,会去对应位置去找

Spring MVC 封装数据到请求域

方法:声明形参 Model, Map 或 ModelMap,然后调用 addAttribute() 或 put() 方法

原理:运行时的具体类型是 BindingAwareModelMap,BindingAwareModelMap 继承了 ExtendedModelMap,ExtendedModelMap 继承了 ModelMap,实现了 Model 接口。

Spring MVC 请求参数绑定

  1. 默认支持 Servlet API 作为方法参数。

    • 当需要使⽤HttpServletRequest、HttpServletResponse、HttpSession等原⽣servlet对象时,直 接在handler⽅法中形参声明使⽤即可。
  2. 绑定简单数据类型

    • 绑定简单数据类型参数,只需要直接声明形参即可(形参参数名和传递的参数名要保持⼀致,建议使⽤包装类型,当形参参数名和传递参数名不⼀致时可以使⽤@RequestParam注解进⾏⼿动映射)
  3. 绑定 Pojo 类型参数

    • 接收pojo类型参数,直接形参声明即可,类型就是Pojo的类型,形参名⽆所谓,但是要求传递的参数名必须和Pojo的属性名保持⼀致
  4. 绑定Pojo包装对象参数

    • 绑定时候直接形参声明即可
    • 传参参数名和 pojo 属性保持⼀致,如果不能够定位数据项,那么通过属性名 + "." 的⽅式进⼀步锁定数据
  5. 绑定日期类型参数

    • 定义⼀个SpringMVC的类型转换器

      implement Converter<String, Date>

    • 扩展实现接口

      重写 public Date convert(String source) 方法

    • 注册你的实现

      <!--注册⾃定义类型转换器-->
      <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
          <property name="converters">
              <set>
                  <bean class="com.test.converter.DateConverter"></bean>
              </set>
          </property>
      </bean>
      
      <!-- ⾃动注册最合适的处理器映射器,处理器适配器(调⽤handler⽅法)-->
      <mvc:annotation-driven conversionservice="conversionServiceBean"/>
      

对 RESTful 风格的支持

RESTful

Restful 是⼀种 web 软件架构⻛格,它不是标准也不是协议,它倡导的是⼀个资源定位及资源操作的⻛格。

REST(Representational State Transfer)

资源在网络中以某种表现形式进行状态转移

  • 资源(Resource):⽹络上的⼀个实体,或者说是⽹络上的⼀个具体信息。

  • 表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层 (Representation),比如用JSON,XML,JPEG等;

  • 状态转化(State Transfer):状态变化。通过HTTP动词实现。

    每发出⼀个请求,就代表了客户端和服务器的⼀次交互过程。HTTP 协议,是⼀个⽆状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器, 必须通过某种⼿段,让服务器端发⽣“状态转化”(State Transfer)。⽽这种转化是建⽴在表现层 之上的,所以就是 “ 表现层状态转化” 。具体说, 就是 HTTP 协议⾥⾯,四个表示操作⽅式的动词: GET 、POST 、PUT 、DELETE 。它们分别对应四种基本操作:GET ⽤来获取资源,POST ⽤来新建资 源,PUT ⽤来更新资源,DELETE ⽤来删除资源。

Spring MVC 对 RESTful 的支持

  • @PathVariable 注解获取 RESTful ⻛格的请求 URL中的路径变量。

  • 配置springmvc请求⽅式转换过滤器,会检查请求参数中是否有_method参数,如果有就按照指定的请求⽅式进⾏转换

    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filterclass>org.springframework.web.filter.HiddenHttpMethodFilter</filterclass>
    </filter>
    
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

Ajax Json 交互

  • 前端到后台:前端ajax发送json格式字符串,后台直接接收为pojo参数,使⽤注解@RequstBody
  • 后台到前端:后台直接返回pojo对象,前端直接接收为json对象或者字符串,使⽤注解 @ResponseBody

Spring MVC 高级技术

拦截器 (Interceptor) 使用

监听器、过滤器和拦截器对比

  • 监听器(Listener):实现了 javax.servlet.ServletContextListener 接⼝的服务器端组件,它随 Web应⽤的启动⽽启动,只初始化⼀次,然后会⼀直运⾏监视,随Web应⽤的停⽌⽽销毁
    • 作⽤⼀:做⼀些初始化⼯作,web应⽤中spring容器启动ContextLoaderListener
    • 作⽤⼆:监听web中的特定事件,⽐如HttpSession,ServletRequest的创建和销毁;变量的创建、 销毁和修改等。可以在某些动作前后增加处理,实现监控,⽐如统计在线⼈数,利⽤HttpSessionLisener等。
  • 过滤器(Filter):对Request请求起到过滤的作⽤,作⽤在Servlet之前,如果配置为/*可以对所有的资源访问(servlet、js/css静态资源等)进⾏过滤处理
  • 拦截器(Interceptor):是SpringMVC、Struts等表现层框架⾃⼰的,不会拦截 jsp/html/css/image的访问等,只会拦截访问的控制器⽅法(Handler)
    • 在Handler业务逻辑执⾏之前拦截⼀次
    • 在Handler逻辑执⾏完毕但未跳转⻚⾯之前拦截⼀次
    • 在跳转⻚⾯之后拦截⼀次

单个拦截器的执行流程

多个拦截器执行流程

注:Interceptor1 配置在前

自定义拦截器

  1. 定义类实现 HandlerInterceptor 接口

  2. 重写相应方法

  3. 注册 Spring MVC 拦截器

    <mvc:interceptors>
        <!--拦截所有handler-->
        <!--<bean class="com.lagou.edu.interceptor.MyIntercepter01"/>-->
        
        <mvc:interceptor>
            <!--配置当前拦截器的url拦截规则,**代表当前⽬录下及其⼦⽬录下的所有url-->
            <mvc:mapping path="/**"/>
            <!--exclude-mapping可以在mapping的基础上排除⼀些url拦截-->
            <!--<mvc:exclude-mapping path="/demo/**"/>-->
            <bean class="com.test.interceptor.MyIntercepter01"/>
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.test.interceptor.MyIntercepter02"/>
        </mvc:interceptor>
    </mvc:interceptors>
    

处理 multipart 形式的数据

  1. 所需依赖

    <!--⽂件上传所需jar坐标-->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
    </dependency>
    
  2. 配置文件上传解析器

    <!--配置⽂件上传解析器,id是固定的multipartResolver-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--设置上传⼤⼩,单位字节-->
        <property name="maxUploadSize" value="1000000000"/>
    </bean>
    
  3. handle 声明形参 MultipartFile uploadFile,形参名与上传文件名相同

在控制器中处理异常

  1. 捕捉当前 Controller 下异常

    在当前 Controller 下创建方法,贴@ExceptionHandler 注解,声明异常类型,进行处理

  2. 全局异常处理

    创建类,贴@ControllerAdvice注解,其中可创建多个方法,针对不同异常进行捕捉处理。

基于 Flash 属性的跨重定向请求数据传递

解决重定向参数丢失问题:

  1. 手动拼接参数

    缺点:属于 get 请求,参数长度和安全性均有限制。

  2. 声明参数 RedirectAttributes redirectAttributes, 使用 redirectAttributes.addAttribute(key, value)

    缺点:同上

  3. 声明参数 RedirectAttributes redirectAttributes, 使用 redirectFlashAttributes.addAttribute(key, value), 该方法设置了一个 flash 类型属性, 该属性会被暂存到 session 中,在跳转到页面后该属性销毁。

Spring MVC 源码剖析

前端控制器 DispatcherServlet 继承结构

doDispatch() 源码分析

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);

            // Determine handler for the current request.
           	// 2 取得处理当前请求的Controller,这里也称为Handler,即处理器
            // 这里并不是直接返回 Controller,而是返回 HandlerExecutionChain 请求处理链对象,该对象封装了Handler和Inteceptor
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                // 如果 handler 为空,则返回404
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            // 3 获取处理请求的处理器适配器 HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            // 处理 last-modified 请求头
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet)  {
                    return;
                }
            }

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

            // Actually invoke the handler.
            // 4 实际处理器处理请求,返回结果视图对象
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
            // 结果视图对象的处理
            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }catch (Exception ex) {
            dispatchException = ex;
        }catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        
        // 5 跳转页面,渲染视图
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    
    }catch (Exception ex) {
        //最终会调用HandlerInterceptor的afterCompletion 方法
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }catch (Throwable err) {
        //最终会调用HandlerInterceptor的afterCompletion 方法
        triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", 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() 源码分析

试图从BeanNameUrlHandlerMappingRequestMappingHandlerMapping 中获取能够处理当前请求的执行链

gethandlerAdapter() 源码分析

遍历各个HandlerAdapter,看哪个Adapter⽀持处理当前Handler

ha.handle() 源码分析

ha.handle() 调用 handleInternal()

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

    ModelAndView mav;
    checkRequest(request);

    // Execute invokeHandlerMethod in synchronized block if required.
    // 判断当前是否需要支持在同一个session中只能线性地处理请求
    if (this.synchronizeOnSession) {
        // 获取当前请求的session对象
        HttpSession session = request.getSession(false);
        if (session != null) {
            // 为当前session生成一个唯一的可以用于锁定的key
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                // 对HandlerMethod进行参数等的适配处理,并调用目标handler
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        } else {
            // No HttpSession available -> no mutex necessary
            // 如果当前不存在session,则直接对HandlerMethod进行适配
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    } else {
        // No synchronization on session demanded at all...
        // 如果当前不需要对session进行同步处理,则直接对HandlerMethod进行适配
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }

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

invokeHandlerMethod()

protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        // 获取容器中全局配置的InitBinder和当前HandlerMethod所对应的Controller中配置的InitBinder,用于进行参数的绑定
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        // 获取容器中全局配置的ModelAttribute和当前当前HandlerMethod所对应的Controller中配置的ModelAttribute,这些配置的方法将会在目标方法调用之前进行调用
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        // 将handlerMethod封装为一个ServletInvocableHandlerMethod对象
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) {
            // 设置当前容器中配置的所有ArgumentResolver
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            // 设置当前容器中配置的所有ReturnValueHandler
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        // 将前面创建的WebDataBinderFactory设置到ServletInvocableHandlerMethod中
        invocableMethod.setDataBinderFactory(binderFactory);

        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        // 这里initModel()方法主要作用是调用前面获取到的@ModelAttribute标注的方法,
        // 从而达到@ModelAttribute标注的方法能够在目标Handler调用之前调用的目的
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.setTaskExecutor(this.taskExecutor);
        asyncManager.setAsyncWebRequest(asyncWebRequest);
        asyncManager.registerCallableInterceptors(this.callableInterceptors);
        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

        if (asyncManager.hasConcurrentResult()) {
            Object result = asyncManager.getConcurrentResult();
            mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
            asyncManager.clearConcurrentResult();
            LogFormatUtils.traceDebug(logger, traceOn -> {
                String formatted = LogFormatUtils.formatValue(result, !traceOn);
                return "Resume with async result [" + formatted + "]";
            });
            invocableMethod = invocableMethod.wrapConcurrentResult(result);
        }

        // 对请求参数进行处理,调用目标HandlerMethod,并且将返回值封装为一个ModelAndView对象
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }

        // 对封装的ModelAndView进行处理,主要是判断当前请求是否进行了重定向,如果进行了重定向,
        // 还会判断是否需要将FlashAttributes封装到新的请求中
        return getModelAndView(mavContainer, modelFactory, webRequest);
    } finally {
        webRequest.requestCompleted();
    }
}

invokeAndHandle()

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    // 对目标handler的参数进行处理,并且调用目标handler
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    // 设置相关的返回状态
    setResponseStatus(webRequest);

    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null ||  mavContainer.isRequestHandled()) {
            disableContentCachingIfNecessary(webRequest);
            mavContainer.setRequestHandled(true);
            return;
        }
    } else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    } catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(formatErrorForReturnValue(returnValue), ex);
        }
        throw ex;
    }
}

invokeForRequest()

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    // 将request中的参数转换为当前handler的参数形式
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Arguments: " + Arrays.toString(args));
    }
    // 这里doInvoke()方法主要是结合处理后的参数,使用反射对目标方法进行调用
    return doInvoke(args);
}

processDispatchResult() 源码解析

processDispatchResult() 调用 render() 方法

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Determine locale for request and apply it to the response.
    Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    response.setLocale(locale);

    View view;
    String viewName = mv.getViewName();
    if (viewName != null) {
        // We need to resolve the view name.
        // 根据视图解析器解析出View视图对象
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        if (view == null) {
            throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'");
        }
    } else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        view = mv.getView();
        if (view == null) { 
            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'");
        }
    }

    // Delegate to the View object for rendering.
    if (logger.isTraceEnabled()) {
        logger.trace("Rendering view [" + view + "] ");
    }
    try {
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        
        // 调用 view 对象的render方法
        view.render(mv.getModelInternal(), request, response);
    } catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "]", ex);
        }
        throw ex;
    }
}

resolveViewName() 方法

protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
    if (this.viewResolvers != null) {
        for (ViewResolver viewResolver : this.viewResolvers) {
            // 视图解析器解析出View视图对象
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}
  • resolveViewName() 调用 createView() 方法

  • createView() 方法 在解析出View视图对象的过程中会判断是否重定向、是否转发等,不同的情况封装的是不同的 View实现

  • 调用super.createView()方法

  • createView() 调用 loadView()

  • loadView() 调用 buildView()

  • buildView() 中将逻辑视图名解析为物理视图名

view.render()

public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    if (logger.isDebugEnabled()) {
        logger.debug("View " + formatViewName() + ", model " + (model != null ? model : Collections.emptyMap()) + (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
    }
   
    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);
    // 渲染数据
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

renderMergedOutputModel()

protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    // Expose the model object as request attributes.
    // 把modelMap中的数据暴露到request域中
    exposeModelAsRequestAttributes(model, request);

    // Expose helpers as request attributes, if any.
    exposeHelpers(request);

    // Determine the path for the request dispatcher.
    String dispatcherPath = prepareForRendering(request, response);

    // Obtain a RequestDispatcher for the target resource (typically a JSP).
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    if (rd == null) {
        throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!");
    }

    // If already included or response already committed, perform include, else forward.
    if (useInclude(request, response)) {
        response.setContentType(getContentType());
        if (logger.isDebugEnabled()) {
            logger.debug("Including [" + getUrl() + "]");
        }
        rd.include(request, response);
    } else {
        // Note: The forwarded resource is supposed to determine the content type itself.
        if (logger.isDebugEnabled()) {
            logger.debug("Forwarding to [" + getUrl() + "]");
        }
        rd.forward(request, response);
    }
}

exposeModelAsRequestAttributes()

protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
    model.forEach((name, value) -> {
        if (value != null) {
            // 将数据设置到请求域中
            request.setAttribute(name, value);
        } else {
            request.removeAttribute(name);
        }
    });
}

Spring MVC 组件初始化

  1. 在DispatcherServlet中定义了九个属性,每⼀个属性都对应⼀种组件

    /** MultipartResolver used by this servlet. */
    // 多部件解析器
    @Nullable private MultipartResolver multipartResolver;
    /** LocaleResolver used by this servlet. */
    // 区域化 国际化解析器
    @Nullable private LocaleResolver localeResolver;
    /** ThemeResolver used by this servlet. */
    // 主题解析器
    @Nullable private ThemeResolver themeResolver;
    /** List of HandlerMappings used by this servlet. */
    // 处理器映射器组件
    @Nullable private List<HandlerMapping> handlerMappings;
    /** List of HandlerAdapters used by this servlet. */
    // 处理器适配器组件
    @Nullableprivate List<HandlerAdapter> handlerAdapters;
    /** List of HandlerExceptionResolvers used by this servlet. */
    // 异常解析器组件
    @Nullable private List<HandlerExceptionResolver> handlerExceptionResolvers;
    /** RequestToViewNameTranslator used by this servlet. */
    // 默认视图名转换器组件
    @Nullable private RequestToViewNameTranslator viewNameTranslator;
    /** FlashMapManager used by this servlet. */
    // flash属性管理组件
    @Nullable private FlashMapManager flashMapManager;
    /** List of ViewResolvers used by this servlet. */
    // 视图解析器
    @Nullable private List<ViewResolver> viewResolvers;
    
    • 九⼤组件都是定义了接⼝,接⼝其实就是定义了该组件的规范,⽐如ViewResolver、HandlerAdapter 等都是接⼝
  2. 九⼤组件的初始化时机

    • DispatcherServlet中的onRefresh(),该⽅法中初始化了九⼤组件

      protected void onRefresh(ApplicationContext context) {
          // 初始化策略
          initStrategies(context);
      }
      
    • initStrategies()

      protected void initStrategies(ApplicationContext context) {
          // 多文件上传的组件
          initMultipartResolver(context);
          // 初始化本地语言环境
          initLocaleResolver(context);
          // 初始化模板处理器
          initThemeResolver(context);
          // 初始化HandlerMapping
          initHandlerMappings(context);
          // 初始化参数适配器
          initHandlerAdapters(context);
          // 初始化异常拦截器
          initHandlerExceptionResolvers(context);
          // 初始化视图预处理器
          initRequestToViewNameTranslator(context);
          // 初始化视图转换器
          initViewResolvers(context);
          // 初始化 FlashMap 管理器
          initFlashMapManager(context);
      }
      
  3. initHandlerMappings(context)

    private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;
        
        if (this.detectAllHandlerMappings) {
            // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
            // 在IoC 容器中按照 HandlerMapping.class 找到所有的HandlerMapping
            Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<>(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        } else {
            try {
                // 否则在ioc中按照固定名称id(handlerMapping)去找
                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.isTraceEnabled()) {
                logger.trace("No HandlerMappings declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties");
            }
        }
    }
    

    getDefaultStrategies()

    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        String key = strategyInterface.getName();
        // DispatcherServlet.properties
        String value = defaultStrategies.getProperty(key);
        if (value != null) {
            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
            List<T> strategies = new ArrayList<>(classNames.length);
            for (String className : classNames) {
                try {
                    Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                    Object strategy = createDefaultStrategy(context, clazz);
                    strategies.add((T) strategy);
                } catch (ClassNotFoundException ex) {
                    throw new BeanInitializationException("Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", ex);
                } catch (LinkageError err) {
                    throw new BeanInitializationException("Unresolvable class definition for DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", err);
                }
            }
            return strategies;
        } else {
            return new LinkedList<>();
        }
    }
    

    DispatcherServlet.properties

    org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
    
    org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
    
    org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
    
    org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
    
    org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
    	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
    
    org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
    
    org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
    
    org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
    
  4. initMultipartResolver()

    private void initMultipartResolver(ApplicationContext context) {
        try {
            // MULTIPART_RESOLVER_BEAN_NAME = mulitipartResolver
            this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
            if (logger.isTraceEnabled()) {
                logger.trace("Detected " + this.multipartResolver);
            } else if (logger.isDebugEnabled()) {
                logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
            }
        } catch (NoSuchBeanDefinitionException ex) {
            // Default is no multipart resolver.
            this.multipartResolver = null;
            if (logger.isTraceEnabled()) {
                logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
            }
        }
    }
    
    • 注:多部件解析器的初始化必须按照id注册对象(multipartResolver)

SSM框架整合

流程

  1. 引入依赖

  2. Spring 配置文件 applicationContext.xml 的配置

    • 包扫描<context:component-scan base-package="com.test.xx"/>
    • bean dataSource
      • 可把相应参数提取出去,再引入外部资源文件<context:property-placeholder location="classpath:jdbc.properties"/>
    • bean sqlSessionFactory
      • 原来 mybaits 中 sqlSessionFactory 的构建需要的 SqlMapConfig.xml中的内容,可在此标签下寻找相应的属性进行设置
    • Mapper动态代理对象交给Spring管理
    • 事务管理:bean transactionManager
    • 事务管理注解驱动
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="
                               http://www.springframework.org/schema/beans
                               http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context
                               http://www.springframework.org/schema/context/spring-context.xsd
                               http://www.springframework.org/schema/tx
                               http://www.springframework.org/schema/tx/spring-tx.xsd">
        <!--包扫描-->
        <context:component-scan base-package="com.test.xx"/>
    
        <!--数据库连接池以及事务管理都交给Spring容器来完成-->
    
        <!--引⼊外部资源⽂件-->
        <context:property-placeholder location="classpath:jdbc.properties"/>
        
        <!--第三⽅jar中的bean定义在xml中-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
        
        <!--SqlSessionFactory对象应该放到Spring容器中作为单例对象管理 原来mybaits中sqlSessionFactory的构建是需要素材的:SqlMapConfig.xml中的内容 -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!--别名映射扫描-->
            <property name="typeAliasesPackage" value="com.test.xx.pojo"/>
            <!--数据源dataSource-->
            <property name="dataSource" ref="dataSource"/>
        </bean>
        
        <!--Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对 象-->
        <!--扫描mapper接⼝,⽣成代理对象,⽣成的代理对象会存储在ioc容器中-->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!--mapper接⼝包路径配置-->
            <property name="basePackage" value="com.test.xx.mapper"/>
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        </bean>
        
        <!--事务管理-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
        
        <!--事务管理注解驱动-->
        <tx:annotation-driven transaction-manager="transactionManager"/>
    </beans>
    
  3. springmvc.xml 配置文件

    • 扫描 Controller
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                               http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context
                               http://www.springframework.org/schema/context/springcontext.xsd
                               http://www.springframework.org/schema/mvc
                               http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!--扫描controller-->
        <context:component-scan base-package="com.lagou.edu.controller"/>
        
        <mvc:annotation-driven/>
    </beans>
    

    Spring容器和SpringMVC容器是有层次的(⽗⼦容器)

    Spring容器:service对象+dao对象

    SpringMVC容器:controller对象,,,,可以引⽤到Spring容器中的对象

  4. web.xml

    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" >
    <web-app>
        <display-name>Archetype Created Web Application</display-name>
        
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:applicationContext*.xml</param-value>
        </context-param>
        
        <!--spring框架启动-->
        <listener>
            <listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
        </listener>
    
        <!--springmvc启动-->
        <servlet>
            <servlet-name>springmvc</servlet-name><servletclass>org.springframework.web.servlet.DispatcherServlet</servletclass>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath*:springmvc.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    
  5. Dao、Service、Controller各层代码完善