Spring MVC

三、  Spring MVC

1.  Spring MVC 简介

1. Spring Web MVC 框架提供了模型(Model)- 视图(View)- 控制(Controller)的体系结构,可以用来开发灵活、松散耦合的 Web 应用程序的组件
1)Spring MVC 框架是围绕 DispatcherServlet 设计的,这个 Servlet 会把请求分发给各个处理器,并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染等,甚至还能支持文件上传
2)处理器(Handler,也叫 Controller 控制器),是你的应用中注解了 @Controller@RequestMapping 的类和方法
3)Spring 3.0 以后提供了 @Controller 注解机制、@PathVariable 注解以及一些其他的特性,你可以使用它们来进行 RESTful Web 站点和应用的开发

2. Spring 框架与 Spring MVC 框架的区别
1)Spring 容器和 Spring MVC 容器是父子关系
2)Spring 整体框架的核心思想是用容器管理 Bean 的生命周期,根据不同的业务模块来划分不同的容器中注册不同的 Bean
3)Spring MVC 容器主要用来注册 Web 组件特定的 Bean,如控制器、处理器映射、视图解析器等。而 Spring 主要用来注册其他 Bean,这些 Bean 通常是业务层和数据层组件

3. Spring MVC 中的重要组件
1)DispatcherServlet(中央处理器,也叫分发器、前端控制器):用来接收所有的 HTTP 请求,将请求分发到不同的 Handler(处理器,也叫 Controller 控制器)
a. DispatcherServlet 应用的其实就是一个“前端控制器”的设计模式,很多优秀的 Web 框架也都使用了这个设计模式
b. DispatcherServlet 本质上是个 Servlet(它继承自 HttpServlet 基类),同样也需要在 Web 应用的 web.xml 配置文件下声明
c. 你需要在 web.xml 文件中把你希望 DispatcherServlet 处理的请求映射到对应的 URL 上去
2)HandlerMapping(处理器映射器):解析请求格式,找到处理请求的 Handler
3)HandlerAdapter(处理器适配器):调用找到的 Handler
4)ViewResolver(视图解析器):解析视图
5)上面所提到的所有组件,即 HandlerMapping、Controller 和 ViewResolver 是 WebApplicationContext 的一部分,而 WebApplicationContext 是带有一些对 web 应用程序必要的额外特性的 ApplicationContext 的扩展

4. Spring MVC 环境搭建
1)在 Spring jar 包基础上导入额外 jar 包:spring-web.jar、spring-webmvc.jar
2)在 src 下新建 springmvc.xml
3)配置 web.xml

5. 下图为 Spring MVC 运行原理
1)客户端请求提交到 DispatcherServlet
2)由 DispatcherServlet 查询一个或多个 HandlerMapping,找到处理请求的 Controller
3)DispatcherServlet 将请求提交到 Controller
4)Controller 调用业务逻辑处理后,返回 ModelAndView
5)DispatcherServlet 查询一个或多个 ViewResoler 视图解析器,找到 ModelAndView 指定的视图
6)视图负责将结果显示到客户端

<?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:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    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/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        ">
        <!-- 扫描注解 -->
        <context:component-scan base-package="com.ncdx.controller"></context:component-scan>
        <!-- 注解驱动,注册 HandlerMapping 和 HandlerAdapter -->
        <mvc:annotation-driven></mvc:annotation-driven>
        <!-- 设置静态资源 -->
        <mvc:resources location="/js/" mapping="/js/**"></mvc:resources>
        <mvc:resources location="/css/" mapping="/css/**"></mvc:resources>
        <mvc:resources location="/images/" mapping="/images/**"></mvc:resources>
        <!-- 视图解析器 -->
        <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
          <property name="prefix" value="/"></property>
          <property name="suffix" value=".jsp"></property>
        </bean>
        <!-- MultipartResolver 多路上传解析器 -->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
          <!-- 支持的属性,设置支持上传文件的大小的最大值,以字节为单位 -->
          <property name="maxUploadSize" value="1024"></property>
        </bean>
        <!-- 拦截器:拦截所有 controller -->
        <mvc:interceptors>
          <bean class="com.ncdx.interceptor.DemoInterceptor"></bean> // class = 拦截器实现类的全限定路径
        </mvc:interceptors>
        <!-- 拦截器:拦截指定 controller -->
        <mvc:interceptors>
          <mvc:interceptor>
            <mvc:mapping path="/index"/>
            <mvc:mapping path="/error"/>
            <mvc:mapping path="/demo"/>
            <bean class="com.ncdx.interceptor.DemoInterceptor"></bean>
          </mvc:interceptor>
        </mvc:interceptors>
