Spring MVC 详细笔记、原理及实例演示

本文深入探讨了Spring MVC框架,从概述、MVC模式、优缺点开始,详细阐述了其执行流程,包括DispatcherServlet、HandlerMapping、HandlerAdapter等核心组件的工作原理。接着介绍了常用的注解如@Controller、@RequestMapping的使用,讲解了参数传递的多种方式,如实体Bean、HttpServletRequest、@PathVariable和@RequestParam。此外,还涉及了转发和重定向的区别、类型转换器Converter、数据格式化Formatter、拦截器Interceptor的使用和数据校验以及异常处理机制。最后,讲解了文件上传的相关知识,包括MultipartResolver接口和单文件上传的实现。通过本文,读者可以全面了解Spring MVC的运作机制和实践技巧。

Spring MVC

参考:编程帮

1.概述

1.1 简介

**Spring MVC **框架是一个开源的 Java 平台,为开发强大的基于 Java 的 Web 应用程序提供全面的基础架构支持非常容易和非常快速。

Spring web MVC框架提供了MVC(模型 - 视图 - 控制器)架构和用于开发灵活和松散耦合的Web应用程序的组件。

MVC模式导致应用程序的不同方面(输入逻辑,业务逻辑和UI逻辑)分离,同时提供这些元素之间的松散耦合。

1.2 MVC 模式概述

MVC 设计模式一般指 MVC 框架,M(Model)指数据模型层,V(View)指视图层,C(Controller)指控制层。使用 MVC 的目的是将 M 和 V 的实现代码分离,使同一个程序可以有不同的表现形式。

其中,View 的定义比较清晰,就是用户界面。

在 Web 项目的开发中,能够及时、正确地响应用户的请求是非常重要的。用户在网页上单击一个 URL 路径,这对 Web 服务器来说,相当于用户发送了一个请求。而获取请求后如何解析用户的输入,并执行相关处理逻辑,最终跳转至正确的页面显示反馈结果,这些工作往往是控制层(Controller)来完成的。

在请求的过程中,用户的信息被封装在 User 实体类中,该实体类在 Web 项目中属于数据模型层(Model)

在请求显示阶段,跳转的结果网页就属于视图层(View)。

1.2.1 总结
  • 视图层(View):负责格式化数据并把它们呈现给用户,包括数据展示、用户交互、数据验证、界面设计等功能。

    具体的页面展示

  • 控制层(Controller):负责接收并转发请求,对请求进行处理后,指定视图并将响应结果发送给客户端。

    数据接收与处理

  • 数据模型层(Model):模型对象拥有最多的处理任务,是应用程序的主体部分,它负责数据逻辑(业务规则)的处理和实现数据操作(即在数据库中存取数据)。

    业务处理

1.3 优缺点

1.3.1 优点
  • 多视图共享一个模型,大大提高了代码的可重用性
  • MVC 三个模块相互独立,松耦合架构
  • 控制器提高了应用程序的灵活性和可配置性
  • 有利于软件工程化管理

总之,我们通过 MVC 设计模式最终可以打造出一个松耦合+高可重用性+高可适用性的完美架构。

1.3.2 缺点
  • 原理复杂
  • 增加了系统结构和实现的复杂性
  • 视图对模型数据的低效率访问
1.3.3 Spring MVC 的优点
  • 清晰地角色划分,Spring MVC 在 Model、View 和 Controller 方面提供了一个非常清晰的角色划分,这 3 个方面真正是各司其职,各负其责。
  • 灵活的配置功能,可以把类当作 Bean 通过 XML 进行配置。
  • 提供了大量的控制器接口和实现类,开发者可以使用 Spring 提供的控制器实现类,也可以自己实现控制器接口。
  • 真正做到与 View 层的实现无关。它不会强制开发者使用 JSP,可以根据项目需求使用 Velocity、FreeMarker 等技术。
  • 国际化支持
  • 面向接口编程
  • 与 Spring 框架无缝集成

结合 Spring 框架使用。

2.Spring MVC 执行流程

2.1 核心组成

Spring MVC主要由DispatcherServlet(前端控制器)、HandlerMapping(处理器映射器)、HandlerAdapter(处理器适配器)、Handler(处理器)、ViewResolver(视图解析器)和 View(视图)组成。它的两个核心是两个核心:

  • 处理器映射:选择使用哪个控制器来处理请求
  • 视图解析器:选择结果应该如何渲染

通过以上两点,Spring MVC保证了如何选择控制处理请求和如何选择视图展现输出之间的松耦合。

