【拉钩笔记】SpringMVC学习笔记

503 阅读28分钟

Spring MVC高级框架

Spring MVC应用

Spring MVC 简介

  • 深刻理解经典三层及MVC模式

    三层架构

    我们的开发架构⼀般都是基于两种形式,⼀种是 C/S 架构,也就是客户端/服务器;另⼀种是 B/S 架构 ,也就是浏览器服务器。在 JavaEE 开发中,⼏乎全都是基于 B/S 架构的开发。那么在 B/S 架构中,系 统标准的三层架构包括:表现层、业务层、持久层。

    • 表现层 :表现层包括展示层和控制层:控制层负责接收请求,展示层负责结果的展示。 表现层依赖业务层,接收到客户端请求⼀般会调⽤业务层进⾏业务处理,并将处理结果响应给客户端。 表现层的设计⼀般都使⽤ MVC 模型。(MVC 是表现层的设计模型,和其他层没有关系)
    • 业务层:也就是我们常说的 service 层。它负责业务逻辑处理,和我们开发项⽬的需求息息相关。web 层依赖业务层,但是业务层不依赖 web 层。业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务⼀致性。(也就是我们说的, 事务应该放到业务层来控制)
    • 持久层:也就是我们是常说的 dao 层。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进行持久化的载体,数据访问层是业务层和持久层交互的接⼝,业务层需要通过数据访问层将数据持久化到数据库中。通俗的讲,持久层就是和数据库交互,对数据库表进行增删改查的。

    MVC 模式(代码组织的方法/形式)

    MVC 全名是 Model View Controller,是 模型(model)-视图(view)-控制器(controller) 的缩写, 是⼀ 种⽤于设计创建 Web 应⽤程序表现层的模式。MVC 中每个部分各司其职: Model(模型):模型包含业务模型和数据模型,数据模型⽤于封装数据,业务模型⽤于处理业 务。 View(视图): 通常指的就是我们的 jsp 或者 html。作⽤⼀般就是展示数据的。通常视图是依据 模型数据创建的。 Controller(控制器): 是应⽤程序中处理⽤户交互的部分。作⽤⼀般就是处理程序逻辑的。 MVC提倡:每⼀层只编写自己的东西,不编写任何其他的代码;分层是为了解耦,解耦是为了维 护⽅便和分⼯协作。

  • Spring MVC

    SpringMVC 全名叫 Spring Web MVC,是⼀种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于 SpringFrameWork 的后续产品。

    SpringMVC 已经成为目前最主流的 MVC 框架 之⼀,并且 随着 Spring3.0 的发布,全面超越 Struts2, 成为最优秀的 MVC 框架。 springmvc中要让⼀个java类能够处理请求只需要添加注解就行。它通过⼀套注解,让⼀个简单的 Java 类成为处理请求的控制器,⽽⽆须实现任何接⼝。同时它还⽀持RESTful 编程⻛格的请求。 总之:Spring MVC和Struts2⼀样,都是 为了解决表现层问题 的web框架,它们都是基于 MVC 设计模 式的。而这些表现层框架的主要职责就是处理前端HTTP请求。 Spring MVC 本质可以认为是对servlet的封装,简化了我们serlvet的开发 作用:1)接收请求 2)返回响应,跳转页面