</beans>
springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <!-- 上选文参数 -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
  <!-- 监听器 -->
  <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>
  <!-- 字符编码过滤器 -->
  <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>
</web-app>
web.xml

2.  Spring MVC 控制器的实现

1. @Controller
1)@Controller 注解表明了一个类是作为控制器的角色而存在的
2)@Controller 注解可以认为是被标注类的原型(stereotype),表明了这个类所承担的角色。分派器(DispatcherServlet)会扫描所有注解了 @Controller 的类,检测其中通过 @RequestMapping 注解配置的方法

2. @RequestMapping
1)@RequestMapping 注解将请求的 URL 映射到整个类上或某个特定的处理器方法(HandlerMethod)上
2)类级别的 @RequestMapping 负责将一个特定(或符合某种模式)的请求路径映射到一个控制器上,同时通过方法级别的 @RequestMapping 来细化映射,即根据特定的 HTTP 请求方法("GET""POST"方法等)、HTTP 请求中是否携带特定参数等条件,将请求映射到匹配的处理器方法上
例如(以下的代码示例来自 Petcare):
@Controller
@RequestMapping("/appointments")
public class AppointmentsController {
private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @RequestMapping(method = RequestMethod.GET)
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }

@RequestMapping(method = RequestMethod.POST)
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }

    @RequestMapping(path = "/new", method = RequestMethod.GET)
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }
}
a. @RequestMapping 注解第一次使用点是作用于类级别的,它指示了所有以 "/appointments" 开头的路径都会被映射到控制器下
b. get()方法上的 @RequestMapping 注解对请求路径进行了进一步细化:它仅接受 "GET" 方法的请求。这样,一个请求路径为 "/appointments"、HTTP 方法为 "GET" 的请求,将会最终进入到这个方法被处理。add()方法也做了类似的细化
c. getNewForm()方法则同时注解了能够接受的请求的 HTTP 方法和路径。这种情况下,一个路径为 "appointments/new"、HTTP 方法为 "GET" 的请求将会被这个方法所处理

3)类级别的 @RequestMapping 注解并不是必须的。不配置的话则所有的路径都是绝对路径,而非相对路径
例如:
@Controller
public class MyController {
    @RequestMapping("/login")
    public String login(String name, String password) {
        return "/main.jsp";
    }
}

3. URI 模板:是一个类似于 URI 的字符串,只不过其中包含了一个或多个的变量名,每个变量名被 "{}" 包含,如:@RequestMapping("/users/{userId}/{userName}")
1)在 Spring MVC 中你可以使用@PathVariable 注解的方法参数,将其与 URI 模板中的变量绑定起来
2)为了处理 @PathVariables 注解,Spring MVC 必须通过方法参数名来找到 URI 模板中相对应的变量。你可以在注解中直接声明,如:
在 JSP 中定义一个链接:
<a href="/users/001/张三">跳转</a>
在控制器中定义的处理器方法:
@RequestMapping("/users/{userId}/{userName}")
public String findUser(@PathVariable("userId") String id, @PathVariable("userName") String name, Model model) {
    // @PathVariable("userId") 与 @PathVariable(value="userId")是完全等价的
}
或者,如果 URI 模板中的变量名与方法的参数名是相同的,则你可以不必再指定一次,因为 Spring MVC 会自动匹配 URI 模板中与方法参数名相同的变量名,如:
@RequestMapping("/users/{userId}/{userName}")
public String findUser(@PathVariable String userId, @PathVariable String userName, Model model) {
    // 具体的方法代码…
}
3)不使用 URI 模板
在 JSP 中定义一个链接:
<a href="/users?id=001&name=张三">跳转</a>
在控制器中定义的处理器方法:
@RequestMapping("/users")
public String findUser(String id, String name, Model model) {
// 具体的方法代码…
}