2.2 执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-InmHHunH-1644066500137)(F:\StudyNotepad\img\1139441444-0.png)]

  1. 发送请求

    用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器)

  2. 由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器),并返回一个执行链(HandlerExecutionChain)

    遍历容器中存在的 HandlerMapping 找寻能够处理的 Handler

    image-20220205194811950
  3. DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器)

    适配器模式,并不会找寻具体的 Handler 而是找寻对象 HandlerAdapter 这样的好处在于可以解耦合,对接口负责,便于添加新的 Handler

  4. HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller)

  5. Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息)

    所有的提交信息都会被封装到这个 ModelAndView 中

  6. HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet

  7. DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析

  8. ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet

  9. DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图)

  10. 视图负责将结果显示到浏览器(客户端)

2.3 组件说明

1)DispatcherServlet

DispatcherServlet 是前端控制器,从图 1 可以看出,Spring MVC 的所有请求都要经过 DispatcherServlet 来统一分发。DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。

2)HandlerMapping

HandlerMapping 是处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息。

3)HandlerAdapter

HandlerAdapter 是处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)。

4)Handler

Handler 是处理器,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中。

5)View Resolver

View Resolver 是视图解析器,其作用是进行解析操作,通过 ModelAndView 对象中的 View 信息将逻辑视图名解析成真正的视图 View(如通过一个 JSP 路径返回一个真正的 JSP 页面)视图渲染。

6)View

View 是视图,其本身是一个接口,实现类支持不同的 View 类型(JSP、FreeMarker、Excel 等)。

以上组件中,需要开发人员进行开发的是处理器(Handler,常称Controller)和视图(View)。通俗的说,要开发处理该请求的具体代码逻辑,以及最终展示给用户的界面。

2.3.1 知识补充 Handler (Controller 控制器)

控制器采用单例模式,在进行多线程访问的时候有线程安全问题,不建议使用同步方法,会有性能问题。

3.常用注解

@Controller 注解

声明该类时一个控制器。

@Controller
public class IndexController {
    // 处理请求的方法
}

Spring MVC 使用扫描机制找到应用中所有基于注解的控制器类,所以,为了让控制器类被 Spring MVC 框架扫描到,需要在配置文件中声明 spring-context,并使用 <context:component-scan/> 元素指定控制器类的基本包(请确保所有控制器类都在基本包及其子包下)。

<!-- 使用扫描机制扫描控制器类,扫描指定路径包及其子包下 -->
<context:component-scan base-package="xx.xx" />

@RequestMapping

一个控制器内有多个处理请求的方法,如 UserController 里通常有增加用户、修改用户信息、删除指定用户、根据条件获取用户列表等。每个方法负责不同的请求操作,而 @RequestMapping 就负责将请求映射到对应的控制器方法上。

@RequestMapping 注解可用于类或方法上。用于类上,表示类中的所有响应请求的方法都以该地址作为父路径。