Spring Web MVC 工作流程

  • SpringMVC请求处理流程解析

    流程说明 第⼀步:用户发送请求⾄前端控制器DispatcherServlet 第⼆步:DispatcherServlet收到请求调⽤HandlerMapping处理器映射器 第三步:处理器映射器根据请求Url找到具体的Handler(后端控制器),生成处理器对象及处理器拦截 器(如果 有则生成成)⼀并返回DispatcherServlet 第四步:DispatcherServlet调⽤HandlerAdapter处理器适配器去调⽤Handler 第五步:处理器适配器执行Handler 第六步:Handler执⾏完成给处理器适配器返回ModelAndView 第七步:处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的⼀个 底层对 象,包括 Model 和 View 第⼋步:前端控制器请求视图解析器去进行视图解析,根据逻辑视图名来解析真正的视图。 第九步:视图解析器向前端控制器返回View 第⼗步:前端控制器进行视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域 第十一步:前端控制器向用户响应结果

  • SpringMVC九大组件

    • HandlerMapping(处理器映射器) HandlerMapping 是⽤来查找 Handler 的,也就是处理器,具体的表现形式可以是类,也可以是 ⽅法。⽐如,标注了@RequestMapping的每个⽅法都可以看成是⼀个Handler。Handler负责具 体实际的请求处理,在请求到达后,HandlerMapping 的作⽤便是找到请求相应的处理器 Handler 和 Interceptor.

    • HandlerAdapter(处理器适配器) HandlerAdapter 是⼀个适配器。因为 Spring MVC 中 Handler 可以是任意形式的,只要能处理请 求即可。但是把请求交给 Servlet 的时候,由于 Servlet 的⽅法结构都是 doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理 ⽅法调⽤ Handler 来进⾏处理,便是 HandlerAdapter 的职责。

    • HandlerExceptionResolver HandlerExceptionResolver ⽤于处理 Handler 产⽣的异常情况。它的作⽤是根据异常设置 ModelAndView,之后交给渲染⽅法进⾏渲染,渲染⽅法会将 ModelAndView 渲染成⻚⾯。 ViewResolver

    • ViewResolver(即视图解析器),用于将String类型的视图名和Locale解析为View类型的视图,只有⼀ 个resolveViewName()⽅法。从⽅法的定义可以看出,Controller层返回的String类型视图名 viewName 最终会在这⾥被解析成为View。View是⽤来渲染⻚⾯的,也就是说,它会将程序返回 的参数和数据填⼊模板中,⽣成html⽂件。ViewResolver 在这个过程主要完成两件事情: ViewResolver 找到渲染所⽤的模板(第⼀件⼤事)和所⽤的技术(第⼆件⼤事,其实也就是找到 视图的类型,如JSP)并填⼊参数。默认情况下,Spring MVC会⾃动为我们配置⼀个 InternalResourceViewResolver,是针对 JSP 类型视图的。

    • RequestToViewNameTranslator RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName.因为 ViewResolver 根据 ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName, 便要通过这个组件从请求中查找 ViewName。

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

    • ThemeResolver ThemeResolver 组件是⽤来解析主题的。主题是样式、图⽚及它们所形成的显示效果的集合。 Spring MVC 中⼀套主题对应⼀个 properties⽂件,⾥⾯存放着与当前主题相关的所有资源,如图 ⽚、CSS样式等。创建主题⾮常简单,只需准备好资源,然后新建⼀个“主题名.properties”并将资 源设置进去,放在classpath下,之后便可以在⻚⾯中使⽤了。SpringMVC中与主题相关的类有 ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名, ThemeSource根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和 具体的资源。

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

    • FlashMapManager FlashMap ⽤于重定向时的参数传递,⽐如在处理⽤户订单时候,为了避免重复提交,可以处理完 post请求之后重定向到⼀个get请求,这个get请求可以⽤来显示订单详情之类的信息。这样做虽然 可以规避⽤户重新提交订单的问题,但是在这个⻚⾯上要显示订单的信息,这些数据从哪⾥来获得 呢?因为重定向时么有传递参数这⼀功能的,如果不想把参数写进URL(不推荐),那么就可以通 过FlashMap来传递。只需要在重定向之前将要传递的数据写⼊请求(可以通过 ServletRequestAttributes.getRequest()⽅法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE 中,这样在重定向之后的Handler中Spring就会⾃动将其设置到Model中,在显示订单信息的⻚⾯ 上就可以直接从Model中获取数据。FlashMapManager 就是⽤来管理 FalshMap 的。

      这九大组件都声明在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. */
      	@Nullable
      	private 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. */
      	@Nullable
      	private FlashMapManager flashMapManager;
      
      	/** List of ViewResolvers used by this servlet. */
      	@Nullable
      	private List<ViewResolver> viewResolvers;
      
  • url-pattern配置及原理解析

    web.xml

        <!--
          方式一:带后缀,比如*.action  *.do *.aaa
                 该种方式比较精确、方便,在以前和现在企业中都有很大的使用比例
          方式二:/ 不会拦截 .jsp,但是会拦截.html等静态资源(静态资源:除了servlet和jsp之外的js、css、png等)
     为什么配置为/ 会拦截静态资源???
      因为tomcat容器中有一个web.xml(父),你的项目中也有一个web.xml(子),是一个继承关系
                父web.xml中有一个DefaultServlet,  url-pattern 是一个 /
                时我们自己的web.xml中也配置了一个 / ,覆写了父web.xml的配置
       为什么不拦截.jsp呢?
        因为父web.xml中有一个JspServlet,这个servlet拦截.jsp文件,而我们并没有覆写这个配置 所以springmvc此时不拦截jsp,jsp的处理交给了tomcat
    如何解决/拦截静态资源这件事?
    		交给SpringMVC处理
          方式三:/* 拦截所有,包括.jsp
        -->
        <!--拦截匹配规则的url请求,进入springmvc框架处理-->
        <url-pattern>/</url-pattern>
    

    springmvc.xml

        <!--静态资源配置,方案一-->
        <!--
            原理:添加该标签配置之后,会在SpringMVC上下文中定义一个DefaultServletHttpRequestHandler对象
                 这个对象如同一个检查人员,对进入DispatcherServlet的url请求进行过滤筛查,如果发现是一个静态资源请求
                 那么会把请求转由web应用服务器(tomcat)默认的DefaultServlet来处理,如果不是静态资源请求,那么继续由
                 SpringMVC框架处理
        -->
        <!--<mvc:default-servlet-handler/>-->
    
    
    
        <!--静态资源配置,方案二,SpringMVC框架自己处理静态资源
            mapping:约定的静态资源的url规则
            location:指定的静态资源的存放位置
    
        -->
        <mvc:resources location="classpath:/"  mapping="/resources/**"/>
        <mvc:resources location="/WEB-INF/js/" mapping="/js/**"/>
    
  • 数据输出机制Model,Map,ModleMap

    SpringMVC在handler方法上传入Map、Model和ModelMap参数,并向这些参数中保存数据(放入到请求域),都可以在页面获取到.。它们之间是什么关系? 运行时的具体类型都是BindingAwareModelMap,相当于给BindingAwareModelMap中保存的数据都会放在请求域中。

    Map(jdk中的接口)

    Model(spring的接口

    ModelMap(class,实现Map接口)

    BindingAwareModelMap继承了ExtendedModelMap,ExtendedModelMap继承了ModelMap,实现了Model接口

请求参数绑定

  • 默认⽀持 Servlet API 作为方法参数

    当需要使⽤HttpServletRequest、HttpServletResponse、HttpSession等原⽣servlet对象时,直 接在handler⽅法中形参声明使⽤即可。

  • 简单类型参数绑定

    简单数据类型:⼋种基本数据类型及其包装类型 参数类型推荐使⽤包装数据类型,因为基础数据类型不可以为null 整型:Integer、int 字符串:String 单精度:Float、float 双精度:Double、double 布尔型:Boolean、boolean 说明:对于布尔类型的参数,请求的参数值为true或false。或者1或0 注意:绑定简单数据类型参数,只需要直接声明形参即可(形参参数名和传递的参数名要保持⼀ 致,建议 使⽤包装类型,当形参参数名和传递参数名不⼀致时可以使⽤@RequestParam注解进⾏ ⼿动映射)

     public ModelAndView handle03(@RequestParam("ids") Integer id,Boolean
    flag) {
     Date date = new Date();
     ModelAndView modelAndView = new ModelAndView();
     modelAndView.addObject("date",date);
     modelAndView.setViewName("success");
     return modelAndView;
     }
    
  • POJO类型参数

    /*
     * SpringMVC接收pojo类型参数 url:/demo/handle04?id=1&username=zhangsan
     *
     * 接收pojo类型参数,直接形参声明即可,类型就是Pojo的类型,形参名⽆所谓
     * 但是要求传递的参数名必须和Pojo的属性名保持⼀致
     */
     @RequestMapping("/handle04")
     public ModelAndView handle04(User user) {
     	Date date = new Date();
     	ModelAndView modelAndView = new ModelAndView();
     	modelAndView.addObject("date",date);
     	modelAndView.setViewName("success");
    	 return modelAndView;
     }
    

    当Pojo为包装对象

    public class QueryVo {
         private String mail;
         private String phone;
         // 嵌套了另外的Pojo对象
         private User user;
         public String getMail() {
         return mail;
         }
         public void setMail(String mail) {
         this.mail = mail;
         }
         public String getPhone() {
         return phone;
         }
         public void setPhone(String phone) {
         this.phone = phone;
         }
         public User getUser() {
         return user;
         }
         public void setUser(User user) {
         this.user = user;
         }
    }
    
    /*
     * SpringMVC接收pojo包装类型参数 url:/demo/handle05?
    user.id=1&user.username=zhangsan&mail=xx&phone=xx
     * 不管包装Pojo与否,它⾸先是⼀个pojo,那么就可以按照上述pojo的要求来
     * 1、绑定时候直接形参声明即可
     * 2、传参参数名和pojo属性保持⼀致,如果不能够定位数据项,那么通过属性名 + "." 的
    ⽅式进⼀步锁定数据
     *
     */
     @RequestMapping("/handle05")
     public ModelAndView handle05(QueryVo queryVo) {
     Date date = new Date();
     ModelAndView modelAndView = new ModelAndView();
     modelAndView.addObject("date",date);
     modelAndView.setViewName("success");
     return modelAndView;
     }
    
  • 日期类型参数

    对于日期参数需要自定义类型转换器并进行配置。

    自定义类型转换器

    public class DateConverter implements Converter<String, Date> {
         @Override
         public Date convert(String source) {
             // 完成字符串向⽇期的转换(根据传递日期格式)
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            try {
             Date parse = simpleDateFormat.parse(source);
             return parse;
            } catch (ParseException e) {
             e.printStackTrace();
            }
             return null;
         }
    }
    

    注册自定义类型转换器

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

对Restfu 风格请求支持

  • 深刻理解Rest风格

    Restful 是⼀种 web 软件架构⻛格,它不是标准也不是协议,它倡导的是⼀个资源定位及资源操作的⻛ 格。 什么是 REST: REST(英⽂:Representational State Transfer,简称 REST)描述了⼀个架构样式的⽹络系统, 比如 web 应⽤程序。它⾸次出现在 2000 年 Roy Fielding 的博⼠论⽂中,他是 HTTP 规范的主要编写者之 ⼀。在⽬前主流的三种 Web 服务交互⽅案中,REST 相⽐于 SOAP(Simple Object Access protocol, 简单对象访问协议)以及 XML-RPC 更加简单明了,⽆论是对 URL 的处理还是对 Payload 的编码, REST 都倾向于⽤更加简单轻量的⽅法设计和实现。值得注意的是 REST 并没有⼀个明确的标准,⽽更像 是⼀种设计的⻛格。 它本身并没有什么实⽤性,其核⼼价值在于如何设计出符合 REST ⻛格的⽹络接⼝。 资源 表现层 状态转移 Restful 的优点 它结构清晰、符合标准、易于理解、扩展⽅便,所以正得到越来越多⽹站的采⽤。 Restful 的特性 资源(Resources):⽹络上的⼀个实体,或者说是⽹络上的⼀个具体信息。 它可以是⼀段⽂本、⼀张图⽚、⼀⾸歌曲、⼀种服务,总之就是⼀个具体的存在。可以⽤⼀个 URI(统 ⼀资源定位符)指向它,每种资源对应⼀个特定的 URI 。要获取这个资源,访问它的 URI 就可以,因此 URI 即为每⼀个资源的独⼀⽆⼆的识别符。 表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层 (Representation)。⽐ 如,⽂本可以⽤ txt 格式表现,也可以⽤ HTML 格式、XML 格式、JSON 格式表现,甚⾄可以采⽤⼆进 制格式。 状态转化(State Transfer):每发出⼀个请求,就代表了客户端和服务器的⼀次交互过程。 HTTP 协议,是⼀个⽆状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务 器, 必须通过某种⼿段,让服务器端发⽣“状态转化”(State Transfer)。⽽这种转化是建⽴在表现层 之上的,所以就是 “ 表现层状态转化” 。具体说, 就是 HTTP 协议⾥⾯,四个表示操作⽅式的动词: GET 、POST 、PUT 、DELETE 。它们分别对应四种基本操作:GET ⽤来获取资源,POST ⽤来新建资 源,PUT ⽤来更新资源,DELETE ⽤来删除资源。

  • SpringMVC对Rest风格请求的支持

    RESTful ⻛格 URL:互联⽹所有的事物都是资源,要求URL中只有表示资源的名称,没有动词。 RESTful⻛格资源操作:使⽤HTTP请求中的method⽅法put、delete、post、get来操作资源。分别对 应添加、删除、修改、查询。不过⼀般使⽤时还是 post 和 get。put 和 delete⼏乎不使⽤。 RESTful ⻛格资源表述:可以根据需求对URL定位的资源返回不同的表述(也就是返回数据类型,⽐如 XML、JSON等数据格式)。 Spring MVC ⽀持 RESTful ⻛格请求,具体讲的就是使⽤ @PathVariable 注解获取 RESTful ⻛格的请求 URL中的路径变量。

    因为HTML中 method属性只能配置为GET POST , 所以需要提供一个 name=“_method" 的参数进行配置PUT DELETE。并在web.xml中配置请求⽅式过滤器(将特定的post请求转换为put和delete请求)

    <!--配置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>
    
        /*
         * restful  put  /demo/handle/15/lisi
         */
        @RequestMapping(value = "/handle/{id}/{name}",method = {RequestMethod.PUT})
        public ModelAndView handlePut(@PathVariable("id") Integer id,@PathVariable("name") String username) {
    
            Date date = new Date();
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("date",date);
            modelAndView.setViewName("success");
            return modelAndView;
        }
    
    
        /*
         * restful  delete  /demo/handle/15
         */
        @RequestMapping(value = "/handle/{id}",method = {RequestMethod.DELETE})
        public ModelAndView handleDelete(@PathVariable("id") Integer id) {
    
            Date date = new Date();
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("date",date);
            modelAndView.setViewName("success");
            return modelAndView;
        }
    
    

Ajax Json 交互

Json是⼀种与语⾔⽆关的数据交互格式,就是⼀种字符串,只是⽤特殊符号{}内表示对象、[]内表示数 组、""内是属性或值、:表示后者是前者的值

交互:两个⽅向 1)前端到后台:前端ajax发送json格式字符串,后台直接接收为pojo参数,使⽤注解@RequstBody 2)后台到前端:后台直接返回pojo对象,前端直接接收为json对象或者字符串,使⽤注解 @ResponseBody

  • @RequestBody

    必须为POST,前端ajax 发送的json字符串存在requestBody中

  • @ResposeBody

    @responseBody注解的作⽤是将controller的⽅法返回的对象通过适当的转换器转换为指定的格式之 后,写⼊到response对象的body区,通常⽤来返回JSON数据或者是XML数据。 注意:在使⽤此注解之 后不会再⾛视图处理器,⽽是直接将数据写⼊到输⼊流中,他的效果等同于通过response对象输出指定 格式的数据