4. 传参:把请求中传递的参数赋值到方法参数中
1)如果方法参数类型是基本数据类型或 String 等简单数据类型:
a. 如果请求参数名和方法参数名相同,Spring MVC 会自动匹配方法参数中与请求传递的参数名相同的参数,如:
表单定义为:
<form action="/formTable">
  ID: <input type="text" name="userId" /><br/>
  <input type="submit" value="提交" /><br/>
</form>
在控制器中定义的处理器方法:
@RequestMapping("/formTable")
public String findUser(String userId) {
    // 具体的方法代码…
}
b. 如果请求参数名和方法参数名不一致,在 Spring MVC 中你可以使用@RequestParame 注解的方法参数,如:
@RequestMapping("/formTable")
public String findUser(@RequestParame(value="userId")String id) {
    // @RequestParame("userId") 与 @RequestParame(value="userId")是完全等价的
}
c. 设置方法参数默认值,防止请求没有传递参数值时出现错误 500 :@RequestParam(defaultValue="")
d. 如果强制要求请求中必须要带有某个参数:@RequestParam(required=true)

2)如果请求传递的参数中包含多个同名参数,如:
在 JSP 定义一个带有复选框的表单:
<form action="/formTable">
篮球 <input type="checkbox" name="favorite"><br/>
足球 <input type="checkbox" name="favorite"><br/>
乒乓球 <input type="checkbox" name="favorite"><br/>
羽毛球 <input type="checkbox" name="favorite"><br/>
  <input type="submit" value="提交" />
</form>
在控制器中定义的处理器方法:
@RequestMapping("/formTable")
public String findUser(@RequestParam("favorite") List<String> list) {
for(String str : list){
System.out.println(str);
}
    return "/index.jsp";
}

3)如果方法参数类型是对象类型,请求传递的参数名要和对象中属性名一致,对象类中要有对应属性的 setter/getter 方法,如:
表单定义为:
<form action="/formTable">
  ID: <input type="text" name="userId" /><br/>
NAME: <input type="text" name="userName" /><br/>
  <input type="submit" value="提交" />
</form>
定义 POJO:
public class Person{
private String userId;
private String userName;
... setter/getter ...
}
在控制器中定义的处理器方法:
@RequestMapping("/formTable")
public String findUser(Person p) {
    System.out.println(p.userId + ":" + p.userName);
return "/index.jsp";
}

4)如果请求中传递的参数名的格式为:" 对象.属性 ",如:
表单定义为:
<form action="/formTable">
  ID: <input type="text" name="p.userId" /><br/>
NAME: <input type="text" name="p.userName" /><br/>
  <input type="submit" value="提交" />
</form>
则需要额外定义一个 POJO:
public class Human{
private Person p;
... setter/getter ...
}
在控制器中定义的处理器方法:
@RequestMapping("/formTable")
public String findUser(Human man) {
    System.out.println(man.p.userId + ":" + man.p.userName);
return "redirect:/index.jsp";
}

5)如果请求中传递的参数是集合对象类型参数,如:
表单定义为:
<form action="/formTable">
  <input type="text" name="p[0].userId" /><br/>
<input type="text" name="p[0].userName" /><br/>
<input type="text" name="p[1].userId" /><br/>
<input type="text" name="p[1].userName" /><br/>
<input type="text" name="p[2].userId" /><br/>
<input type="text" name="p[2].userName" /><br/>
  <input type="submit" value="提交" />
</form>
则需要额外定义一个 POJO:
public class Human{
private List<Person> p;
... setter/getter ...
}
在控制器中定义的处理器方法:
@RequestMapping("/formTable")
public String findUser(Human man) {
for(Person per : man.p){
System.out.println(per.userId + ":" + per.userName);
}
    return "redirect:/index.jsp";
}

