Spring MVC 应用

1. Spring MVC 简介

1.1 MVC体系结构
三层架构

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

三层架构中,每⼀层各司其职,接下来我们就说说每层都负责哪些⽅⾯:

  • 表现层 : 也就是我们常说的web 层。它负责接收客户端请求,向客户端响应结果,通常客户端使⽤http协议请求web 层,web需要接收 http请求,完成http 响应。

表现层包括展示层和控制层:控制层负责接收请求,展示层负责结果的展示。

表现层依赖业务层,接收到客户端请求⼀般会调⽤业务层进⾏业务处理,并将处理结果响应给客户端。

表现层的设计⼀般都使⽤ MVC 模型。(MVC 是表现层的设计模型,和其他层没有关系)

  • 业务层 : 也就是我们常说的 service 层。它负责业务逻辑处理,和我们开发项⽬的需求息息相关。web 层依赖业务层,但是业务层不依赖 web 层。

业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务⼀致性。(也就是我们说的, 事务应该放到业务层来控制)

  • 持久层 : 也就是我们是常说的 dao 层。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进⾏持久化的载体,数据访问层是业务层和持久层交互的接⼝,业务层需要通过数据访问层将数据持久化到数据库中。通俗的讲,持久层就是和数据库交互,对数据库表进⾏增删改查的。
MVC设计模式

MVC 全名是 Model View Controller,是 模型(model)-视图(view)-控制器(controller) 的缩写, 是⼀种⽤于设计创建 Web 应⽤程序表现层的模式。 MVC 中每个部分各司其职:

  • Model(模型) :模型包含业务模型和数据模型,数据模型⽤于封装数据,业务模型⽤于处理业务。
  • View(视图) : 通常指的就是我们的 jsp 或者 html。作⽤⼀般就是展示数据的。通常视图是依据模型数据创建的。
  • Controller(控制器) : 是应⽤程序中处理⽤户交互的部分。作⽤⼀般就是处理程序逻辑的。

MVC提倡 :每⼀层只编写⾃⼰的东⻄,不编写任何其他的代码;分层是为了解耦,解耦是为了维护⽅便和分⼯协作

1.2 Spring MVC是什么

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

SpringMVC 已经成为⽬前最主流的 MVC 框架之⼀,并且 随着 Spring3.0的发布,全⾯超越 Struts2,成为最优秀的 MVC 框架。 SpringMVC中要让⼀个java类能够处理请求只需要添加注解就ok

它通过⼀套注解,让⼀个简单的 Java 类成为处理请求的控制器,⽽⽆须实现任何接⼝。同时它还⽀持RESTful 编程⻛格的请求。

总之:Spring MVC和Struts2⼀样,都是 为了解决表现层问题 的web框架,它们都是基于 MVC 设计模式的。⽽这些表现层框架的主要职责就是处理前端HTTP请求。

Spring MVC 本质可以认为是对servlet的封装,简化了我们serlvet的开发 作⽤:1. 接收请求 2. 返回响应,跳转⻚⾯

2. Spring MVC 工作流程

2.1 开发过程

1)web.xml中配置DispatcherServlet前端控制器
<!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>


  <servlet>
    <servlet-name>mvc</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>
  </servlet>
  <servlet-mapping>
    <servlet-name>mvc</servlet-name>
    <!--  拦截方式
      方式一:带后缀,比如*.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

      方式三:/* 拦截所有,包括.jsp
    -->
    <!--拦截匹配规则的url请求,进入springmvc框架处理-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>
2)开发处理具体业务逻辑的Handler(@Controller、@RequestMapping)
@Controller
@RequestMapping("/demo")
public class DemoController {
    @Autowired
    private DemoService demoService;

    @RequestMapping("/handler01")
    public ModelAndView handler01(String name) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("name", demoService.getName(name));
        modelAndView.setViewName("/WEB-INF/jsp/success.jsp");
        return modelAndView;
    }
}
3)xml配置⽂件配置controller扫描,配置springmvc三⼤件

springmvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd
">

    <!--开启包扫描-->
    <context:component-scan base-package="com.example"></context:component-scan>

    <!--
        自动注册最合适的处理器映射器,处理器适配器(调用handler方法)
    -->
    <mvc:annotation-driven />
    
    <!--配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
    
</beans>
4)将xml⽂件路径告诉springmvc(DispatcherServlet)
<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>
  </servlet>
2.2 Springmvc请求处理流程

流程说明

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

2.3 Spring MVC 九⼤组件
  • 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<FileName,File>这样的结构,MultipartResolver 的作⽤就是封装普通的请求,使其拥有⽂件上传的功能。

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

3. Spring MVC 参数绑定

3.1 原生servlet-api的支持,默认⽀持 Servlet API 作为⽅法参数
3.1.1 pom文件添加servlet-api依赖
<dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
3.1.2 添加handler()处理
	/**
     * SpringMVC 对原⽣servlet api的⽀持  url:/demo/handler02?id=1
     * 如果要在SpringMVC中使⽤servlet原⽣对象,⽐如
     * HttpServletRequest\HttpServletResponse\HttpSession,直接在Handler⽅法形参中声明使⽤即可
     * 
     */
    @RequestMapping("/handler02")
    public ModelAndView handler02(HttpServletRequest request) {
        String id = request.getParameter("id");
        System.out.println(id);
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("name", new Date());
        modelAndView.setViewName("success");
        return modelAndView;
    }