SpringMVC高级技术

拦截器(Interceptor)的使用

Javaweb 三大组件为 Servlet、Filter、Listener。Interceptor是SpringMVC、Struts等表现出框架自己实现的。

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

    • Servlet:处理Request请求和Response响应

    • 过滤器(Filter):对Request请求起到过滤的作⽤,作⽤在Servlet之前,如果配置为/*可以对所 有的资源访问(servlet、js/css静态资源等)进⾏过滤处理。如对post请求设置编码过滤器

    • 监听器(Listener):实现了javax.servlet.ServletContextListener 接⼝的服务器端组件,它随 Web应⽤的启动⽽启动,只初始化⼀次,然后会⼀直运⾏监视,随Web应⽤的停止而销毁。

      作用⼀:做⼀些初始化⼯作,web应⽤中spring容器启动ContextLoaderListener 作用二:监听web中的特定事件,⽐如HttpSession,ServletRequest的创建和销毁;变量的创建、 销毁和修改等。可以在某些动作前后增加处理,实现监控,比如利用 HttpSessionLisener等统计在线⼈数。

  • 单个拦截器执行流程

    首先我们看下源码:

    	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    	        .....
    				// Determine handler adapter for the current request.
    				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
    			.....
                    //preHandle
    
    				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    					return;
    				}
    
    				// Actually invoke the handler.
    				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    				if (asyncManager.isConcurrentHandlingStarted()) {
    					return;
    				}
    
    				applyDefaultViewName(processedRequest, mv);
          			  //PostHandle
    				mappedHandler.applyPostHandle(processedRequest, response, mv);
    			}
    			catch (Exception ex) {
    				dispatchException = ex;
    			}
    		........
                // 该方法中调用triggerAfterCompletion
                	processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    	
    	}
    

    在运行程序时,拦截器的执⾏是有⼀定顺序的,该顺序与配置⽂件中所定义的拦截器的顺序相关。 单个 拦截器,在程序中的执⾏流程如下图所示:

1)程序先执⾏preHandle()⽅法,如果该⽅法的返回值为true,则程序会继续向下执⾏处理器中的⽅ 法,否则将不再向下执⾏。 2)在业务处理器(即控制器Controller类)处理完请求后,会执⾏postHandle()⽅法,然后会通过 DispatcherServlet向客户端返回响应。 3)在DispatcherServlet处理完请求后,才会执⾏afterCompletion()⽅法

  • 多个拦截器执行流程

    多个拦截器(假设有两个拦截器Interceptor1和Interceptor2,并且在配置⽂件中, Interceptor1拦截 器配置在前),在程序中的执⾏流程如下图所示:

从图可以看出,当有多个拦截器同时⼯作时,它们的preHandle()⽅法会按照配置⽂件中拦截器的配置 顺序执⾏,⽽它们的postHandle()⽅法和afterCompletion()⽅法则会按照配置顺序的反序执⾏。

自定义SpringMVC拦截器

/**
* ⾃定义springmvc拦截器
*/
public class MyIntercepter01 implements HandlerInterceptor {
	/**会在handler⽅法业务逻辑执⾏之前执⾏
	*/
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse
    response, Object handler) throws Exception {
     System.out.println("MyIntercepter01 preHandle......");
     return true;
     }
     /**
     * 会在handler⽅法业务逻辑执⾏之后尚未跳转⻚⾯时执⾏
     * @param request
     * @param response
     * @param handler
     * @param modelAndView 封装了视图和数据,此时尚未跳转⻚⾯呢,你可以在这⾥针对返回的
    数据和视图信息进⾏修改
     * @throws Exception
     */
     @Override
     public void postHandle(HttpServletRequest request, HttpServletResponse
    response, Object handler, ModelAndView modelAndView) throws Exception {
     System.out.println("MyIntercepter01 postHandle......");
     }
     /**
     * ⻚⾯已经跳转渲染完毕之后执⾏
     * @param request
     * @param response
     * @param handler
     * @param ex 可以在这⾥捕获异常
     * @throws Exception
     */
     @Override
     public void afterCompletion(HttpServletRequest request,
    HttpServletResponse response, Object handler, Exception ex) throws Exception {
     System.out.println("MyIntercepter01 afterCompletion......");
 }
}