1.属性
  • value:@RequestMapping 注解的默认属性,因此如果只有 value 属性时,可以省略该属性名,如果有其它属性,则必须写上 value 属性名称

    @RequestMapping(value="toUser") // 多个属性
    @RequestMapping("toUser") // 默认 value
    

    value 属性支持通配符匹配,如 @RequestMapping(value=“toUser/*”) 表示 http://localhost:8080/toUser/1 或 http://localhost:8080/toUser/hahaha 都能够正常访问。

  • path:path 属性和 value 属性都用来作为映射使用

  • name:name属性相当于方法的注释,使方法更易理解。如 @RequestMapping(value = “toUser”,name = “获取用户信息”)

  • method:用于表示该方法支持哪些 HTTP 请求。如果省略 method 属性,则说明该方法支持全部的 HTTP 请求

    @RequestMapping(value = “toUser”,method = RequestMethod.GET) 表示该方法只支持 GET 请求。也可指定多个 HTTP 请求,如 @RequestMapping(value = “toUser”,method = {RequestMethod.GET,RequestMethod.POST}),说明该方法同时支持 GET 和 POST 请求。

  • params:用于指定请求中规定的参数,在访问具体路径时需要携带以 params 的参数

    @RequestMapping(value = "toUser",params = "type")
    public String toUser() {
        
        return "showUser";
    }
    

    以上代码表示请求中必须包含 type 参数时才能执行该请求。即 http://localhost:8080/toUser?type=xxx 能够正常访问 toUser() 方法,而 http://localhost:8080/toUser 则不能正常访问 toUser() 方法。

  • header:表示请求中必须包含某些指定的 header 值

  • consumers:用于指定处理请求的提交内容类型(Content-Type)

    // 指定数据的提交类型为 json,否则无法正确接收
    @RequestMapping(value = "toUser",consumes = "application/json")
    
  • produces:用于指定返回的内容类型,返回的内容类型必须是 request 请求头(Accept)中所包含的类型

    如:@RequestMapping(value = “toUser”,produces = “application/json”)。

    除此之外,produces 属性还可以指定返回值的编码。如 @RequestMapping(value = “toUser”,produces = “application/json,charset=utf-8”),表示返回 utf-8 编码。

@RequestBody

数据响应式,返回数据。

1.返回类型
  • ModelAndView
  • Model
  • 包含模型属性的 Map
  • View
  • 代表逻辑视图名的 String
  • void
  • 其它任意 Java 类型

@SessionAttribute

希望在多个请求之间共用数据,则可以在控制器类上标注一个 @SessionAttributes,配置需要在session中存放的数据范围,Spring MVC将存放在model中对应的数据暂存到HttpSession 中。

只能使用在类定义上。

@SessionAttributes 除了可以通过属性名指定需要放到会 话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中。

1.参数
  • names:这是一个字符串数组。里面应写需要存储到session中数据的名称
  • types:根据指定参数的类型,将模型中对应类型的参数存储到session中
  • value:和names一样

4.Spring MVC 参数传递

4.1 概述

Spring MVC Controller 接收请求参数的方式有很多种,有的适合 get 请求方式,有的适合 post 请求方式,有的两者都适合。主要有以下几种方式:

  • 通过实体 Bean 接收请求参数
  • 通过处理方法的形参接收请求参数
  • 通过 HttpServletRequest 接收请求参数
  • 通过 @PathVariable 接收 URL 中的请求参数
  • 通过 @RequestParam 接收请求参数
  • 通过 @ModelAttribute 接收请求参数

4.2 通过实体Bean接收请求参数

实体 Bean 接收请求参数适用于 get 和 post 提交请求方式。需要注意,Bean 的属性名称必须与请求参数名称相同。

Spring MVC 会将接收到的属性和值封装到 Bean 中。

4.2.1 实例
@RequestMapping("/login")
public String login(User user, Model model) {
    // 获取封装后的值
    if ("xxx".equals(user.getName())
            && "123456".equals(user.getPwd())) {
        // 将业务处理后的结果封装到 Attribute 中,也就是封装到响应中
        // 前端也可以获取到相应的信息
        model.addAttribute("message", "登录成功");
        // 登录成功,跳转到 main.jsp
        return "main"; 
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

4.3 通过表单发送的信息接收请求参数

形参名称与请求参数名称完全相同。该接收参数方式适用于 get 和 post 提交请求方式。

4.3.1 实例
@RequestMapping("/login")
// 按顺序赋值
public String login(String name, String pwd, Model model) {
    if ("xxx".equals(user.getName())
            && "123456".equals(user.getPwd())) {
       
        model.addAttribute("message", "登录成功");
        // 登录成功,跳转到 main.jsp
        return "main"; 
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

4.4 通过HttpServletRequest接收请求参数

通过 HttpServletRequest 接收请求参数适用于 get 和 post 提交请求方式。

4.4.1 实例
@RequestMapping("/login")
// 获取请求域中的值
public String login(HttpServletRequest request, Model model) {
    String name = request.getParameter("name");
    String pwd = request.getParameter("pwd");
   
    if ("xxx".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        // 登录成功,跳转到 main.jsp
        return "main"; 
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

4.5 通过 @PathVariable 接收 URL 中的请求参数

获取请求 URL 中的参数 Rest 风格(通过/分割)获取。

4.5.1 实例
@RequestMapping("/login/{name}/{pwd}")
public String login(
   @PathVariable 
   String name, 
   @PathVariable 
   String pwd, 
   Model model) {
   
    if ("xxx".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        // 登录成功,跳转到 main.jsp
        return "main"; 
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

在访问“http://localhost:8080/register/bianchengbang/123456”路径时,上述代码会自动将 URL 中的模板变量 {name} 和 {pwd} 绑定到通过 @PathVariable 注解的同名参数上,即 name=xxx、pwd=123456。

4.6 通过@RequestParam接收请求参数

在方法入参处使用 @RequestParam 注解指定其对应的请求参数。

@RequestParam 有以下三个参数:

  • value:参数名
  • required:是否必须,默认为 true,表示请求中必须包含对应的参数名,若不存在将抛出异常
  • defaultValue:参数默认值和形参名字相同
4.6.1 实例
@RequestMapping("/login")
public String login(@RequestParam String name, @RequestParam String pwd, Model model) {
   
    if ("xxx".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        // 登录成功,跳转到 main.jsp
        return "main"; 
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

和直接接收参数不懈怠注释的参数传递相比:当请求参数与接收参数名不一致时,“通过处理方法的形参接收请求参数”不会报 404 错误,而“通过 @RequestParam 接收请求参数”会报 404 错误。

4.7 通过@ModelAttribute接收请求参数

@ModelAttribute 注解用于将多个请求参数封装到一个实体对象中,从而简化数据绑定流程,而且自动暴露为模型数据,在视图页面展示时使用。

而“通过实体 Bean 接收请求参数”中只是将多个请求参数封装到一个实体对象,并不能暴露为模型数据。

@RequestMapping("/login")
public String login(@ModelAttribute("user") User user, Model model) {
    if ("xxx".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        // 登录成功,跳转到 main.jsp
        return "main"; 
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}
4.7.1 使用方式

在 Controller 中使用 @ModelAttribute 时,有以下几种应用情况:

  • 应用在方法上
  • 应用在方法的参数上
  • 应用在方法上,并且方法也使用了 @RequestMapping

需要注意的是,因为模型对象要先于 controller 方法之前创建,所以被 @ModelAttribute 注解的方法会在 Controller 每个方法执行之前都执行。因此一个 Controller 映射多个 URL 时,要谨慎使用。

4.7.2 Model和ModelView的区别

Model:每次请求中都存在的默认参数,利用其 addAttribute() 方法即可将服务器的值传递到客户端页面中。

ModelAndView:包含 model 和 view 两部分,使用时需要自己实例化,利用 ModelMap 来传值,也可以设置 view 的名称。

4.7.3 应用

@ModelAttribute 注解的方法会在每次调用该控制器类的请求处理方法前被调用。这种特性可以用来控制登录权限。

控制登录权限的方法有很多,例如拦截器、过滤器等。

4.7.3.1 实例
1.创建 BaseController
public class BaseController {
    // 检查Session
    @ModelAttribute
    public void isLogin(HttpSession session) throws Exception {
        if (session.getAttribute("user") == null) {
            throw new Exception("没有权限");
        }
    }
}
2.创建 ModelAttributeController
@RequestMapping("/admin")
// 继承,子类的实例化一定会先实例化父类
public class ModelAttributeController extends BaseController {
    @RequestMapping("/add")
    public String add() {
        return "addSuccess";
    }
    @RequestMapping("/update")
    public String update() {
        return "updateSuccess";
    }
    @RequestMapping("/delete")
    public String delete() {
        return "deleteSuccess";
    }
}

5.转发和重定向

5.1 概述

重定向是将用户从当前处理请求定向到另一个视图(例如 JSP)或处理请求,以前的请求(request)中存放的信息全部失效,并进入一个新的 request 作用域

转发是将用户对当前处理的请求转发给另一个视图或处理请求,以前的 request 中存放的信息不会失效

Spring MVC 请求方式分为转发、重定向 2 种,分别使用 forward 和 redirect 关键字在 controller 层进行处理。

转发是服务器行为,重定向是客户端行为。

服务器帮我们转发到指定地址,重定向是客户端交给用户一个地址,用户自己定位到新的地址上。

5.2 转发过程

客户浏览器发送 http 请求,Web 服务器接受此请求,调用内部的一个方法在容器内部完成请求处理和转发动作,将目标资源发送给客户;

在这里转发的路径必须是同一个 Web 容器下的 URL,其不能转向到其他的 Web 路径上,中间传递的是自己的容器内的 request。

在客户浏览器的地址栏中显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。

在 Spring MVC 框架中,控制器类中处理方法的 return 语句默认就是转发实现,只不过实现的是转发到视图。

5.3 重定向过程

客户浏览器发送 http 请求,Web 服务器接受后发送 302 状态码响应及对应新的 location 给客户浏览器,客户浏览器发现是 302 响应,则自动再发送一个新的 http 请求,请求 URL 是新的 location 地址,服务器根据此请求寻找资源并发送给客户。

在这里 location 可以重定向到任意 URL,既然是浏览器重新发出了请求,那么就没有什么 request 传递的概念了。

在客户浏览器的地址栏中显示的是其重定向的路径,客户可以观察到地址的变化。重定向行为是浏览器做了至少两次的访问请求。

5.3.1 实例演示
@Controller
@RequestMapping("/index")
public class IndexController {
    @RequestMapping("/login")
    public String login() {
        // 转发到一个请求方法(同一个控制器类可以省略/index/)
        return "forward:/index/isLogin";
    }
   
    @RequestMapping("/isLogin")
    public String isLogin() {
        // 重定向到一个请求方法
        return "redirect:/index/isRegister";
    }
   
    @RequestMapping("/isRegister")
    public String isRegister() {
        // 转发到一个视图
        return "register";
    }
}

6.Spring MVC 类型转换器(Converter)

6.1 概述

Spring MVC 框架的 Converter<S,T> 是一个可以将一种数据类型转换成另一种数据类型的接口,这里 S 表示源类型,T 表示目标类型。开发者在实际应用中使用框架内置的类型转换器基本上就够了,但有时需要编写具有特定功能的类型转换器。

6.2 内置的类型转换器

除了内置转换器用户也可以自定义,因为用处不多,就不进行展开。

6.2.1 标量转换器
名称作用
StringToBooleanConverterString 到 boolean 类型转换
ObjectToStringConverterObject 到 String 转换,调用 toString 方法转换
StringToNumberConverterFactoryString 到数字转换(例如 Integer、Long 等)
NumberToNumberConverterFactory数字子类型(基本类型)到数字类型(包装类型)转换
StringToCharacterConverterString 到 Character 转换,取字符串中的第一个字符
NumberToCharacterConverter数字子类型到 Character 转换
CharacterToNumberFactoryCharacter 到数字子类型转换
StringToEnumConverterFactoryString 到枚举类型转换,通过 Enum.valueOf 将字符串转换为需要的枚举类型
EnumToStringConverter枚举类型到 String 转换,返回枚举对象的 name 值
StringToLocaleConverterString 到 java.util.Locale 转换
PropertiesToStringConverterjava.util.Properties 到 String 转换,默认通过 ISO-8859-1 解码
StringToPropertiesConverterString 到 java.util.Properties 转换,默认使用 ISO-8859-1 编码
6.2.2 集合、数组相关转换器
名称作用
ArrayToCollectionConverter任意数组到任意集合(List、Set)转换
CollectionToArrayConverter任意集合到任意数组转换
ArrayToArrayConverter任意数组到任意数组转换
CollectionToCollectionConverter集合之间的类型转换
MapToMapConverterMap之间的类型转换
ArrayToStringConverter任意数组到 String 转换
StringToArrayConverter字符串到数组的转换,默认通过“,”分割,且去除字符串两边的空格(trim)
ArrayToObjectConverter任意数组到 Object 的转换,如果目标类型和源类型兼容,直接返回源对象;否则返回数组的第一个元素并进行类型转换
ObjectToArrayConverterObject 到单元素数组转换
CollectionToStringConverter任意集合(List、Set)到 String 转换
StringToCollectionConverterString 到集合(List、Set)转换,默认通过“,”分割,且去除字符串两边的空格(trim)
CollectionToObjectConverter任意集合到任意 Object 的转换,如果目标类型和源类型兼容,直接返回源对象;否则返回集合的第一个元素并进行类型转换
ObjectToCollectionConverterObject 到单元素集合的类型转换

类型转换是在视图与控制器相互传递数据时发生的。Spring MVC 框架对于基本类型(例如 int、long、float、double、boolean 以及 char 等)已经做好了基本类型转换。

注意:在使用内置类型转换器时,请求参数输入值与接收参数类型要兼容,否则会报 400 错误。请求参数类型与接收参数类型不兼容问题需要学习输入校验后才可解决。

7.Spring MVC 数据格式化(Formatter)

7.1 概述

Spring MVC 框架的 Formatter 与 Converter<S, T> 一样,也是一个可以将一种数据类型转换成另一种数据类型的接口。不同的是,Formatter 的源类型必须是 String 类型,而 Converter 的源类型可以是任意数据类型。Formatter 更适合 Web 层,而 Converter 可以在任意层中。所以对于需要转换表单中的用户输入的情况,应该选择 Formatter,而不是 Converter。

在 Web 应用中由 HTTP 发送的请求数据到控制器中都是以 String 类型获取,因此在 Web 应用中选择 Formatter 比选择 Converter<S, T> 更加合理。

7.2 内置的格式化转换器

Spring MVC 提供了几个内置的格式化转换器,具体如下:

  • NumberFormatter:实现 Number 与 String 之间的解析与格式化。
  • CurrencyFormatter:实现 Number 与 String 之间的解析与格式化(带货币符号)。
  • PercentFormatter:实现 Number 与 String 之间的解析与格式化(带百分数符号)。
  • DateFormatter:实现 Date 与 String 之间的解析与格式化。

还可以自定义

8.Spring MVC 拦截器(Interceptor)

8.1 概述

在系统中,经常需要在处理用户请求之前和之后执行一些行为,例如检测用户的权限,或者将请求的信息记录到日志中,即平时所说的“权限检测”及“日志记录”。当然不仅仅这些,所以需要一种机制,拦截用户的请求,在请求的前后添加处理逻辑。

Spring MVC 提供了 Interceptor 拦截器机制,用于请求的预处理和后处理。

在开发一个网站时可能有这样的需求:某些页面只希望几个特定的用户浏览。对于这样的访问权限控制,应该如何实现呢?拦截器就可以实现上述需求。Spring MVC 框架提供了拦截器功能。

Spring MVC 的拦截器(Interceptor)与 Java Servlet 的过滤器(Filter)类似,它主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。

8.2 拦截器的定义

在 Spring MVC 框架中定义一个拦截器需要对拦截器进行定义和配置,主要有以下 2 种方式:

  1. 通过实现 HandlerInterceptor 接口或继承 HandlerInterceptor 接口的实现类(例如 HandlerInterceptorAdapter)来定义
  2. 通过实现 WebRequestInterceptor 接口或继承 WebRequestInterceptor 接口的实现类来定义
8.2.1 实例 - HandlerInterceptor
public class TestInterceptor implements HandlerInterceptor {
    @Override
    public void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("afterCompletion方法在控制器的处理请求方法执行完成后执行,即视图渲染结束之后执行");
    }
    @Override
    public void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle方法在控制器的处理请求方法调用之后,解析视图之前执行");
    }
    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle方法在控制器的处理请求方法调用之前执行");
        return false;
    }
}

上述拦截器的定义中实现了 HandlerInterceptor 接口,并实现了接口中的 3 个方法,说明如下:

  • preHandle() :该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。
  • postHandle() :该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改。
  • afterCompletion() :该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作。

8.3 拦截器的配置

让自定义的拦截器生效需要在 Spring MVC 的配置文件中进行配置。

<!-- 配置拦截器 -->
<mvc:interceptors>
    <!-- 配置一个全局拦截器,拦截所有请求 -->
    <bean class="net.biancheng.interceptor.TestInterceptor" /> 
    <mvc:interceptor>
        <!-- 配置拦截器作用的路径 -->
        <mvc:mapping path="/**" />
        <!-- 配置不需要拦截作用的路径 -->
        <mvc:exclude-mapping path="" />
        <!-- 定义<mvc:interceptor>元素中,表示匹配指定路径的请求才进行拦截 -->
        <bean class="net.biancheng.interceptor.Interceptor1" />
    </mvc:interceptor>
    <mvc:interceptor>
        <!-- 配置拦截器作用的路径 -->
        <mvc:mapping path="/gotoTest" />
        <!-- 定义在<mvc:interceptor>元素中,表示匹配指定路径的请求才进行拦截 -->
        <bean class="net.biancheng.interceptor.Interceptor2" />
    </mvc:interceptor>
</mvc:interceptors>

在上述示例代码中,元素说明如下。

  • mvc:interceptors:该元素用于配置一组拦截器。
  • :该元素是 mvc:interceptors 的子元素,用于定义全局拦截器,即拦截所有的请求,指定类源。
  • mvc:interceptor:该元素用于定义指定路径的拦截器。
  • mvc:mapping:该元素是 mvc:interceptor 的子元素,用于配置拦截器作用的路径,该路径在其属性 path 中定义。path 的属性值为/**时,表示拦截所有路径,值为/gotoTest时,表示拦截所有以/gotoTest结尾的路径。如果在请求路径中包含不需要拦截的内容,可以通过 mvc:exclude-mapping 子元素进行配置。

需要注意的是,mvc:interceptor 元素的子元素必须按照 mvc:mapping.../、mvc:exclude-mapping.../、<bean…/> 的顺序配置。

9.Spring MVC 数据校验

9.1 概述

一般情况下,用户的输入是随意的,为了保证数据的合法性,数据验证是所有 Web 应用必须处理的问题。

Spring MVC 有以下两种方法可以验证输入:

  • 利用 Spring 自带的验证框架
  • 利用 JSR 303 实现

数据验证分为客户端验证和服务器端验证,客户端验证主要是过滤正常用户的误操作,通过 JavaScript 代码完成。服务器端验证是整个应用阻止非法数据的最后防线,通过在应用中编程实现。

演示以 JSR 303 为例。

9.2 JSR 303

JSR 303 是 Java 为 Bean 数据合法性校验所提供的标准框架。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证。可以通过 https://jcp.org/en/jsr/detail?id=303 查看详细内容并下载 JSR 303 Bean Validation。

9.2.1 注解
名称说明
@Null被标注的元素必须为 null
@NotNull被标注的元素必须不为 null
@AssertTrue被标注的元素必须为 true
@AssertFalse被标注的元素必须为 false
@Min(value)被标注的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)被标注的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMax(value)被标注的元素必须是一个数字,其值必须大于等于指定的最大值
@DecimalMin(value)被标注的元素必须是一个数字,其值必须小于等于指定的最小值
@size被标注的元素的大小必须在指定的范围内
@Digits(integer,fraction)被标注的元素必须是一个数字,其值必须在可接受的范围内;integer 指定整数精度,fraction 指定小数精度
@Past被标注的元素必须是一个过去的日期
@Future被标注的元素必须是一个将来的日期
@Pattern(value)被标注的元素必须符合指定的正则表达式

9.3 实例

public class User {
    @NotNull(message = "用户id不能为空")
    private Integer id;
    @NotNull
    @Length(min = 2, max = 8, message = "用户名不能少于2位大于8位")
    private String name;
    @Email(regexp = "[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]", message = "邮箱格式不正确")
    private String email;
    /** 省略setter和getter方法*/
}