3.1.3 测试页面 index.jsp
<%@ page isELIgnored="false" contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>SpringMVC 测试页</title>


<%--    <script type="text/javascript" src="/js/jquery.min.js"></script>--%>

    <script>
        $(function () {

            $("#ajaxBtn").bind("click",function () {
                // 发送ajax请求
                $.ajax({
                    url: '/demo/handler07',
                    type: 'POST',
                    data: '{"id":"1","name":"李四"}',
                    contentType: 'application/json;charset=utf-8',
                    dataType: 'json',
                    success: function (data) {
                        alert(data.name);
                    }
                })

            })


        })
    </script>


    <style>
        div{
            padding:10px 10px 0 10px;
        }
    </style>
</head>
<body>
<div>
    <h2>Spring MVC 请求参数绑定</h2>
    <fieldset>
        <p>测试用例:SpringMVC 对原生servlet api的支持</p>
        <a href="/demo/handler02?id=1">点击测试</a>
    </fieldset>
    <fieldset>
        <p>测试用例:SpringMVC 接收简单数据类型参数</p>
        <a href="/demo/handler03?id=1">点击测试</a>
    </fieldset>
    <fieldset>
        <p>测试用例:SpringMVC 使用@RequestParam 接收简单数据类型参数(形参名和参数名不一致)</p>
    </fieldset>

    <fieldset>
        <p>测试用例:SpringMVC接收pojo类型参数</p>
        <a href="/demo/handler04?id=1&name=zhangsan">点击测试</a>
    </fieldset>

    <fieldset>
        <p>测试用例:SpringMVC接收pojo包装类型参数</p>
        <a href="/demo/handler05?user.id=1&user.name=zhangsan">点击测试</a>
    </fieldset>

    <fieldset>
        <p>测试用例:SpringMVC接收日期类型参数</p>
        <a href="/demo/handler06?birthday=2019-10-08">点击测试</a>
    </fieldset>
</div>


<div>
    <h2>SpringMVC对Restful风格url的支持</h2>
    <fieldset>
        <p>测试用例:SpringMVC对Restful风格url的支持</p>

        <a href="/demo/handler/15">rest_get测试</a>


        <form method="post" action="/demo/handler">
            <input type="text" name="username"/>
            <input type="submit" value="提交rest_post请求"/>
        </form>


        <form method="post" action="/demo/handler/15/lisi">
            <input type="hidden" name="_method" value="put"/>
            <input type="submit" value="提交rest_put请求"/>
        </form>


        <form method="post" action="/demo/handler/15">
            <input type="hidden" name="_method" value="delete"/>
            <input type="submit" value="提交rest_delete请求"/>
        </form>
    </fieldset>
</div>



<div>
    <h2>Ajax json交互</h2>
    <fieldset>
        <input type="button" id="ajaxBtn" value="ajax提交"/>
    </fieldset>
</div>


<div>
    <h2>multipart 文件上传</h2>
    <fieldset>
        <%--
            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>
    </fieldset>
</div>



</body>
</html>

3.1.4 测试代码

3.2 简单数据类型

简单数据类型:1. ⼋种基本数据类型及其包装类型(参数类型推荐使⽤包装数据类型,因为基础数据类型不可以为null) 2. String

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

3.2.1 参数名称一致

3.2.2 参数名称不一致

/demo/handler03?ids=1

3.3 pojo类型

http://localhost:8088/demo/handler04?id=1&name=zhangsan

3.4 包装pojo类型

http://localhost:8088/demo/handler05?user.id=1&user.name=zhangsan

QueryVo

public class QueryVo {

    private Integer id;

    private User user;

    public QueryVo() {
    }

    public QueryVo(Integer id, User user) {
        this.id = id;
        this.user = user;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}
3.5 日期类型

http://localhost:8088/demo/handler06?birthday=2021-04-20

一般日期的格式太多,比如yyyy-MM-dd 、 yyyy.MM.dd 、 yyyy/MM/dd springmvc不知道该以何种方式解析日期,所以提供了Converter接口,可以自定义日期类型转换器

3.5.1 ⾃定义类型转换器
public class DateConverter implements Converter<String, Date> {
    @Override
    public Date convert(String dateStr) {
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date parse = sf.parse(dateStr);
            return parse;
        } catch (ParseException e) {
            sf = new SimpleDateFormat("yyyy/MM/dd");
            Date parse = null;
            try {
                parse = sf.parse(dateStr);
            } catch (ParseException ex) {
                ex.printStackTrace();
            }
            return parse;
        }
    }
}
3.5.2 注册⾃定义类型转换器
<!--
        自动注册最合适的处理器映射器,处理器适配器(调用handler方法)
    -->
    <mvc:annotation-driven conversion-service="conversionServiceBean" />
    
    <!--
        自定义日期转换器
        id="conversionServiceBean"   这个id是固定的
        FormattingConversionServiceFactoryBean中有一个converters集合,存放所有的转换器
    -->
    <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.example.converter.DateConverter"></bean>
            </set>
        </property>
    </bean>