6)使用 @RequestBody 注解映射请求体@RequestBody 注解的参数,提供了对 HTTP 请求体的存取
a. 请求体到方法参数的转换是由 HttpMessageConverter 完成的。HttpMessageConverter 负责将 HTTP 请求信息转换成对象,以及将对象转换回一个 HTTP 响应体

5. HandlerMethod 支持的作用域方法参数类型
1)Servlet 域对象(Servlet API),比如 ServletRequest、 HttpServletRequest、HttpSession 对象等
2)Map 集合,Spring 会对通过 BindingAwareModelMap 将 Map 中的内容存放在 request 作用域中
3)ModelMap 类,它是 Map 的一个实现类,会自动为添加进来的对象生成一个键名。为添加对象生成名称的策略是,若添加对象是一个纯Java bean,如 User,那么使用对象类的短类名 user 来作为该对象的名称,如:
User us = new User();
ModelMap mm = new ModelMap(); // 注意 ModelMap 不能声明泛型,即不能像 Map 那样可以声明泛型:Map<String, Object> map = new HashMap<>();
mm.addAttribute(us); 等价于 mm.addAttribute("user", us);

4)Model 接口,把内容最终存放在 request 作用域中
model.addAttribute(Object attributeValue);
model.addAttribute(String attributeName, Object attributeValue);

5)ModelAndView 类,ModelAndView 内部使用了一个 ModelMap 类。把内容最终存放在 request 作用域中
ModelAndView mav = new ModelAndView(String viewName);
mav.addObject(Object attributeValue);
mav.addObject(String attributeName, Object attributeValue);

6)例如,定义一个控制器如下:
@Controller
public class MenuController {
     @RequestMapping("/demo1")
     public String demo1(HttpServletRequest req){
         req.setAttribute("request", "hello,request");
        HttpSession session = req.getSession();
         session.setAttribute("session", "hello,session");
         ServletContext application = req.getServletContext();
         application.setAttribute("application", "hello,application");
         return "/index.jsp";
}

@RequestMapping("/demo2")
    public String demo2(Map<String,Object> map){
         System.out.println(map.getClass());
        map.put("map", "hello,map");
         return "/index.jsp";
     }

@RequestMapping("/demo3")
    public String demo3(ModelMap mm){
//         mm.addAttribute(Object attributeValue);
//         mm.addAttribute(String attributeName, Object attributeValue)
         return "/index.jsp";
     }
    
     @RequestMapping("/demo4")
     public String mode4(Model model){
         model.addAttribute("model", "hello,model");
         return "/index.jsp";
     }
    
    @RequestMapping("/demo5")
    public ModelAndView mode5(){
        // 参数:指定跳转视图
         ModelAndView mav = new ModelAndView("/index.jsp");
        mav.addObject("mav", "hello,modelAndView");
        return mav;
    }
}
定义 index.jsp 如下:
<body>
${requestScope.request} <br/>
${sessionScope.session} <br/>
${sessionScope.sessionParam} <br/>
${applicationScope.application} <br/>
${requestScope.map} <br/>
${requestScope.model} <br/>
${requestScope.mav} <br/>
</body>

6. 跳转方式
  1)使用 return 进行跳转,默认跳转方式为:请求转发,如:return "/index.jsp";
2)指定跳转方式
  a. 重定向,在返回值字符串内容前添加 "redirect:",如:return "redirect:/index.jsp";
  b. 请求转发,在返回值字符串内容前添加 "forward:",如:return "forward:/index.jsp";

7. 使用 @ResponseBody 注解映射响应体
1)@ResponseBody 注解可被应用于方法上,标志该方法的返回值应该被直接写回到 HTTP 响应体中去,而不是被放置到 Model 中或被解释为一个视图名,如:
@RequestMapping("/demo")
@ResponseBody
public String helloWorld() {
   return "Hello World!";
}
上面的代码结果是文本 Hello World 将被写入 HTTP 的响应流中
2)@ResponseBody 注解与 @RequestBody 注解类似,Spring 使用了 HttpMessageConverter 来将返回对象转换到响应体中