10.Spring MVC 异常处理

10.1 概述

在 Spring MVC 应用的开发中,不管是操作底层数据库,还是业务层或控制层,都会不可避免地遇到各种可预知的、不可预知的异常。我们需要捕捉处理异常,才能保证程序不被终止。

Spring MVC 有以下 3 种处理异常的方式:

  1. 使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver。
  2. 实现 Spring 的异常处理接口 HandlerExceptionResolver,自定义自己的异常处理器。
  3. 使用 @ExceptionHandler 注解实现异常处理

10.2 @ExceptionHandler

局部异常处理仅能处理指定 Controller 中的异常。

10.2.1 实例

下面使用 @ExceptionHandler 注解实现。定义一个处理过程中可能会存在异常情况的 testExceptionHandle 方法。

@RequestMapping("/testExceptionHandle")
public String testExceptionHandle(@RequestParam("i") Integer i) {
    System.out.println(10 / i);
    return "success";
}

显然,当 i=0 时会产生算术运算异常。

下面在同一个类中定义处理异常的方法。

@ExceptionHandler({ ArithmeticException.class })
public String testArithmeticException(Exception e) {
    System.out.println("打印错误信息 ===> ArithmeticException:" + e);
    // 跳转到指定页面
    return "error";
}

注意:该注解不是加在产生异常的方法上,而是加在处理异常的方法上。