3.5.3 测试日期转换

4. 请求⽅式过滤器 (将特定的post请求转换为put和delete请求)

4.1 form表单添加隐藏域
<form method="post" action="/demo/handler/15/lisi">
            <input type="hidden" name="_method" value="put"/>
            <input type="submit" value="提交rest_put请求"/>
        </form>
4.2 定义handler处理
@RequestMapping(value = "/handler/{id}/{name}", method = RequestMethod.PUT)
    public ModelAndView handler06(@PathVariable Integer id, @PathVariable String name) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("name", new Date());
        modelAndView.setViewName("success");
        return modelAndView;
    }
4.3 web.xml中配置请求⽅式过滤器 将特定的post请求转换为put或者delete请求
<!--配置springmvc请求方式转换过滤器,会检查请求参数中是否有_method参数,如果有就按照指定的请求方式进行转换-->
  <filter>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
4.4 测试代码

5. post请求编码过滤器

<!--springmvc提供的针对post请求的编码过滤器-->
  <filter>
    <filter-name>encoding</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>
<filter-mapping>
    <filter-name>encoding</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

6. 静态资源配置

<!--静态资源配置,方案一-->
    <!--
        原理:添加该标签配置之后,会在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/**"/>

7. 拦截器(Inteceptor)使⽤

7.1 监听器、过滤器和拦截器对⽐
  • Servlet:处理Request请求和Response响应

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

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

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

  • 拦截器(Interceptor):是SpringMVC、Struts等表现层框架⾃⼰的,不会拦截jsp/html/css/image的访问等,只会拦截访问的控制器⽅法(Handler)。

    从配置的⻆度也能够总结发现:serlvet、filter、listener是配置在web.xml中的,⽽interceptor是配置在表现层框架⾃⼰的配置⽂件中的

    • 在Handler业务逻辑执⾏之前拦截⼀次
    • 在Handler逻辑执⾏完毕但未跳转⻚⾯之前拦截⼀次
    • 在跳转⻚⾯之后拦截⼀次

7.2 拦截器的执行流程

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

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

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

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

代码示例

定义interceptor 两个interceptor

public class MyInterceptor1 implements HandlerInterceptor {


    /**
     * 执行handler之前执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor1中的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("MyInterceptor1中的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("MyInterceptor1中的afterCompletion.....");
    }
}

注册拦截器

<!--拦截所有handler-->
    <!--<bean class="com.example.interceptor.MyInterceptor1"/>-->

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

执行handler 查看日志

8. 处理multipart形式的数据

8.1 所需要的依赖
<dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>
8.2 jsp页面
<div>
    <h2>multipart 文件上传</h2>
    <fieldset>
        <%--
            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>
    </fieldset>
</div>
8.3 handler
@RequestMapping("/upload")
    public String upload(MultipartRequest multipartRequest, MultipartFile uploadFile, HttpServletRequest request) {
        // 获取源文件名称
        MultipartFile requestFile = multipartRequest.getFile("uploadFile");
        String originalFilename = uploadFile.getOriginalFilename();
        // 获取⽂件的扩展名,如jpg
        String extendName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1, originalFilename.length());
        // 生成新的文件名
        String uuid = UUID.randomUUID().toString();
        String newFileName = uuid + "." + extendName;
        // 获取路径
        String basePath = request.getSession().getServletContext().getRealPath("/uploads");

        // 按照日期创建文件夹
        String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        File file = new File(basePath + "/" + datePath);
        if (!file.exists()) {
            file.mkdirs();
        }
        try {
            uploadFile.transferTo(new File(file, newFileName));
        } catch (IOException e) {
            e.printStackTrace();
        }

        return "success";
    }
8.4 springmvc.xml中配置文件解析器
<!--配置文件解析器   id="multipartResolver"为固定值-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--设置允许上传的文件的最大大小-->
        <property name="maxUploadSize" value="10000000"></property>
    </bean>
8.5 查看测试结果

9. 异常处理器

9.1 异常处理类
/**
 * 全局异常处理器
 * @ControllerAdvice  增强处理器
 * 可以处理:
     全局异常处理
     全局数据绑定
     全局数据预处理
 */
@ControllerAdvice
public class GlobalExceptionResolver {

    @ExceptionHandler(Exception.class)
    public ModelAndView handlerException(Exception exception, HttpServletResponse response) {
        ModelAndView modelAndView = new ModelAndView();
        // 将异常信息添加到response中
        modelAndView.addObject("msg", exception.getMessage());
        // 返回到异常页面
        modelAndView.setViewName("error");
        return modelAndView;
    }
}
9.2 异常测试

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

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

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

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

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