8. 使用 @RestController 注解创建 REST 控制器
1)当今让控制器实现一个 REST API 是非常常见的,这种场景下控制器只需要提供 JSON、XML 或其他自定义的媒体类型内容即可
2)@RestController 是一个原生内置的注解,它结合了 @Controller 与 @ResponseBody 注解的功能。不仅如此,它也让你的控制器更表义,而且在框架未来的发布版本中,它也可能承载更多的意义
3)这就意味着你不需要在每个 @RequestMapping 方法上都添加一个 @ResponseBody 注解,而只要在控制器上添加一个 @RestController 注解,如:
@Controller
public class MyController{
@RequestMapping("/demo")
@ResponseBody
public String demo() {
   return "Hello World !";
}

@RequestMapping("/demo2")
@ResponseBody
public String demo2() {
   return "Hello World !";
}
}
可简写为:
@RestController
public class MyController{
@RequestMapping("/demo")
public String demo() {
   return "Hello World !";
}

@RequestMapping("/demo2")
public String demo2() {
   return "Hello World !";
}
}

3.  Spring MVC 视图解析器

1. Spring 中的视图由一个视图名标识,并由视图解析器来渲染
2. 正如在 Spring MVC 控制器的实现一节中看到的,所有控制器的处理器方法都必须返回一个逻辑视图的名字,无论是显式返回(比如返回一个 String、View 或者 ModelAndView)还是隐式返回(比如基于约定的返回)
3. Spring 有非常多内置的视图解析器,最常用的视图解析器:InternalResourceViewResolver
1)UrlBasedViewResolver 的一个好用的子类,它支持内部资源视图(具体来说,Servlet 和 JSP)
2)全限定路径:
org.springframework.web.servlet.view.InternalResourceViewResolver
3) 在 springmvc.xml 中添加配置:

<!-- 视图解析器 -->
   <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
          <property name="prefix" value="/"></property>
          <property name="suffix" value=".jsp"></property>
      </bean>
如:若返回一个 "index" 逻辑视图名,那么该视图解析器会将请求转发到 RequestDispatcher,后者会将请求交给 /WebContent/index.jsp 视图去渲染

4.  Spring MVC 文件上传

1. Spring 内置对多路上传的支持(MultipartResolver 多路解析器),专门用于处理 Web 应用中的文件上传
2. 默认情况下,Spring 的多路上传支持是不开启的,如果想启用多路上传支持,你需要在 springmvc.xml 中添加一个多路传输解析器:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
   <!-- 支持的属性,设置支持上传文件的大小的最大值,以字节为单位 -->
       <property name="maxUploadSize" value="1024"></property>
   </bean>
3. 要让多路解析器正常工作,你需要导入额外的 jar 包:commons-fileupload.jar、commons-io.jar
下载地址: https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload
https://mvnrepository.com/artifact/commons-io/commons-io

4. 当 DispatcherServlet 检测到一个多部分请求时,它会激活你在上下文中声明的多路解析器并把请求交给它。解析器会把当前的 HttpServletRequest 请求对象包装成一个支持多路文件上传的请求对象 MultipartHttpServletRequest。有了 MultipartHttpServletRequest 对象,你不仅可以获取该多路请求中的信息,还可以在你的控制器中获得该多路请求的内容本身

5. Spring MVC 文件上传实例
1)定义 index.jsp :
<form action="/upload" enctype="multipart/form-data" method="post">
          文件:<input type="file" name="file"/></br>
      <input type="submit" value="提交"/>
</form>
// enctype 属性控制表单类型
a. 默认值 "application/x-www-form-urlencoded" ,用于普通表单数据(少量文字信息)
b. "text/plain",用于大文字量表单数据,如:邮件、论文
c. "multipart/form-data",用于包含二进制文件内容的表单数据

2)编写控制器:
@Controller
public class UploadController {
     @RequestMapping("/upload")
     public String upload (MultipartFile file) throws IOException{
         String originalName = file.getOriginalFilename();
         String suffix = fileName.substring(fileName.lastIndexOf("."));
         String fileName = UUID.randomUUID().toString() + suffix;
         FileUtils.copyInputStreamToFile(file.getInputStream(), new File("E:/" + fileName));
         return "/index.jsp";
    }
}
// 注意,MultipartFile 声明的参数名必须和表单中 <input/> name 属性值相同