@ExceptionHandler 注解定义的方法优先级问题:

​ 例如发生的是 NullPointerException,但是声明的异常有 RuntimeException 和 Exception,这时候会根据异常的最近继承关系找到继承深度最浅的那个 @ExceptionHandler 注解方法,即标记了 RuntimeException 的方法。

被 @ExceptionHandler 标记为异常处理方法,不能在方法中设置别的形参。但是可以使用 ModelAndView 向前台传递数据。

使用局部异常处理,仅能处理某个 Controller 中的异常,若需要对所有异常进行统一处理,可使用以下两种方法。

10.3 HandlerExceptionResolver

10.3.1 概述

Spring MVC 通过 HandlerExceptionResolver 处理程序异常,包括处理器异常、数据绑定异常以及控制器执行时发生的异常。HandlerExceptionResolver 仅有一个接口方法,源码如下。

public interface HandlerExceptionResolver {
    @Nullable
    ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

发生异常时,Spring MVC 会调用 resolveException() 方法,并转到 ModelAndView 对应的视图中,返回一个异常报告页面反馈给用户。

10.3.2 实例

在 net.xxx.exception 包中创建一个 HandlerExceptionResolver 接口的实现类 MyExceptionHandler,代码如下。

public class MyExceptionHandler implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) {
        Map<String, Object> model = new HashMap<String, Object>();
        // 根据不同错误转向不同页面(统一处理),即异常与View的对应关系
        if (arg3 instanceof ArithmeticException) {
            return new ModelAndView("error", model);
        }
       