处理multipart形式的数据(文件上传)

原生servlet可以处理上传文件数据,springMVC又对其进行了封装

  • multipartResolver

    配置multipartResolver,id必须为固定的multipartResolver。(默认设置)

    所需jar包

    <!--⽂件上传所需jar坐标-->
    <dependency>
     <groupId>commons-fileupload</groupId>
     <artifactId>commons-fileupload</artifactId>
     <version>1.3.1</version>
    </dependency>
    

    前端Form

    <%--
     1 method="post"
     2 enctype="multipart/form-data"
     3 type="file"
    --%>
    <form method="post" enctype="multipart/form-data" action="/demo/upload">
     <input type="file" name="uploadFile"/>
     <input type="submit" value="上传"/>
    </form>
    

    后台接收Handler

    @RequestMapping("upload")
    public String upload(MultipartFile uploadFile, HttpServletRequest request)
    throws IOException {
     // ⽂件原名,如xxx.jpg
     String originalFilename = uploadFile.getOriginalFilename();
     // 获取⽂件的扩展名,如jpg
     String extendName =
    originalFilename.substring(originalFilename.lastIndexOf(".") + 1,
    originalFilename.length());
     String uuid = UUID.randomUUID().toString();
     // 新的⽂件名字
     String newName = uuid + "." + extendName;
     String realPath =
    request.getSession().getServletContext().getRealPath("/uploads");
     // 解决⽂件夹存放⽂件数量限制,按⽇期存放
     String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
     File floder = new File(realPath + "/" + datePath);
     if(!floder.exists()) {
     floder.mkdirs();
     }
     uploadFile.transferTo(new File(floder,newName));
     return "success";
    }
    