6. Spring MVC 文件下载实例
1)在 index.jsp 定义下载链接:
<a href="/download?fileName=a.txt">点击下载</a>

2)编写控制器:
@Controller
public class DownloaderController {
    @RequestMapping("/download")
    public void download(String fileName, HttpServletRequest req, HttpServletResponse resp) throws IOException{
        resp.setHeader("Content-Disposition", "attachment;filename=" + fileName);
         ServletOutputStream os = resp.getOutputStream();
        String realPath = req.getServletContext().getRealPath("/files");
         File file = new File(realPath, fileName);
         byte[] bytes = FileUtils.readFileToByteArray(file);
        os.write(bytes);
         os.flush();
         os.close();
     }
}
// 修改响应头:Context-Disposition = "attachment;filename=文件名"
a. attachment,以附件形式下载;
b. filename = "",下载时显示的文件名
c. 访问资源时响应头如果没有设置 "Content-Disposition",浏览器默认按照 "inline" 值进行处理,即能显示就显示,不能显示就下载

5.  Spring MVC 异常处理

1. Spring 的处理器异常解析器 HandlerExceptionResolver 接口的实现负责处理各类控制器执行过程中出现的异常
2. 实现 HandlerExceptionResolver 接口并非实现异常处理的唯一方式,它只是提供了 resolveException(Exception, Hanlder) 方法的一个实现而已,方法会返回一个 ModelAndView。除此之外,你还可以使用框架提供的 SimpleMappingExceptionResolver 或在异常处理方法上注解 @ExceptionHandler
3. 使用 SimpleMappingExceptionResolver 解析器声明式地将异常映射到特定的视图上,在 springmvc.xml 添加配置:
<!-- 异常映射解析器 -->
   <bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
      <property name="exceptionMappings">
         <props>
<!-- Spring MVC 多路上传的文件大小超过支持的最大值出现异常时,跳转到指定视图 -->
          <prop key="org.springframework.web.multipart.MaxUploadSizeExceededException">/error.jsp</prop>
         </props>
      </property>
  </bean>

4. 使用 @ExceptionHandler 注解,若抛出了已在注解参数中声明的异常,那么相应的 @ExceptionHandler 方法将会被调用
1)如果 @ExceptionHandler 方法是在控制器内部定义的,那么它会接收并处理由控制器(或其任何子类)中的 @RequestMapping 方法抛出的异常,如:
定义在控制器内部的 @ExceptionHandler 方法
@Controller
public class SimpleController {
// @RequestMapping methods omitted(省略) ...
@ExceptionHandler(IOException.class)
    public ResponseEntity<String> handleIOException(IOException ex) {
        // prepare responseEntity
        return responseEntity;
    }
}
2)如果没有给 @ExceptionHandler 注解任何参数值,那么默认处理的异常类型将是方法参数所声明的那些异常
3)@ExceptionHandler 注解还可以接受一个异常类型的数组作为参数值

5. Spring MVC 对 Servlet 默认容器错误页面的定制化
1)当响应的状态码被设置为错误状态码,并且响应体中没有内容时,Servlet 容器通常会渲染一个 HTML 错误页
2)若需要定制容器默认提供的错误页,你可以在 web.xml 中定义一个错误页元素:<error-page>,如下:
<error-page>
    <location>/error</location>
</error-page>
a. 在 Servlet 3 规范出来之前,该错误页元素必须被显式指定映射到一个具体的错误码或一个异常类型
b. 从 Servlet 3 出来之后,错误页不再需要映射到其他信息了,这意味着,你指定的位置就是对 Servlet 容器默认错误页的自定制了
c. 这里错误页的位置所在可以是一个 JSP 页面,或者其他的一些 URL

3)只要它指定任意一个 @Controller 控制器下的处理器方法,写回的 HttpServletResponse 的错误信息和错误状态码就可以在控制器中通过请求属性来获取,如
@Controller
public class ErrorController {
@RequestMapping(path = "/error", produces = MediaType.APPLICATION_JSON_UTF8_VALUE )
    @ResponseBody
    public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", request.getAttribute("javax.servlet.error.status_code"));
        map.put("reason", request.getAttribute("javax.servlet.error.message"));
        return map;
    }
}
或者在 JSP 中这么使用:
<%@ page contentType="application/json" pageEncoding="UTF-8"%>
{
    status:<%=request.getAttribute("javax.servlet.error.status_code") %>,
    reason:<%=request.getAttribute("javax.servlet.error.message") %>
}