        return new ModelAndView("error-2", model);
    }
}

在 springmvc-servlet.xml 文件中添加以下代码。

<!--托管MyExceptionHandler-->
<bean class="net.xxx.exception.MyExceptionHandler"/>

再次访问 http://localhost:8080/springmvcDemo2/testExceptionHandle?i=0,页面跳转到 error.jsp 页面,运行结果如上图 1 所示。

10.4 SimpleMappingExceptionResolver

10.4.1 概述

全局异常处理可使用 SimpleMappingExceptionResolver 来实现。它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。

10.4.2 实例

在 springmvc-servlet.xml 中配置全局异常,代码如下。

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <!-- 定义默认的异常处理页面,当该异常类型注册时使用 -->
    <property name="defaultErrorView" value="error"></property>
    <!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception -->
    <property name="exceptionAttribute" value="ex"></property>
    <!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常页名作为值 -->
    <property name="exceptionMappings">
        <props>
            <prop key="ArithmeticException">error</prop>
            <!-- 在这里还可以继续扩展对不同异常类型的处理 -->
        </props>
    </property>
</bean>

再次访问 http://localhost:8080/springmvcDemo2/testExceptionHandle?i=0,页面跳转到 error.jsp 页面,运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wi8UZWRb-1644066500139)(F:\StudyNotepad\img\1533303H0-0.png)]