在控制器中处理异常

  • @ControllerAdvice

  • @ExcecptionHandler

    // 可以让我们优雅的捕获所有Controller对象handler方法抛出的异常
    @ControllerAdvice
    public class GlobalExceptionResolver {
    
    
        @ExceptionHandler(Exception.class)
        public void handleException(Exception exception, HttpServletResponse response) {
    		//异常处理逻辑
            System.out.println(exception.getMessage());
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("msg",exception.getMessage());
            modelAndView.setViewName("error");
            return modelAndView;
        }
    }
    

    单独写在Controller中则只对当前Controller生效

    @Controller
    public class Controller01 {
    
    
        @ExceptionHandler(Exception.class)
        public void handleException(Exception exception, HttpServletResponse response) {
    		//异常处理逻辑
            System.out.println(exception.getMessage());
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("msg",exception.getMessage());
            modelAndView.setViewName("error");
            return modelAndView;
        }
    }
    

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

重定向时请求参数会丢失,我们往往需要重新携带请求参数,我们可以进⾏⼿动参数拼接如下:

return "redirect:handle01?name=" + name;

但是上述拼接参数的⽅法属于get请求,携带参数⻓度有限制,参数安全性也不⾼,此时,我们可以使 ⽤SpringMVC提供的flash属性机制,向上下⽂中添加flash属性,框架会在session中记录该属性值,当 跳转到⻚⾯之后框架会⾃动删除flash属性,不需要我们⼿动删除,通过这种⽅式进⾏重定向参数传递, 参数⻓度和安全性都得到了保障,如下:

@RequestMapping("/handleRedirect")
     public String handleRedirect(String name,RedirectAttributes
    redirectAttributes) {
     //return "redirect:handle01?name=" + name; // 拼接参数安全性、参数⻓度都有
    局限
     // addFlashAttribute⽅法设置了⼀个flash类型属性,该属性会被暂存到session中,在
    跳转到⻚⾯之后该属性销毁
     redirectAttributes.addFlashAttribute("name",name);
     return "redirect:handle01";
 }

SpringMVC源码剖析

DispatcherServlet继承结构

处理请求大致流程

  • doDispatch

    DispacherServlet#doDispathchs是处理请求流程的核心方法。

    SpringMVC处理请求的流程即为:

    org.springframework.web.servlet.DispatcherServlet#doDispatch⽅法的执⾏过程,其中步骤 2、3、4、5是核心步骤 1)调⽤getHandler()获取到能够处理当前请求的执⾏链 HandlerExecutionChain(Handler+拦截 器) 2)调⽤getHandlerAdapter();获取能够执⾏1)中Handler的适配器 3)适配器调⽤Handler执⾏ha.handle(总会返回⼀个ModelAndView对象) 4)调⽤processDispatchResult()⽅法完成视图渲染跳转

    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) {
    			//最终会调⽤HandlerInterceptor的afterCompletion ⽅法
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest,
            response);
             }
             }
             else {
             // Clean up any resources used by a multipart request.
             if (multipartRequestParsed) {
             cleanupMultipart(processedRequest);
            }
           }
         }
     }
    

getHandler方法

org.springframework.web.servlet.DispatcherServlet#getHandler

	/**
	 * Return the HandlerExecutionChain for this request.
	 * <p>Tries all handler mappings in order.
	 * @param request current HTTP request
	 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
	 */
	@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
                //遍历所有HandlerMapping
                //BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

继续调用org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerExecutionChain

	/**
	 * Build a {@link HandlerExecutionChain} for the given handler, including
	 * applicable interceptors.
	 * <p>The default implementation builds a standard {@link HandlerExecutionChain}
	 * with the given handler, the handler mapping's common interceptors, and any
	 * {@link MappedInterceptor MappedInterceptors} matching to the current request URL. Interceptors
	 * are added in the order they were registered. Subclasses may override this
	 * in order to extend/rearrange the list of interceptors.
	 * <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a
	 * pre-built {@link HandlerExecutionChain}. This method should handle those
	 * two cases explicitly, either building a new {@link HandlerExecutionChain}
	 * or extending the existing chain.
	 * <p>For simply adding an interceptor in a custom subclass, consider calling
	 * {@code super.getHandlerExecutionChain(handler, request)} and invoking
	 * {@link HandlerExecutionChain#addInterceptor} on the returned chain object.
	 * @param handler the resolved handler instance (never {@code null})
	 * @param request current HTTP request
	 * @return the HandlerExecutionChain (never {@code null})
	 * @see #getAdaptedInterceptors()
	 */
	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			}
			else {
				chain.addInterceptor(interceptor);
			}
		}
		return chain;
	}

org.springframework.web.servlet.HandlerExecutionChain

/**
 * Handler execution chain, consisting of handler object and any handler interceptors.
 * Returned by HandlerMapping's {@link HandlerMapping#getHandler} method.
 *
 * @author Juergen Hoeller
 * @since 20.06.2003
 * @see HandlerInterceptor
 */
public class HandlerExecutionChain {

	private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);

	private final Object handler;

	@Nullable
	private HandlerInterceptor[] interceptors;

	@Nullable
	private List<HandlerInterceptor> interceptorList;

	private int interceptorIndex = -1;

...

getHandlerAdapter

	/**
	 * Return the HandlerAdapter for this handler object.
	 * @param handler the handler object to find an adapter for
	 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
	 */
	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
            //遍历handlerAdapter
			for (HandlerAdapter adapter : this.handlerAdapters) {
                //supports方法通过类型判断
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#supports

	/**
	 * This implementation expects the handler to be an {@link HandlerMethod}.
	 * @param handler the handler instance to check
	 * @return whether or not this adapter can adapt the given handler
	 */
	@Override
	public final boolean supports(Object handler) {
		return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
	}

handler方法执行细节

1.首先进入handle方法

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

2.RequestMappingHandlerAdapter#handleInternal

	@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...
            //如果不需要对session进行同步处理,贼调用该方法
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}

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

		return mav;
	}