6.  Spring MVC 处理器拦截器

1. Spring 的处理器映射机制包含了处理器拦截器,拦截器在你需要为特定类型的请求添加一些功能时可能很有用,比如,检查用户身份、日志记录、敏感词汇过滤等
2. Spring MVC 处理器拦截器和过滤器(Filter)的区别
1)Spring MVC 处理器拦截器只能拦截 Controller
2)Filter 可以拦截任何请求

3. 处理器映射处理过程配置的拦截器,必须实现 org.springframework.web.servlet 包下的 HandlerInterceptor 接口,这个接口定义了三个方法
1)preHandle(..),它在处理器实际执行之前被执行
a. 方法返回一个 boolean 值,你可以通过这个方法来决定是否继续执行处理链中的部件
b. 当方法返回 true 时,处理器链会继续执行
c. 当方法返回 false 时, DispatcherServlet 会认为拦截器自身已经完成了对请求的处理(比如说,已经渲染了一个合适的视图),那么其余的拦截器以及执行链中的其他处理器就不会再被执行了
2)postHandle(..),它在处理器执行完毕以后被执行
3)afterCompletion(..),它在整个请求处理完成之后被执行

4. Spring MVC 拦截器需要在 springmvc.xml 中通过 interceptors 属性来配置,并且有两种配置方式:
1)拦截所有 Controller
<!-- 拦截器:拦截所有 controller -->
      <mvc:interceptors>
         <bean class="com.ncdx.interceptor.DemoInterceptor01"></bean> // class = 拦截器实现类的全限定路径
      <bean class="com.ncdx.interceptor.DemoInterceptor02"></bean>
</mvc:interceptors>

2)拦截指定 Controller
<!-- 拦截器:拦截指定 controller -->
      <mvc:interceptors>
         <mvc:interceptor>
           <mvc:mapping path="/index"/>
            <mvc:mapping path="/error"/>
            <mvc:mapping path="/demo"/>
            <bean class="com.ncdx.interceptor.DemoInterceptor"></bean>
         </mvc:interceptor>
      </mvc:interceptors>

5. Spring MVC 拦截器栈
1)多个拦截器同时生效时,组成了拦截器栈
2)拦截器顺序跟 Spring MVC 配置顺序有关,并且是 "先进后出",即:
先进:preHandle01 --> preHandle02 -->
      执行处理器方法 -->
      后出:postHandle02 --> postHandle01 -->
      执行 index.jsp -->
      后出:afterCompletion02 --> afterCompletion01

6. Spring MVC 拦截器实例如下:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class DemoInterceptor implements HandlerInterceptor{
    /**
     * 在进入控制器之前执行
     * 如果返回值为false,阻止进入控制器,返回空白页
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("handler:"+handler.getClass());
        System.out.println("preHandle");
        return true;
    }
    
    /**
     * 控制器执行完成,进入jsp之前执行
     * 日志记录,敏感词语过滤
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("往"+modelAndView.getViewName()+"跳转");
        System.out.println("model值: "+modelAndView.getModel().get("model"));
        //拦截敏感词
        String word = modelAndView.getModel().get("model").toString();
        String newWord = word.replace("祖国","**");
        modelAndView.getModel().put("model", newWord);
        System.out.println("model值: "+modelAndView.getModel().get("model"));
        System.out.println("postHandle");
    }
    
    /**
     * jsp执行完成之后执行
     * 记录执行过程中出现的异常
     * 我们可以把异常记录到日志中
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("异常记录: "+ex.getMessage());
        System.out.println("afterCompletion");
    }
DemoInterceptor.java

7.  参考资料:https://www.w3cschool.cn/spring_mvc_documentation_linesh_translation/

原文地址:https://www.cnblogs.com/IT-LFP/p/11803689.html