11.Spring MVC 文件上传

11.1 概述

Spring MVC 框架的文件上传基于 commons-fileupload 组件,并在该组件上做了进一步的封装,简化了文件上传的代码实现,取消了不同上传组件上的编程差异。

11.2 MultipartResolver 接口

在 Spring MVC 中实现文件上传十分容易,它为文件上传提供了直接支持,即 MultpartiResolver 接口。MultipartResolver 用于处理上传请求,将上传请求包装成可以直接获取文件的数据,从而方便操作。

MultpartiResolver 接口有以下两个实现类:

  • StandardServletMultipartResolver:使用了 Servlet 3.0 标准的上传方式。
  • CommonsMultipartResolver:使用了 Apache 的 commons-fileupload 来完成具体的上传操作。
11.2.1 接口方法
名称作用
byte[] getBytes()以字节数组的形式返回文件的内容
String getContentType()返回文件的内容类型
InputStream getInputStream()返回一个InputStream,从中读取文件的内容
String getName()返回请求参数的名称
String getOriginalFillename()返回客户端提交的原始文件名称
long getSize()返回文件的大小,单位为字节
boolean isEmpty()判断被上传文件是否为空
void transferTo(File destination)将上传文件保存到目标目录下

下面我们使用 CommonsMultipartResolver 来完成文件上传,分为单文件上传和多文件上传两部分介绍。