3.RequestMappingHandlerAdapter#invokeHandlerMethod

	/**
	 * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
	 * if view resolution is required.
	 * @since 4.2
	 * @see #createInvocableHandlerMethod(HandlerMethod)
	 */
	@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
            //进行一些调用方法前的数据绑定,返回值handlers
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			if (this.argumentResolvers != null) {
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
			invocableMethod.setDataBinderFactory(binderFactory);
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
			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);
			}
			//调用handler方法
			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}
			//通过mavContainer modelFactory webRequest 得到一个mv返回
			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}

4.org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle

	/**
	 * Invoke the method and handle the return value through one of the
	 * configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
	 * @param webRequest the current request
	 * @param mavContainer the ModelAndViewContainer for this request
	 * @param providedArgs "given" arguments matched by type (not resolved)
	 */
	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		//进行调用
		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;
		}
	}

5.org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest

	/**
	 * Invoke the method after resolving its argument values in the context of the given request.
	 * <p>Argument values are commonly resolved through
	 * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
	 * The {@code providedArgs} parameter however may supply argument values to be used directly,
	 * i.e. without argument resolution. Examples of provided argument values include a
	 * {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.
	 * Provided argument values are checked before argument resolvers.
	 * <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
	 * resolved arguments.
	 * @param request the current request
	 * @param mavContainer the ModelAndViewContainer for this request
	 * @param providedArgs "given" arguments matched by type, not resolved
	 * @return the raw value returned by the invoked method
	 * @throws Exception raised if no suitable argument resolver can be found,
	 * or if the method raised an exception
	 * @see #getMethodArgumentValues
	 * @see #doInvoke
	 */
	@Nullable
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
		return doInvoke(args);
	}

6.org.springframework.web.method.support.InvocableHandlerMethod#doInvoke通过反射真正进行调用

	/**
	 * Invoke the handler method with the given argument values.
	 */
	@Nullable
	protected Object doInvoke(Object... args) throws Exception {
		ReflectionUtils.makeAccessible(getBridgedMethod());
		try {
            //反射调用
			return getBridgedMethod().invoke(getBean(), args);
		}
		catch (IllegalArgumentException ex) {
			assertTargetBean(getBridgedMethod(), getBean(), args);
			String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
			throw new IllegalStateException(formatInvokeError(text, args), ex);
		}
		catch (InvocationTargetException ex) {
			// Unwrap for HandlerExceptionResolvers ...
			Throwable targetException = ex.getTargetException();
			if (targetException instanceof RuntimeException) {
				throw (RuntimeException) targetException;
			}
			else if (targetException instanceof Error) {
				throw (Error) targetException;
			}
			else if (targetException instanceof Exception) {
				throw (Exception) targetException;
			}
			else {
				throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
			}
		}
	}

}

跳转页面,渲染视图

1.调用org.springframework.web.servlet.DispatcherServlet#processDispatchResult方法

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

2.先判断是否发生异常需要生成异常ModelAndView,然后对MV进行渲染

	/**
	 * Handle the result of handler selection and handler invocation, which is
	 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
	 */
	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

		boolean errorView = false;

		if (exception != null) {
			if (exception instanceof ModelAndViewDefiningException) {
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}

		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
            //渲染方法
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		else {
			if (logger.isTraceEnabled()) {
				logger.trace("No view rendering, null ModelAndView returned.");
			}
		}

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}

		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

3.org.springframework.web.servlet.DispatcherServlet#render

	/**
	 * Render the given ModelAndView.
	 * <p>This is the last stage in handling a request. It may involve resolving the view by name.
	 * @param mv the ModelAndView to render
	 * @param request current HTTP servlet request
	 * @param response current HTTP servlet response
	 * @throws ServletException if view is missing or cannot be resolved
	 * @throws Exception if there's a problem rendering the view
	 */
	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实现。并将逻辑视图名解析成物理视图名)
			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(mv.getModelInternal(), request, response);
		}
		catch (Exception ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Error rendering view [" + view + "]", ex);
			}
			throw ex;
		}
	}

4.org.springframework.web.servlet.view.AbstractView#render

	/**
	 * Prepares the view given the specified model, merging it with static
	 * attributes and a RequestContext attribute, if necessary.
	 * Delegates to renderMergedOutputModel for the actual rendering.
	 * @see #renderMergedOutputModel
	 */
	@Override
	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);
	}

5.org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel

	/**
	 * Render the internal resource given the specified model.
	 * This includes setting the model as request attributes.
	 */
	@Override
	protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		// Expose the model object as request attributes.
		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);
		}
	}

6.org.springframework.web.servlet.view.AbstractView#exposeModelAsRequestAttributes将Model对象的数据数据设置到request域中,这样就能在jsp页面获取到数据

	/**
	 * Expose the model objects in the given map as request attributes.
	 * Names will be taken from the model Map.
	 * This method is suitable for all resources reachable by {@link javax.servlet.RequestDispatcher}.
	 * @param model a Map of model objects to expose
	 * @param request current HTTP request
	 */
	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);
			}
		});
	}

九大组件初始化

  • onRefresh()

    SpirngMVC在org.springframework.web.servlet.DispatcherServlet#onRefresh中进行九大组件的初始化。

    	/**
    	 * This implementation calls {@link #initStrategies}.
    	 */
    	@Override
    	protected void onRefresh(ApplicationContext context) {
    		initStrategies(context);
    	}
    
    	/**
    	 * Initialize the strategy objects that this servlet uses.
    	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
    	 */
    	protected void initStrategies(ApplicationContext context) {
    		initMultipartResolver(context);
    		initLocaleResolver(context);
    		initThemeResolver(context);
    		initHandlerMappings(context);
    		initHandlerAdapters(context);
    		initHandlerExceptionResolvers(context);
    		initRequestToViewNameTranslator(context);
    		initViewResolvers(context);
    		initFlashMapManager(context);
    	}
    

    通过观察方法调用栈

    图片

    我们发现onfresh方法是在org.springframework.context.support.AbstractApplicationContext#refresh中调用的。这个方法就是在Spring框架中执行容器初始化的主要方法。在finishRefresh()方法中进行事件的发布。从而执行onRefresh方法。

    九大组件初始化流程基本一致,我们以initHandlerMappings为例子

    	/**
    	 * Initialize the HandlerMappings used by this class.
    	 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
    	 * we default to BeanNameUrlHandlerMapping.
    	 */
    	//如果在BeanFactory中存在就使用,没有就使用默认的
    	private void initHandlerMappings(ApplicationContext context) {
    		this.handlerMappings = null;
    
    		if (this.detectAllHandlerMappings) {
    			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
    			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 {
                //如果不需要探测所有的HandlerMapping,则通过固定ID()去寻找
                //public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
    			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) {
                //通过DispatcherServlet.properties寻找 default HandlerMapping
    			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    			if (logger.isTraceEnabled()) {
    				logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
    						"': using default strategies from DispatcherServlet.properties");
    			}
    		}
    	}
    

    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
    

SSM整合

SSM = Spring + SpringMVC + Mybatis = (Spring + Mybatis)+ SpringMVC 先整合 Spring + Mybatis 然后再整合 SpringMVC

Mybatis整合Spring

  • 整合⽬标 数据库连接池以及事务管理都交给Spring容器来完成 SqlSessionFactory对象应该放到Spring容器中作为单例对象管理 Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对象

  • 整合所需 Jar 分析 Junit测试jar(4.12版本) Mybatis的jar(3.4.5) Spring相关jar(spring-context、spring-test、spring-jdbc、spring-tx、spring-aop、 aspectjweaver) Mybatis/Spring整合包jar(mybatis-spring-xx.jar) Mysql数据库驱动jar Druid数据库连接池的jar

  • 整合后的 Pom 坐标

        <!--junit-->
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
          <scope>test</scope>
        </dependency>
        <!--mybatis-->
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis</artifactId>
          <version>3.4.5</version>
        </dependency>
        <!--spring相关-->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>5.1.12.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-test</artifactId>
          <version>5.1.12.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>5.1.12.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-tx</artifactId>
          <version>5.1.12.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aop</artifactId>
          <version>5.1.12.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.8.9</version>
        </dependency>
        <!--mybatis与spring的整合包-->
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis-spring</artifactId>
          <version>2.0.3</version>
        </dependency>
        <!--数据库驱动jar-->
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.46</version>
        </dependency>
        <!--druid连接池-->
        <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.1.21</version>
        </dependency>
    
  • Spring 配置⽂件

    applicationContext-dao.xml

    <?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.lagou.edu.mapper"/>
    
    
        <!--数据库连接池以及事务管理都交给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.lagou.edu.pojo"/>
            <!-
    
    
            <!--Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对象-->
            <!--扫描mapper接口,生成代理对象,生成的代理对象会存储在ioc容器中-->
            <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
                <!--mapper接口包路径配置-->
                <property name="basePackage" value="com.lagou.edu.mapper"/>
                <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
            </bean>-数据源dataSource-->
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
    
    </beans>
    

    applicationContext-service.xml

    <?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.lagou.edu.service"/>
    
    
    
        <!--事务管理-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
        <!--事务管理注解驱动-->
        <tx:annotation-driven transaction-manager="transactionManager"/>
    
    
    
    </beans>
    

整合SpringMVC

  • 引入pom坐标

    <!--SpringMVC-->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>5.1.12.RELEASE</version>
        </dependency>
        <!--jsp-api&servlet-api-->
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>jsp-api</artifactId>
          <version>2.0</version>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>3.1.0</version>
          <scope>provided</scope>
        </dependency>
        <!--页面使用jstl表达式-->
        <dependency>
          <groupId>jstl</groupId>
          <artifactId>jstl</artifactId>
          <version>1.2</version>
        </dependency>
        <dependency>
          <groupId>taglibs</groupId>
          <artifactId>standard</artifactId>
          <version>1.1.2</version>
        </dependency>
    
        <!--json数据交互所需jar,start-->
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-core</artifactId>
          <version>2.9.0</version>
        </dependency>
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.9.0</version>
        </dependency>
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-annotations</artifactId>
          <version>2.9.0</version>
        </dependency>
        <!--json数据交互所需jar,end-->
    
  • 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>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    
    
      <!--springmvc启动-->
      <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <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>