11.3 单文件上传

11.3.1 导入 jar 文件

文件上传使用 Apache Commons FileUpload 组件,需要导入 commons-io-2.4.jar 和 commons-fileupload-1.2.2.jar 两个 jar 文件(可在 Apache 官网下载)。

Maven 项目在 pom.xml 文件中添加以下依赖。

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.2.2</version>
</dependency>
11.3.2 配置 MultipartResolver

使用 CommonsMultipartReslover 配置 MultipartResolver 解析器,在 springmvc-servlet.xml 中添加代码如下。

<!-- 配置MultipartResolver,用于上传文件,使用spring的CommonsMultipartResolver -->
<bean id="multipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 最大文件上传大小,单位字节 -->
    <property name="maxUploadSize" value="5000000" />
    <property name="defaultEncoding" value="UTF-8" />
</bean>
  • defaultEncoding:请求的编码格式,默认为 ISO-8859-1,此处设置为 UTF-8(注:defaultEncoding 必须和 JSP 中的 pageEncoding 一致,以便正确读取表单的内容)。
  • maxUploadSize:上传文件大小上限,单位为字节。

编写前端文件上传表单。

11.3.3 编写文件 POJO 类
public class FileDomain {
    private String description;
    private MultipartFile myfile;
    /** 省略setter和getter参数*/
}

也可以直接获取MultipartFile,根据用户自己的选择。

11.3.3 编写 Controller 层
@Controller
public class FileUploadController {
    // 得到一个用来记录日志的对象,这样在打印信息时能够标记打印的是哪个类的信息
    private static final Log logger = LogFactory.getLog(FileUploadController.class);
    @RequestMapping("getFileUpload")
    public String getFileUpload() {
        return "fileUpload";
    }
    /**
     * 单文件上传
     */
    @RequestMapping("/fileupload")
    public String oneFileUpload(@ModelAttribute FileDomain fileDomain, HttpServletRequest request) {
        /*
         * 文件上传到服务器的位置“/uploadfiles”,该位置是指 workspace\.metadata\.plugins\org.eclipse
         * .wst.server.core\tmp0\wtpwebapps, 发布后使用
         */
        String realpath = request.getServletContext().getRealPath("uploadfiles");
        String fileName = fileDomain.getMyfile().getOriginalFilename();
        File targetFile = new File(realpath, fileName);
        if (!targetFile.exists()) {
            targetFile.mkdirs();
        }
        // 上传
        try {
            fileDomain.getMyfile().transferTo(targetFile);
            logger.info("成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "showFile";
    }
}

底部

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值