Spring MVC(全称 Spring Web MVC)是 Spring 框架提供的一款基于 MVC 模式的轻量级 Web 开发框架,是 Spring 为表示层(UI)开发提供的一整套完备的解决方案。Spring MVC 使用 MVC 架构模式的思想,将 Web 应用进行职责解构,把一个复杂的 Web 应用划分成模型(Model)、控制器(Contorller)以及视图(View)三层,有效地简化了 Web 应用的开发,降低了出错风险,同时也方便了开发人员之间的分工配合。
注:三层架构分为表示层(UI)、业务逻辑层(BLL)、数据访问层(DAL),表示层则包含前台页面和后台 Servlet
Spring MVC 各层的职责如下:
Model:负责对请求进行处理,并将结果返回给 Controller;
View:负责将请求的处理结果进行渲染,展示在客户端浏览器上;
Controller:是 Model 和 View 交互的纽带;主要负责接收用户请求,并调用 Model 对请求处理,然后将 Model 的处理结果传递给 View。
Spring MVC 本质是对 Servlet 的进一步封装,其最核心的组件是 DispatcherServlet,它是 Spring MVC 的前端控制器,主要负责对请求和响应的统一地处理和分发。Controller 接收到的请求其实就是 DispatcherServlet 根据一定的规则分发给它的。
Spring MVC 框架内部采用松耦合、可插拔的组件结构,具有高度可配置性,比起其他的 MVC 框架更具有扩展性和灵活性。此外,Spring MVC 的注解驱动(annotation-driven)和对 REST 风格的支持,也是它最具有特色的功能。
Spring MVC 的常用组件如下表。
组件 |
提供者 |
描述 |
DispatcherServlet |
框架提供 |
前端控制器,它是整个 Spring MVC 流程控制中心,负责统一处理请求和响应,调用其他组件对用户请求进行处理。 |
HandlerMapping |
框架提供 |
处理器映射器,根据请求的 url、method 等信息查找相应的 Handler。 |
Handler |
开发人员提供 |
处理器,通常被称为 Controller(控制器)。它可以在 DispatcherServlet 的控制下,对具体的用户请求进行处理。 |
HandlerAdapter |
框架提供 |
处理器适配器,负责调用具体的控制器方法,对用户发来的请求来进行处理。 |
ViewResolver |
框架提供 |
视图解析器,其职责是对视图进行解析,得到相应的视图对象。常见的视图解析器有 ThymeleafViewResolver、InternalResourceViewResolver 等。 |
View |
开发人员提供 |
视图,它作用是将模型(Model)数据通过页面展示给用户。 |
Spring MVC 具有以下特点:
Spring MVC 是 Spring 家族原生产品,可以与 IoC 容器等 Spring 基础设施无缝对接;
Spring MVC 支持各种视图技术,例如 JSP、Thymeleaf、 JSP 和 FreeMaker 等。
Spring MVC 基于原生的 Servlet 实现,通过功能强大的前端控制器 DispatcherServlet,对请求和响应进行统一处理;
Spring MVC 对表示层各细分领域需要解决的问题全方位覆盖,并提供一整套全面的解决方案;
代码清新简洁,大幅度提升开发效率;
内部组件化程度高,可插拔式组件即插即用,想要使用什么功能,配置相应组件即可;
性能卓著,尤其适合现代大型、超大型互联网项目的开发。
MVC 模式,全称为 Model-View-Controller(模型-视图-控制器)模式,它是一种软件架构模式,其目标是将软件的用户界面(即前台页面)和业务逻辑分离,使代码具有更高的可扩展性、可复用性、可维护性以及灵活性。
通常情况下,一个完整的 Java Web 应用程序,其结构如下图所示。
图1:Java Web 应用的结构
MVC 模式将应用程序划分成模型(Model)、视图(View)、控制器(Controller)等三层,如下图所示。
图2:MVC 模式
分层 |
描述 |
Model(模型) |
它是应用程序的主体部分,主要由以下 2 部分组成:
一个模型可以为多个视图(View)提供数据,一套模型(Model)的代码只需写一次就可以被多个视图重用,有效地减少了代码的重复性,增加了代码的可复用性。 |
View(视图) |
指在应用程序中专门用来与浏览器进行交互,展示数据的资源。在 Web 应用中,View 就是我们常说的前台页面,通常由 HTML、JSP、CSS、JavaScript 等组成。 |
Controller(控制器) |
通常指的是,应用程序的 Servlet。它负责将用户的请求交给模型(Model)层进行处理,并将 Model 层处理完成的数据,返回给视图(View)渲染并展示给用户。 在这个过程中,Controller 层不会做任何业务处理,它只是 View(视图)层和 Model (模型)层连接的枢纽,负责调度 View 层和 Model 层,将用户界面和业务逻辑合理的组织在一起,起粘合剂的效果。 |
和 MVC 模式类似,三层架构同样将系统划分成了 3 层:
表示层(UI):用来实现与用户的交互,接收用户请求,并将请求交给业务逻辑层(BLL)和数据访问层(DAL)进行处理,最后将处理结果返回给用户。
业务逻辑层(BLL):起到承上启下的作用,接收表示层传递来的请求,并针对业务对数据进行处理,以实现业务目标。
数据访问层(DAL):用于实现与数据库的交互和访问,例如从数据库中获取数据、保存或修改数据库中的数据等。
虽然三层架构和 MVC 模式一样,都是将应用划分成了 3 层,但它们的划分方式是不同的。
下图展示了三层架构的划分方式,我们可以很清楚地分辨出它与 MVC 模式的不同。
图3:三层架构
从上图可以看出,三层架构是由表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)三个层次构成的,而 MVC 则是由视图(View)层、控制(Controller)层以及模型(Model)层,且它们之间并不是一一对应的。
三层架构和 MVC 模式中各层对应关系如下:
三层架构中的表示层(UI)包含 HTML、JSP 等前台页面以及后台的 Servlet,即它相当于 MVC 模式中的 View 层 + Controller 层。
三层架构中的业务逻辑层(BLL),则只包含了 Service 接口及其实现类(Servicelmpl)的代码,即它相当于 MVC 模式中 Model 层的一部分,并不包含 Dao 和实体类。
三层架构中的数据访问层(DAL),则只包含了 Dao 接口及其实现类(DaoImpl)的代码,即它相当于 MVC 模式中 Model 层的一部分,并不包含 Service 和实体类。
三层架构将应用中的各个模块划分为表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)等三层,各层之间采用接口相互访问,并通过实体类作为数据传递的载体。不同的实体类一般对应于数据库中不同的数据表,且实体类的属性与数据库表的字段名一一对应 。
从上面的划分方式来看,三层架构和 MVC 模式确实是不一样的,但从它们的核心来看,两者又是一样的,它们的核心都是“分层、解耦”。
MVC 的工作流程如下:
用户发送请求到服务器;
在服务器中,请求被控制层(Controller)接收;
Controller 调用相应的 Model 层处理请求;
Model 层处理完毕将结果返回到 Controller;
Controller 再根据 Model 返回的请求处理结果,找到相应的 View 视图;
View 视图渲染数据后最终响应给浏览器。
MVC 模式具有以下优点:
降低代码耦合性:在 MVC 模式中,三层之间相互独立,各司其职。一旦某一层的需求发生了变化,我们就只需要更改相应层中的代码即可,而不会对其他层中的代码造成影响。
有利于分工合作:在 MVC 模式中,将应用系统划分成了三个不同的层次,可以更好地实现开发分工。例如,网页设计人员专注于视图(View)层的开发,而那些对业务熟悉的开发人员对 Model 层进行开发,其他对业务不熟悉的开发人员则可以对 Controller 层进行开发。
有利于组件的重用:在 MVC 中,多个视图(View)可以共享同一个模型(Model),大大提高了系统中代码的可重用性。
MVC 模式存在以下不足之处:
增加了系统结构和实现的复杂性:对于简单的应用,如果也严格遵循 MVC 模式,按照模型、视图与控制器对系统进行划分,无疑会增加系统结构的复杂性,并可能产生过多的更新操作,降低运行效率。
视图与控制器间的联系过于紧密:虽然视图与控制器是相互分离的,但它们之间联系却是十分紧密的。视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了它们的独立重用。
视图对模型数据的低效率访问:视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。
MVC 并不适合小型甚至中型规模的项目,花费大量时间将 MVC 应用到规模并不是很大的应用程序中,通常会得不偿失,因此对于 MVC 设计模式的使用要根据具体的应用场景来决定
Struts2是类级别的拦截,每次请求就会创建一个Action,和Spring整合时Struts2的ActionBean注入作用域是原型模式prototype,然后通过setter,getter吧request数据注入到属性。Struts2中,一个Action对应一个request,response上下文,在接收参数时,可以通过属性接收,这说明属性参数是让多个方法共享的。Struts2中Action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了,只能设计为多例。
SpringMVC是方法级别的拦截,一个方法对应一个Request上下文,所以方法直接基本上是独立的,独享request,response数据。而每个方法同时又何一个url对应,参数的传递是直接注入到方法中的,是方法所独有的。处理结果通过ModeMap返回给框架。在Spring整合时,SpringMVC的Controller Bean默认单例模式Singleton,所以默认对所有的请求,只会创建一个Controller,有应为没有共享的属性,所以是线程安全的,如果要改变默认的作用域,需要添加@Scope注解修改。
Struts2有自己的拦截Interceptor机制,SpringMVC这是用的是独立的Aop方式,这样导致Struts2的配置文件量还是比SpringMVC大。
Struts2采用Filter(StrutsPrepareAndExecuteFilter)实现,SpringMVC(DispatcherServlet)则采用Servlet实现。Filter在容器启动之后即初始化;服务停止以后坠毁,晚于Servlet。Servlet在是在调用时初始化,先于Filter调用,服务停止后销毁。
Struts2是类级别的拦截,每次请求对应实例一个新的Action,需要加载所有的属性值注入,SpringMVC实现了零配置,由于SpringMVC基于方法的拦截,有加载一次单例模式bean注入。所以,SpringMVC开发效率和性能高于Struts2。
spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高。
拦截器是基于java的反射机制的,而过滤器是基于函数回调。
拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
好文参考:https://www.cnblogs.com/lixiuming521125/p/16000192.html
好文参考:https://www.cnblogs.com/diumyself/p/16223554.html
好文参考:https://blog.csdn.net/howeres/article/details/124941519
好文参考:https://blog.csdn.net/Eaeyson/article/details/125205228
Spring MVC 执行流程如图 1 所示。
图1:Spring MVC 工作流程
SpringMVC 的执行流程如下
用户通过浏览器发起一个 HTTP 请求,该请求会被 DispatcherServlet(前端控制器)拦截;
DispatcherServlet 调用 HandlerMapping(处理器映射器)找到具体的处理器(Handler)及拦截器,最后以 HandlerExecutionChain 执行链的形式返回给 DispatcherServlet。
DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);
HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(即 Controller 控制器)对请求进行处理;
Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC 的底层对象,包括 Model 数据模型和 View 视图信息);
HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
ViewResolver 解析完成后,会将 View 视图并返回给 DispatcherServlet;
DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
视图负责将结果显示到浏览器(客户端)。
Spring MVC 的常用组件共有 6 个,它们分别是: DispatcherServlet(前端控制器)、HandlerMapping(处理器映射器)、HandlerAdapter(处理器适配器)、Handler(处理器)、ViewResolver(视图解析器)和 View(视图)。
下表对各个组件的功能说明如下
组件 |
提供者 |
说明 |
DispatcherServlet(前端控制器) |
由框架提供 |
它是 Spring MVC 的核心,其本质就是一个 Servlet。 前端控制器,它是整个 Spring MVC 流程控制中心,负责统一处理请求和响应,调用其他组件对用户请求进行处理。 它负责将所有的请求进行统一分发,相当于一个 Spring MVC 的流程控制中心,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。 |
HandlerMapping(处理器映射器) |
由框架提供 |
负责根据请求的 url、method 等信息查找响应的 Handler 处理器(即 Controller 控制器方法)。 |
Handler(处理器) |
由开发人员提供 |
也就是我们常说的 Controller 控制器,负责在 DispatcherServlet 的控制下,对具体的用户请求进行处理。 |
HandlerAdapter(处理器适配器) |
由框架提供 |
它负责根据 HandlerMapping 映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)方法。 |
ViewResolver(视图解析器) |
由框架提供 |
通过 ModelAndView 对象中的 View 信息对逻辑视图名进行解析,将其解析成真正的视图 View(例如 ThymeleafView、InternalResourceView、RedirectView 等),并返回给 DispatcherServlet。 |
View(视图) |
View 对象本身由框架提供,但视图所对应的前端页面(例如 JSP、HTML)则需要开发人员自行编写 |
将 Model 模型数据通过页面展示给用户。 |
@Controller 注解可以将一个普通的 Java 类标识成控制器(Controller)类,示例代码如下。
package net.biancheng.controller;
import org.springframework.stereotype.Controller;
@Controller
public class IndexController {
// 处理请求的方法
}
在一个类上添加@Controller注解,表明了这个类是一个控制器类,可以支持同时处理多个请求。Spring使用扫描机制查找应用程序中所有用了这个注解的控制器类。分发处理器会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping注解,而使用@RequestMapping注解的方法才是真正处理请求的处理器
注意:这里注意@Controller 只是定义了一个控制器类,而使用@RequestMapping 注解的方法才是真正处理请求的处理器。单单使用@Controller 标记在一个类上还不能真正意义上的说它就是Spring MVC 的一个控制器类,因为这个时候Spring 还不认识它。那么要如何做Spring 才能认识它呢?这个时候就需要我们把这个控制器类交给Spring 来管理。
Spring MVC 是通过组件扫描机制查找应用中的控制器类的,为了保证控制器能够被 Spring MVC 扫描到,我们还需要在 Spring MVC 的配置文件中使用
RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。
Spring MVC 的前端控制器(DispatcherServlet)拦截到用户发来的请求后,会通过 @RequestMapping 注解提供的映射信息找到对应的控制器方法,对这个请求进行处理。
@RequestMapping 既可以标注在控制器类上,也可以标注在控制器方法上。
当 @RequestMapping 注解被标注在方法上时,value 属性值就表示访问该方法的 URL 地址。当用户发送过来的请求想要访问该 Controller 下的控制器方法时,请求路径就必须与这个 value 值相同,示例代码如下。
@Controller
public class HelloController {
@RequestMapping("/login")
public String welcome() {
return "login";
}
}
当 @RequestMapping 注解标注在控制器类上时,value 属性的取值就是这个控制器类中的所有控制器方法 URL 地址的父路径。也就是说,访问这个 Controller 下的任意控制器方法都需要带上这个父路径。
@Controller
@RequestMapping(value = "/springmvc")
public class HelloController {
@RequestMapping("/login")
public String welcome() {
return "login";
}
}
例如,在上面的控制类中,用户想要访问 HelloController 中的 welcome() 方法,请求的地址就必须带上父路径“/springmvc”,即请求地址必须为“/springmvc/login”。
@RequestMapping 注解中提供了多个可用属性,下面我们就对其中几个比较常用的属性进行介绍。
在 @RequestMapping 注解中,value 属性用来设置控制器方法的请求映射地址。所有能够匹配到该请求映射地址的请求,都可以被该控制器方法处理,示例代码如下。
@RequestMapping(value = "/register")
value 属性是 @RequestMapping 注解的默认属性,如果我们在 @RequestMapping 注解中只设置了一个 value 属性,则该属性名可以被省略,示例代码如下。
//省略 value 属性名
@RequestMapping( "/register")
name 属性相当于方法的注释,用于解释这个方法是用来干什么的,使方法更易理解。
例如,下面的代码表示 getUsers() 方法是一个用来获取用户信息的控制器方法。
@RequestMapping(value = "toUser",name = "获取用户信息")
public String getUsers() {
……
}
method 属性用来设置控制器方法支持的请求方式。如果一个控制器方法没有设置 @RequestMapping 注解的 method 属性,则说明该控制器方法支持全部请求类型,可以处理所有类型的请求。
method 属性的取值是一个 RequestMethod 类型的数组,表示一个控制器方法支持多种方式的请求,常用的请求方式有 GET、POST、DELETE、PUT 等。
例如,控制器方法只支持 GET 方式的请求,代码如下。
@RequestMapping(value ="/toUser",method = RequestMethod.GET)
我们也可以为同一个控制器方法指定支持多种类型的请求。例如,一个方法既支持 GET 方式的请求,也支持 POST 方式的请求,代码如下。
@RequestMapping(value = "/toUser",method = {RequestMethod.GET,RequestMethod.POST})
params 属性用于指定请求中的参数,只有当请求中携带了符合条件的参数时,控制器方法才会对该请求进行处理。
我们可以通过以下 4 种表达式来对请求的参数进行配置。
序号 |
表达式 |
含义 |
① |
"param" |
请求中必须携带名为 param 的参数 |
② |
"!param" |
与表达式 ① 的含义完全相反,请求中不能携带名为 param 的参数 |
③ |
"param=value" |
请求中必须携带名为 param 的参数,且参数的取值必须为:value |
④ |
"param!=value" |
与表达式 ③ 的含义完全相反,请求中不能携带参数:param = value。 |
params 属性的取值是一个字符串类型的数组,表示只有请求中同时携带了 params 属性指定的全部参数时,控制器方法才会对该请求进行处理。
例如,控制器方法 testParam() 的代码如下:
@RequestMapping(value = "/testParam", params = {"name=C语言中文网", "url=http://c.bianheng.net"})
@ResponseBody
public String testParam() {
return "success";
}
以上代码表示,只有当请求中同时携带 name 和 url 两个请求参数,且参数值必须分别为 “C语言中文网” 和“http://c.biancheng.net”时,控制器方法 testParam() 才会对该请求进行处理 。
headers 属性用于设置请求中请求头信息,只有当请求中携带指定的请求头信息时,控制器方法才会处理该请求。
我们可以通过以下 4 种表达式来指定请求中的请求头信息。
序号 |
表达式 |
含义 |
① |
"header" |
请求必须携带请求头信息:header |
② |
"!header" |
与表达式 ① 的含义完全相反,请求中不能携带请求头信息:header |
③ |
"header=value" |
请求中必须携带请求头信息:header=value 。 |
④ |
"header!=value" |
与表达式 ③ 的含义完全相反,请求中不能携带请求头信息:header=value。 |
header 属性是一个字符换类型的数组,表示只有当请求同时携带数组中规定的所有头信息时,控制器方法才会对该请求进行处理。
例如,控制器方法 method() 的代码如下。
@RequestMapping(value = "toUser",headers = "Referer=http://c.biancheng.net")
public String metnod() {
……
}
在以上代码中,只有当请求的头信息中包含“Referer=http://c.biancheng.net”时,控制器方法 method() 才会处理该请求。
(1)使用model对象往前台传递数据 attribute
(2)在jsp中接收从后台传递过来的参数
(1)使用HttpServletRequest对象往前台传递数据
(2)jsp中接收,同上!!!
把Model改成Map即可。
请求转发是一种在服务器内部的资源跳转方式,简单来说,当客户浏览器发送http请求到web服务器中,web服务器接受请求后调用内部servlet方法完成请求处理和转发资源给同一个web容器下的另一资源做处理,最后将目标资源response给客户
比如图中web容器(Tomcat)有两个资源(A和B),若浏览器请求资源A的时候,资源A处理了一部分,然后跳转到资源B,让资源B接着处理,资源B处理完成后做出响应到客户端。资源A跳转到资源B的这个过程就叫转发。
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求转发器对象,调用forward()方法,请求转发
RequestDispatcher requestDispatcher = request.getRequestDispatcher("跳转资源的路径").forward(requset,response);
}
RequestDispatcher requestDispatcher = request.getRequestDispatcher("跳转资源的路径").forward(requset,response);
1. 请求转发资源间共享数据,图中资源A处理了一部分数据后把其他数据转交给资源B处理,所以在这过程中资源之间数据是共享的。
2. 浏览器地址栏路径不发生变化,只能转发到当前服务器的内部资源,浏览器只做了一次请求。在这里,转发的路径必须是同一个web容器下的url,其不能转向到其他的web路径上去,中间传递的是自己的容器内的request。在客户浏览器路径栏显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。
重定向(Redirect)是一种资源跳转的方式,一般用来解决登录进入主页、跨域访问、系统维护等等都使用重定向技术,比如当系统进行维护时,用户若发起请求,系统将会告诉浏览器重定向访问指定的url。
简单来说,当浏览器发起请求给资源A时,资源A发现浏览器发起的请求自身处理不了,但是知道资源B可以处理,这时候资源A就会告诉浏览器说这次请求处理不了,请找资源B处理并且告诉资源B的访问路径,浏览器会自动去请求资源B。
客户浏览器发送http请求后,web服务器中资源A接受请求后无法处理,资源A将会发送302状态码响应及对应新的location(资源B)给客户浏览器,客户浏览器发现是302响应,则自动再发送一个新的http请求,请求url是新的location地址。
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//旧方法
//response.setStatus(302);
//response.setHeader("location","资源B的路径");
//其中资源B的路径需要添加虚拟目录
response.sendRedirect("资源B的路径");
}
response.sendRedirect("资源B的路径");
1. 浏览器地址栏路径发生变化
2. 可以重定向到任意位置的资源(服务器内部、外部均可)
3. 浏览器发起两次请求,不能在多个资源使用request共享资源
转发和重定向是两种不同的请求处理方式,转发是服务器行为,重定向是客户端行为。
请求转发过程中,数据在同一个web服务器中得到共享,因为浏览器只做了一次访问请求,浏览器地址栏路径不发生变化,为同一个request域;而重定向中,浏览器做了两次请求,浏览器地址栏路径发生变化,请求并不是同一个request域。
一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理。如果返回的字符串中带 forward: 或 redirect: 前缀时,SpringMVC 会对他们进行特殊处理:将 forward: 和redirect: 当成指示符,其后的字符串作为 URL 来处理。
(1)转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4"
@RequestMapping("/springMvc")
@Controller
public class handler {
//测试转发
@RequestMapping("/testRedirect")
public String testRedirect(){
System.out.println("testRedirect");
return "forward:/index.jsp";
}
}
(2)重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com"
@RequestMapping("/springMvc")
@Controller
public class handler {
//测试重定向
@RequestMapping("/testRedirect")
public String testRedirect(){
System.out.println("testRedirect");
return "redirect:/index.jsp";
}
}
好文参考:https://www.cnblogs.com/bigdatalearn/p/6c1cabd5c64bf43d8dcc303e07d1fe8b.html
好文参考:https://blog.csdn.net/weixin_45007916/article/details/107880908
为什么仅仅通过一些注解,控制器方法就能够得到各种类型的参数,其实这都要归功于 Spring MVC 的类型转换机制。
Spring 提供了一种 Converter(类型转换器)的类型转换工具。在 Spring MVC 中,它的作用是在控制器方法对请求进行处理前,先获取到请求发送过来的参数,并将其转换为控制器方法指定的数据类型,然后再将转换后的参数值传递给控制器方法的形参,这样后台的控制器方法就可以正确地获取请求中携带的参数了。
Spring MVC 框架默认提供了许多内置的类型转换器,主要包括以下几种类型。
名称 |
作用 |
StringToBooleanConverter |
String 到 boolean 类型转换 |
ObjectToStringConverter |
Object 到 String 转换,调用 toString 方法转换 |
StringToNumberConverterFactory |
String 到数字转换(例如 Integer、Long 等) |
NumberToNumberConverterFactory |
数字子类型(基本类型)到数字类型(包装类型)转换 |
StringToCharacterConverter |
String 到 Character 转换,取字符串中的第一个字符 |
NumberToCharacterConverter |
数字子类型到 Character 转换 |
CharacterToNumberFactory |
Character 到数字子类型转换 |
StringToEnumConverterFactory |
String 到枚举类型转换,通过 Enum.valueOf 将字符串转换为需要的枚举类型 |
EnumToStringConverter |
枚举类型到 String 转换,返回枚举对象的 name 值 |
StringToLocaleConverter |
String 到 java.util.Locale 转换 |
PropertiesToStringConverter |
java.util.Properties 到 String 转换,默认通过 ISO-8859-1 解码 |
StringToPropertiesConverter |
String 到 java.util.Properties 转换,默认使用 ISO-8859-1 编码 |
名称 |
作用 |
ArrayToCollectionConverter |
任意数组到任意集合(List、Set)转换 |
CollectionToArrayConverter |
任意集合到任意数组转换 |
ArrayToArrayConverter |
任意数组到任意数组转换 |
CollectionToCollectionConverter |
集合之间的类型转换 |
MapToMapConverter |
Map之间的类型转换 |
ArrayToStringConverter |
任意数组到 String 转换 |
StringToArrayConverter |
字符串到数组的转换,默认通过“,”分割,且去除字符串两边的空格(trim) |
ArrayToObjectConverter |
任意数组到 Object 的转换,如果目标类型和源类型兼容,直接返回源对象;否则返回数组的第一个元素并进行类型转换 |
ObjectToArrayConverter |
Object 到单元素数组转换 |
CollectionToStringConverter |
任意集合(List、Set)到 String 转换 |
StringToCollectionConverter |
String 到集合(List、Set)转换,默认通过“,”分割,且去除字符串两边的空格(trim) |
CollectionToObjectConverter |
任意集合到任意 Object 的转换,如果目标类型和源类型兼容,直接返回源对象;否则返回集合的第一个元素并进行类型转换 |
ObjectToCollectionConverter |
Object 到单元素集合的类型转换 |
Spring MVC 对于基本类型(例如 int、long、float、double、boolean 以及 char 等)已经做好了基本类型转换。因此,通常情况下 Spring MVC 提供的这些类型转换器可以满足开发人员大多数的类型转换需求的。
注意:在使用内置类型转换器时,请求参数输入值需要与接收参数类型相兼容,否则会报 400 错误。
在实际的项目开发中,经常会涉及到一些需要进行格式化的数据,例如金额、日期等。以金额为例,当金额为 10 万元时,在比较正式的场合往往要写成 ¥100000;而日期也可以被写作多种格式,例如 yyyy-MM-dd、yyyy-MM-dd hh:ss:mm 等。这些数据都要经过一定的格式化处理才能够正常使用。
Spring 提供了一个 Formatter
Formatter 的作用与 Converter(类型转换器)相似,都是可以将一种数据类型转换成另一种数据类型。但不同的是,Formatter 的源类型必须是 String 类型,而 Converter 的源类型可以是任意数据类型。
Spring MVC 默认提供了多个内置的格式化器,通过它们,我们可以轻松地实现对日期(Date)类型和数值(Number)类型数据的格式化工作,具体如下。
内置格式化器 |
说明 |
NumberFormatter |
实现 Number 与 String 之间的解析与格式化。 |
CurrencyFormatter |
实现 Number 与 String 之间的解析与格式化(带货币符号)。 |
PercentFormatter |
实现 Number 与 String 之间的解析与格式化(带百分数符号)。 |
DateFormatter |
实现 Date 与 String 之间的解析与格式化。 |
虽然 Formatter 与 Converter 存在一定的差异,但格式化转换本质上还是属于“类型转换”的范畴,因此在 Spring MVC 中 Formatter(格式化转换器)实际上是委托给 Converter 机制实现的。
Spring 的格式化模块中定义了一个 FormattingConversionService 类,它是 ConversionService 接口(类型转换器的服务接口)的实现类。它与其他的类型转换器实现类不同,它不仅具有类型转换功能,还具有格式化转换功能。
Spring MVC 还专门为 FormattingConversionService 提供了一个名为 FormattingConversionServiceFactroyBean 的工厂类,它主要作用就是在 Spring 上下文中构造 FormattingConversionService 的实例。
除此之外,FormattingConversionServiceFactroyBean 还为以下 2 个格式化注解提供了支持,使得我们可以更加方便地使用 Spring MVC 内置的格式化器,对数据进行格式化转换。
格式化注解 |
说明 |
@DateTimeFormat 注解 |
使用该注解,能够实现对日期类型数据的格式化 |
@NumberFormat 注解 |
使用该注解,能够实现对数值类型数据的格式化 |
我们知道,只要 Spring MVC 的配置文件中配置一个
也就是说,我们只要在 Spring MVC 配置文件中配置了
@DateTimeFormat 注解可对 java.util.Date、java.util.Calendar、java.long.Long 等时间类型的数据进行标注,以实现对日期类型的数据进行格式化处理。
@DateTimeFormat 注解主要包含以下 3 个属性。
属性 |
类型 |
说明 |
pattern |
String |
用于指定解析或格式化日期时间的模式,其常用取值包括 yyyy-MM-dd、yyyy-MM-dd hh:mm:ss 等。 |
iso |
DateTimeFormat.ISO |
用于指定解析或格式化日期时间的 ISO 模式,其取值有 4 种:
|
style |
String |
用于指定日期时间的格式。 该属性由两个字符组成,第一个字符表示日期的格式,第二个字符表示时间的格式。
其默认值为“SS”,即日期和时间都采用短格式。 |
@NumberFormat 注解可以用来格式化任何数字基本类型(如 int、long 等)或 java.lang.Number 类型的实例(如 BigDecimal、Integer 等)。
@NumberFormat 注解拥有两个互斥的属性,如下表。
属性 |
类型 |
说明 |
style |
NumberFormat.Style |
该属性用于指定数值的样式类型,其取值有以下 4 种:
|
pattern |
String |
该属性用于自定义数值的样式星星,例如 #,### |
代码演示:http://c.biancheng.net/spring_mvc/9678.html
我们在进行 Spring MVC 项目开发时,一般会使用 EL 表达式和 JSTL 标签来完成页面视图的开发。其实 Spring 也有自己的一套表单标签库,通过 Spring 表单标签,可以很容易地将模型数据中的命令对象绑定到 HTML 表单元素
在使用 SpringMVC 的时候我们可以使用 Spring 封装的一系列表单标签,这些标签都可以访问到 ModelMap 中的内容。我们需要先在 JSP 中声明使用的标签,具体做法是在 JSP 文件的顶部加入以下指令:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
使用 Spring MVC 的 form 标签主要有两个作用,第一是它会自动的绑定来自 Model 中的一个属性值到当前form对应的实体对象,默认是 command 属性,这样我们就可以在 form 表单体里面方便的使用该对象的属性了。第二是它支持我们在提交表单的时候使用除 GET 和 POST 之外的其他方法进行提交,包括 DELETE 和 PUT 等。
Name:
Age:
使用
使用
使用
使用
使用
使用
使用
使用
使用
通过观察可以发现path其实就是原来的name,然后这个用的少,其实就是对原来的input写法封装,然后简化,方便使用
Spring MVC 在传递数据时,通常都需要对数据的类型和格式进行转换。而这些数据不仅可以常见的 String 类型,还可以是 JSON 等其他类型。
JSON 是近些年一种比较流行的数据格式,它与 XML 相似,也是用来存储数据的。但相较于 XML,JSON 数据占用的空间更小,解析速度更快。因此,使用 JSON 数据进行前后台的数据交互也是一种十分常见的手段。
JSON(JavaScript Object Notation,JS 对象标记)是一种轻量级的数据交互格式。与 XML 一样,JSON 也是一种基于纯文本的数据格式。通过它,我们不仅能够传递 String、Number、Boolean 等简单类型的数据,还可以传递数组、Object 对象等复杂类型的数据。
JSON 支持 2 种数据结构,它们分别是对象结构和数组结构。
JSON 的对象结构以“{”开始,以“}”结束,中间则由 0 个或多个以英文的逗号(即“,”)分隔的 key/value 对构成。
对象结构的语法结构如下。
{
key1:value1,
key2:value2,
...
}
其中,key 必须为 String 类型,value 可以是 String、Number、Object、Array 等数据类型。
例如,一个 person 对象包含姓名、密码、年龄等信息,使用 JSON 的表示形式如下:
{
"pname":"张三",
"password":"123456",
"page":40
}
JSON 的数组结构以“[”开始、以“]”结束,中间部分由 0 个或多个以英文的逗号(即“,”)分隔的值列表组成。
数组结构的语法结构如下:
{
value1,
value2,
...
}
例如,一个数组中包含了 String、Number、Boolean、null 多种类型的数据,使用 JSON 的数组结构表示形式如下。
[
"c语言中文网",
123456789,
true,
null
]
上述两种(对象、数组)数据结构也可以分别组合构成更加复杂的数据结构。
例如,一个 student 对象包含 sno、sname、hobby 和 college 等多个属性,其 JSON 表示形式如下。
{
"sno":"201802228888",
"sname":"张三",
"hobby":[
"篮球",
"足球"
],
"college":{
"cname":"清华大学",
"city":"北京",
"code":100000
}
}
为了实现浏览器与控制器类之间的 JSON 数据交互,Spring MVC 提供了一个默认的 MappingJackson2HttpMessageConverter 类,来处理 JSON 格式请求和响应。通过它,我们既可以将 Java 对象转换为 JSON 数据,也可以将 JSON 数据转换为 Java 对象。
想要使用 MappingJackson2HttpMessageConverter 对 JSON 数据进行转换,第一步就是要将 Jackson 的依赖包引入到 Spring MVC 项目中。
jackson-annotations-x.x.x.jar:JSON 转换的注解包
jackson-core-x.x.x.jar:JSON 转换的核心包
jackson-databind-x.x.x.jar:JSON 转换的数据绑定包
以上这些 Jackson 的开源依赖包都可以通过“https://mvnrepository.com/artifact/com.fasterxml.jackson.core”下载得到。
Spring MVC 为我们提供了两个十分重要的与 JSON 格式转换相关的注解,它们分别是 @RequestBody 和 @ResponseBody。
注解 |
位置 |
说明 |
@RequestBody |
方法的形参上 |
注解实现接收http请求json数据,将json转换为java对象。 |
@ResponseBody |
方法上 |
将方法的返回值,以特定的格式写入到response的body区域,进而将数据返回给客户端。 |
将方法的返回值,以特定的格式写入到response的body区域,进而将数据返回给客户端。
当方法上面没有写ResponseBody,底层会将方法的返回值封装为ModelAndView对象。
当方法上面写ResponseBody,如果返回值是字符串,那么直接将字符串写到客户端;
当方法上面写ResponseBody,如果是一个对象,会将对象转化为json串,然后写到客户端。
这里需要注意的是,如果返回对象,按utf-8编码。
如果返回String,默认按iso8859-1编码,页面可能出现乱码。因此在注解中我们可以手动修改编码格式,例如@RequestMapping(value="/cat/query",produces="text/html;charset=utf-8"),前面是请求的路径,后面是编码格式。
其实是通过HttpMessageConverter中的方法实现的,它本是一个接口,在其实现类完成转换。【Converter是转换器】
如果是bean对象,会调用对象的getXXX()方法获取属性值并且以键值对的形式进行封装,进而转化为json串。
代码演示:http://c.biancheng.net/spring_mvc/9679.html
拦截器(Interceptor)是 Spring MVC 提供的一种强大的功能组件。它可以对用户请求进行拦截,并在请求进入控制器(Controller)之前、控制器处理完请求后、甚至是渲染视图后,执行一些指定的操作。
在 Spring MVC 中,拦截器的作用与 Servlet 中的过滤器类似,它主要用于拦截用户请求并做相应的处理,例如通过拦截器,我们可以执行权限验证、记录请求信息日志、判断用户是否已登录等操作。
Spring MVC 拦截器使用的是可插拔式的设计,如果我们需要某一拦截器,只需在配置文件中启用该拦截器即可;如果不需要这个拦截器,则只要在配置文件中取消应用该拦截器即可。
想要在 Spring MVC 项目中使用拦截器,第一步就是要对拦截器类进行定义。
Spring MVC 在 org.springframework.web.servlet 包中提供了一个 HandlerInterceptor 接口,该接口包含 3 个方法,如下表。
方法名 |
返回值 |
说明 |
preHandle () |
boolean |
该方法在控制器方法之前执行,其返回值用来表示是否中断后续操作。
|
postHandle () |
void |
该方法会在控制器方法调用之后,解析式图之前执行。我们可以通过此方法对请求域中的模型(Model)数据和视图做出进一步的修改。 |
afterCompletion () |
void |
该方法会在整个请求完成后,即视图渲染结束之后执行。我们可以通过该方法实现资源清理、日志记录等工作。 |
我们可以通过实现 HandlerInterceptor 接口,重写其方法,来实现对拦截器类的定义,示例代码如下。
package net.biancheng.c.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle 执行");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle 执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion 执行");
}
}
在定义完拦截器后,我们还需要在 Spring MVC 的配置文件中使用
在 Spring MVC 的配置文件中,
标签 |
一级子标签 |
二级子标签 |
说明 |
- |
|||
- |
|||
在了解完
我们可以在 Spring MVC 的配置文件中,通过
除了
注意:
我们还可以在 Spring MVC 的配置文件中通过
需要注意的是,在
拦截器的执行流程如下图所示。
图1:单个拦截器处理流程
拦截器处理流程的步骤如下:
当请求的路径与拦截器拦截的路径相匹配时,程序会先执行拦截器类(MyInterceptor)的 preHandle() 方法。若该方法返回值为 true,则继续向下执行 Controller(控制器)中的方法,否则将不再向下执行;
控制器方法对请求进行处理;
调用拦截器的 postHandle() 方法,此时我们可以对请求域中的模型(Model)数据和视图做出进一步的修改;
通过 DispatcherServlet 的 render() 方法对视图进行渲染;
调用拦截器的 afterCompletion () 方法,完成资源清理、日志记录等工作。
在大型的企业级项目中,通常都不会只有一个拦截器,开发人员可能会定义许多不同的拦截器来实现不同的功能。在程序运行期间,拦截器的执行是有一定的顺序的,该顺序与拦截器在配置文件中定义的顺序有关。
假设一个项目中包含两个不同的拦截器:Interceptor1 和 Interceptor2,它们在配置文件中定义的顺序为:Interceptor1 → Interceptor2。下面我们就通过一个拦截器流程图来描述下多个拦截器的执行流程。
图3:多个拦截器的执行流程
从上面的执行流程图可以看出,当存在多个拦截器同时工作时,它们的 preHandle() 方法会按照拦截器在配置文件中的配置顺序执行,但它们的 PostHandle() 和 afterCompletion() 方法则会按照配置顺序的反序执行。
从控制台输出的内容可知:
由于这三个拦截器在 Spring MVC 的配置文件中是按照 MyInterceptor → MyInterceptor2 → MyInterceptor3 的顺序配置的,因此这三个拦截器的执行顺序也是 MyInterceptor → MyInterceptor2 → MyInterceptor3;
由于 MyInterceptor3 的 preHandle() 方法返回的是 false,因此 MyInterceptor3 和它之前的拦截器 MyInterceptor 和 MyInterceptor2 中的 preHandle() 都会执行。
由于 MyInterceptor3 的 preHandle() 方法返回的是 false,因此 MyInterceptor、MyInterceptor2 和 MyInterceptor3 的 postHandle() 方法都不会执行。
由于 MyInterceptor3 的 preHandle() 方法返回的是 false,因此只有 MyInterceptor 和 MyInterceptor2 的 afterCompletion() 方法执行了。
数据校验是每个项目中必不可少的模块,Spring MVC 提供了两种数据校验的组件:
基于 Validator 接口进行校验
使用 Annotation JSR-303 标准校验
使用基于 Validator 接口进行校验会复杂一些,具体的数据校验的规则需要开发者手动设置。而使用 Annotation JSR-303 标准会相对简单一些,开发者不需要编写校验规则,直接通过注解的形式给每一条数据添加校验规则,具体操作是直接在实体类的属性上添加对应的校验注解即可。
好文参考:https://blog.csdn.net/dingd1234/article/details/122793424
在实际的应用开发中,经常会不可避免地遇到各种可预知的、不可预知的异常,此时我们就需要对这些异常处理,以保证程序正常运行。
Spring MVC 提供了一个名为 HandlerExceptionResolver 的异常处理器接口,它可以对控制器方法执行过程中出现的各种异常进行处理。
Srping MVC 为 HandlerExceptionResolver 接口提供了多个不同的实现类,其中最常用的实现类如下。
DefaultHandlerExceptionResolver
ResponseStatusExceptionResolver
ExceptionHandlerExceptionResolver
SimpleMappingExceptionResolver
其中,ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver 和 DefaultHandlerExceptionResolver 是 Spring MVC 的默认异常处理器。
如果程序发生异常,Spring MVC 会按照 ExceptionHandlerExceptionResolver → ResponseStatusExceptionResolver → DefaultHandlerExceptionResolver 的顺序,依次使用这三个异常处理器对异常进行解析,直到完成对异常的解析工作为止。
DefaultHandlerExceptionResolver 是 HandlerExceptionResolver 接口的常用实现类之一,更是 Spring MVC 提供的默认异常处理器之一,Spring MVC 默认通过它对控制器处理请求时出现的异常进行处理。
DefaultHandlerExceptionResolver 提供了一个 doResolveException() 方法,其返回类型为 ModelAndView。该方法会在控制器方法出现指定异常时,生成一个新的包含了异常信息的 ModelAndView 对象替换控制器方法的 ModelAndView 对象,以达到跳转到指定的错误页面,展示异常信息的目的,其部分源码如下。
@Nullable
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
if (ex instanceof HttpRequestMethodNotSupportedException) {
return this.handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException)ex, request, response, handler);
}
if (ex instanceof HttpMediaTypeNotSupportedException) {
return this.handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException)ex, request, response, handler);
}
if (ex instanceof HttpMediaTypeNotAcceptableException) {
return this.handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException)ex, request, response, handler);
}
……
} catch (Exception var6) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", var6);
}
}
return null;
}
从上面的代码可以看出,DefaultHandlerExceptionResolver 的 doResolveException() 方法可以将 Spring MVC 产生的各种异常转换为合适的状态码(code)。通过这些状态码,我们就可以进一步的确定发生异常的原因,以便于找到对应的问题。
下表中列举了 Spring MVC 中一些常见异常的默认状态码
异常 |
状态码 |
说明 |
HttpRequestMethodNotSupportedException |
405(Method Not Allowed) |
HTTP 请求方式不支持异常 |
HttpMediaTypeNotSupportedException |
415(Unsupported Media Type) |
HTTP 媒体类型不支持异常 |
HttpMediaTypeNotAcceptableException |
406(Not Acceptable) |
HTTP 媒体类型不可接受异常 |
BindException |
400(Bad Request) |
数据绑定异常 |
MissingServletRequestParameterException |
400(Bad Request) |
缺少参数异常 |
ConversionNotSupportedException |
500(Internal Server Error) |
数据类型转换异常 |
TypeMismatchException |
400(Bad Request) |
类型不匹配异常 |
HttpMessageNotReadableException |
400(Bad Request) |
HTTP 消息不可读异常 |
HttpMessageNotWritableException |
500(Internal Server Error) |
HTTP 消息不可写异常 |
ResponseStatusExceptionResolver 也是 HandlerExceptionResolver 的实现类之一。与 DefaultHandlerExceptionResolver 一样,ResponseStatusExceptionResolver 也是 Spring MVC 提供的默认异常处理器之一,它被用来解析 @ResponseStatus 注解标注的自定义异常,并把异常的状态信息返回给客户端展示。
@ResponseStatus 注解主要用来标注在自定义的异常类上,示例代码如下。
package net.biancheng.c.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "自定义异常")
public class UserNotExistException extends RuntimeException {
}
如果程序运行时发生了这个自定义的异常,Spring MVC 就会通过 ResponseStatusExceptionResolver 对该异常进行解析,并将异常信息展示到错误页上。
@ResponseStatus 注解包含了三个属性,如下表。
属性 |
说明 |
code |
设置异常的状态码。 code 为 @ResponseStatus 注解 value 属性的别名,与 value 属性完全等价。 |
value |
设置异常的状态码。 value 为 @ResponseStatus 注解 code 属性的别名,与 code 属性完全等价。 |
reason |
设置异常的原因或描述。 |
ExceptionHandlerExceptionResolver 是 HandlerExceptionResolver 接口的实现类之一,它也是 Spring MVC 提供的默认异常处理器之一。
ExceptionHandlerExceptionResolver 可以在控制器方法出现异常时,调用相应的 @ExceptionHandler 方法(即使用了 @ExceptionHandler 注解的方法)对异常进行处理。
Spring MVC 允许我们在控制器类(Controller 类)中通过 @ExceptionHandler 注解来定义一个处理异常的方法,以实现对控制器类内发生异常的处理。
package net.biancheng.c.controller;
import net.biancheng.c.exception.UserNotExistException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ExceptionController2 {
//控制器方法
@RequestMapping(value = "/testExceptionHandler")
public String testExceptionHandler() {
//发生 ArithmeticException 异常
System.out.println(10 / 0);
return "success";
}
//使用 @ExceptionHandler 注解定义一个异常处理方法
@ExceptionHandler(ArithmeticException.class)
public String handleException(ArithmeticException exception, Model model) {
//将异常信息通过 Model 放到 request 域中,以方便在页面中展示异常信息
model.addAttribute("ex", exception);
//跳转到错误页
return "error";
}
}
@ExceptionHandler 注解中包含了一个 value 属性,我们可以通过该属性来声明一个指定的异常。如果在程序运行过程中,这个 Controller 类中的方法发生了这个指定的异常,那么 ExceptionHandlerExceptionResolver 就会调用这个 @ExceptionHandler 方法对异常进行处理。
如果我们在同一个控制器类内使用 @ExceptionHandler 注解定义了多个异常处理的方法,那么我们就需要注意下 @ExceptionHandler 方法的优先级问题。
package net.biancheng.c.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ExceptionController {
//控制器方法
@RequestMapping(value = "/testExceptionHandler")
public String testExceptionHandler() {
//发生 ArithmeticException 异常
System.out.println(10 / 0);
return "success";
}
//使用 @ExceptionHandler 注解定义一个异常处理方法
@ExceptionHandler(value = {Exception.class})
public String handleException(Exception exception, Model model) {
//将异常信息通过 Model 放到 request 域中,以方便在页面中展示异常信息
model.addAttribute("ex", exception);
//跳转到错误页
return "error";
}
@ExceptionHandler(value = {RuntimeException.class})
public String handleException2(Exception exception, Model model) {
//将异常信息通过 Model 放到 request 域中,以方便在页面中展示异常信息
model.addAttribute("ex", exception);
//跳转到错误页
return "error-2";
}
}
从上面的代码可以看出,handleException 声明的异常为 Exception,handleException 声明的异常为 RuntimeException,且 Exception 是 RuntimeException 的父类。若此时控制器方法发生了 ArithmeticException 异常,那么 ExceptionHandlerExceptionResolver 会根据继承关系,调用继承深度最浅的异常处理方法(即 handleException2 方法),对异常进行处理。
注意,定义在某个控制器类中的 @ExceptionHandler 方法只在当前的控制器中有效,它只能处理其所在控制器类中发生的异常。
我们还可以将 @ExceptionHandler 方法定义在一个使用了 @ControllerAdvice 注解的类中。使用 @ControllerAdvice 注解的类可以包含多个不同的带有 @ExceptionHandler 注解的方法,这些方法可以应用应用程序中所有带有 @RequestMapping 注解的控制器方法中,实现全局异常处理。
package net.biancheng.c.controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class ExceptionControllerAdvice {
@ExceptionHandler
public String exceptionAdvice(Exception exception, Model model) {
System.out.println("ExceptionControllerAdvice>>>>>>>>>>>>>>>>>>>");
model.addAttribute("ex", exception);
return "error-2";
}
}
RESTful(REST 风格)是一种当前比较流行的互联网软件架构模式,它充分并正确地利用 HTTP 协议的特性,为我们规定了一套统一的资源获取方式,以实现不同终端之间(客户端与服务端)的数据访问与交互。
说到 REST,我们可能会想到英文单词 rest(意为:休息、放松等),但这里的 REST 实际上是 Resource Representational State Transfer 的缩写,翻译成中文就是“表现层资源表述状态转移”。
我们可以从以下 3 个角度来理解 REST。
当我们把 Web 工程部署到服务器(例如 Tomcat)中之后,那么这个工程中的所有内容在都可以被称为这个服务器中的资源。它可以是一个类、一个 HTML 文件、一个 CSS 文件、一个 JS 文件、数据库中的一张表、一段文本、一张图片、一段音频等等,它们都可以被称为资源。而服务器则可以看作是由许许多多离散的资源组成的。
这些资源都有一个共同的特征,那就是它们都可以通过一个 URI(统一资源标识符) 进行标识,任何对于该资源的操作都不能改变其 URI。想要获取这个资源,只要访问它的 URI 即可。
资源的表述指的是资源在某个特定时刻的状态的描述,即资源的具体表现形式,它可以有多种格式,例如 HTML、XML、JSON、纯文本、图片、视频、音频等等。通常情况下,服务端与客户端资源的表述所有使用的格式往往是不同的,例如在服务端资源可能是数据库中的一段纯文本、一个 XML 文件、或者是数据库中的一张表,而客户端则可能是表现为 HTML 页面、JSON、甚至是音频和视频。
所谓的资源状态转移,简单点说就是,客户端与服务端进行交互时,资源从一种表现形式转换到另一种表现形式的过程。但是 HTTP 协议是一种无状态协议,它是无法保存任何状态的,因此如果客户端想要获取服务器上的某个资源,就必须通过某种手段让资源在服务器端发生“状态转化”,而这种状态转化又是建立在应用的表现层(UI)上的。这就是“表现层资源状态转移”的含义。
REST 实际上描述的是服务器与客户端的一种交互形式,REST 本身并不是一个实用的概念,真正实用的是如何设计 RESTFul(REST 风格)的接口,即我们到底通过什么样的手段让资源在服务器端发生状态转移。
在传统的项目开发中,我们通常都会将操作资源的动词写进 URL 中,而这些动词通常都是我们自行定义的,并没有一个统一的规范。莎士比亚说:一千个人眼中就有一个千个哈姆雷特,这句话应用在这里,再合适不过了。哪怕是对同一资源的相同操作,不同的人所定义的 URL 也是各不相同的。
例如,同样都是通过用户 ID 获取用户信息的请求,其 URL 可能是以下多种形式。
http://localhost:8080/biancheng/getUserById?id=1
http://localhost:8080/biancheng/user/getById?id=1
http://localhost:8080/biancheng/getUserInfo?id=1
http://localhost:8080/biancheng/a/b?id=1
RESTFul 提倡我们使用统一的风格来设计 URL,其规则如下。
1. URL 只用来标识和定位资源,不得包含任何与操作相关的动词。例如访问与用户(user)相关的资源时,其 URL 可以定义成以下形式。
http://localhost:8080/biancheng/user
2. 当请求中需要携带参数时,RESTFul 允许我们将参数通过斜杠(/)拼接到 URL 中,将其作为 URL 的一部分发送到服务器中,而不再像以前一样使用问号(?)拼接键值对的方式来携带参数,示例如下。
http://localhost:8080/biancheng/user/1
注:我们在 URL 的末尾通过 “/1”的形式传递了一个取值为 1 的参数。
3. HTTP 协议中有四个表示操作方式的动词:GET、POST、PUT 和 DELETE,它们分别对应了四种与资源相关的基本操作: GET 用来获取资源, POST 用来新建资源, PUT 用来更新资源, DELETE 用来删除资源。客户端通过这四个动词,即可实现对服务器端资源状态转移的描述。
资源操作 |
传统方式 URL |
RESTFul URL |
HTTP 请求方式 |
获取资源(SELECT) |
http://localhost:8080/biancheng/getUserById?id=1 |
http://localhost:8080/biancheng/user/1 |
GET |
保存或新增资源(INSERT) |
http://localhost:8080/biancheng/saveUser |
http://localhost:8080/biancheng/user |
POST |
修改或更新资源(UPDATE) |
http://localhost:8080/biancheng/updateUser |
http://localhost:8080/biancheng/user |
PUT |
删除资源(DELETE) |
http://localhost:8080/biancheng/deleteUser?id=1 |
http://localhost:8080/biancheng/user/1 |
DELETE |
事实上,Spring 4.3 之后,为了更好的支持 RESTful 风格,增加了几个注解:@PutMapping、@GetMapping、@DeleteMapping、@PostMapping,从名字也能大概的看出,其实也就是将 method 属性的值与 @RequestMapping 进行了绑定而已
在 Spring MVC 中,我们可以通过 @RequestMapping +@PathVariable 注解的方式,来实现 RESTful 风格的请求。
当请求中携带的参数是通过请求路径传递到服务器中时,我们就可以在 @RequestMapping 注解的 value 属性中通过占位符 {xxx} 来表示传递的参数,示例代码如下。
@RequestMapping("/testRest/{id}/{username}")
注意:value 属性中占位符的位置应当与请求 URL 中参数的位置保持一致,否则会出现传错参数的情况。
我们可以在控制器方法的形参位置通过 @PathVariable 注解,将占位符 {xxx} 所表示的参数赋值给指定的形参。
@RequestMapping("/testRest/{id}/{username}")
public String testRest(@PathVariable("id") String id, @PathVariable("username")
String username) {
System.out.println("id:" + id + ",username:" + username);
return "success";
}
由于浏览器默认只支持发送 GET 和 POST 方法的请求,因此我们需要在 web.xml 中使用 Spring MVC 提供的 HiddenHttpMethodFilter 对请求进行过滤。这个过滤器可以帮助我们将 POST 请求转换为 PUT 或 DELETE 请求,其具体配置内容如下。
HiddenHttpMethodFilter
org.springframework.web.filter.HiddenHttpMethodFilter
HiddenHttpMethodFilter
/*
HiddenHttpMethodFilter 处理 PUT 和 DELETE 请求时,必须满足以下 2 个条件:
当前请求的请求方式必须为 POST;
当前请求必须传输请求参数 _method。
在满足了以上条件后,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数 _method 的值,即请求参数 _method 的值才是最终的请求方式,因此我们需要在 POST 请求中携带一个名为 _method 的参数,参数值为 DELETE 或 PUT。
注意:若 web.xml 中同时存在 CharacterEncodingFilter 和 HiddenHttpMethodFilter 两个过滤器,必须先注册 CharacterEncodingFilter,再注册 HiddenHttpMethodFilter。
代码演示
/**
* 提交方式GET
* 通过学生编号stuNo获得学生信息
*/
@RequestMapping(value="/stuManager/{stuNo}", method=RequestMethod.GET)
public String getStuInfo(@PathVariable("stuNo") String stuNo, Map map){
map.put("stu", us.getStuInfo(stuNo));
//实现Service层方法获得学生信息,并添加进map返回前台
return "queStu";
}
/**
* 提交方式POST
* 添加学生信息
*/
@RequestMapping(value="/stuManager", method=RequestMethod.POST)
public String addStu(Student stu, Map map){
us.addStu(stu);
//实现Service层方法添加学生信息
map.put("msg", "学生信息添加成功");
return "addStu";
}
/**
* 提交方式PUT
* 修改学生信息
*/
@RequestMapping(value="/stuManager", method=RequestMethod.PUT)
public String updateStu(Student stu){
us.updateStu(stu);
//实现Service层方法更新学生信息
return "redirect:/stuList";
}
/**
* 提交方式DELETE
* 通过学生编号stuNo删除学生信息
*/
@RequestMapping(value="/stuManager/{stuNo}", method=RequestMethod.DELETE)
public String delStu(@PathVariable("stuNo") String stuNo){
us.delStu(stuNo);
//实现Service层方法删除学生信息
return "redirect:/stuList";
}
讲解:前端代码其实就是在表单里设置表单提交的方法为post,然后在里面加一个隐藏标签就可,后端代码可以在@RequestMapping中指定对应的方法为put或者delete或者用@PutMapping、@GetMapping、@DeleteMapping、@PostMapping注解
例如@RequestMapping(value="/stuManager", method=RequestMethod.PUT/DELETE)
在实际的项目开发中,文件的上传和下载可以说是最常用的功能之一,例如图片的上传与下载、邮件附件的上传和下载等。在 Spring MVC 中想要实现文件上传工作,需要的步骤如下。
在 Spring MVC 项目中,大多数的文件上传功能都是通过 form 表单提交到后台服务器的。
form 表单想要具有文件上传功能,其必须满足以下 3 个条件。
form 表单的 method 属性必须设置为 post。
form 表单的 enctype 属性设置为 multipart/form-data。
至少提供一个 type 属性为 file 的 input 输入框。
常见的文件上传表单示例代码如下。
当 form 表单的 enctype 属性为 multipart/form-data 时,浏览器会以二进制流的方式对表单数据进行处理,由服务端对文件上传的请求进行解析和处理。
在上面的代码中,除了满足文件上传表单所必须具备的 3 个条件外, 标签中还增加了一个 multiple 属性。该属性可以让我们同时选择对个文件进行上传,即实现多文件上传功能。
Spring MVC 提供了一个名为 MultipartResolver 的文件解析器,来实现文件上传功能。MultipartResolver 本身是一个接口,我们需要通过它的实现类来完成对它的实例化工作。
MultipartResolver 接口共有两个实现类,如下表。
实现类 |
说明 |
依赖 |
支持的 Servlet 版本 |
StandardServletMultipartResolver |
它是 Servlet 内置的上传功能。 |
不需要第三方 JAR 包的支持。 |
仅支持 Servlet 3.0 及以上版本 |
CommonsMultipartResolver |
借助 Apache 的 commons-fileupload 来完成具体的上传操作。 |
需要 Apache 的 commons-fileupload 等 JAR 包的支持。 |
不仅支持 Servlet 3.0 及以上版本,还可以在比较旧的 Servlet 版本中使用。 |
以上这两个 MultipartResolver 的实现类,无论使用哪一个都可以实现 Spring MVC 的文件上传功能。这里,我们以 CommonsMultipartResolver 为例进行讲解。
想要在 Spring MVC 中使用 CommonsMultipartResolver 对象实现文件上传,我们需要在 Spring MVC 的配置文件中对其进行以下配置。
在以上配置中,除了定义了 CommonsMultipartResolver 的 Bean 外,还通过
通过
属性 |
说明 |
defaultEncoding |
上传文件的默认编码格式。 |
maxUploadSize |
上传文件的最大长度(单位为字节)。 |
maxInMemorySize |
读取文件到内存中的最大字节数。 |
resolveLazily |
判断是否要延迟解析文件。 |
注意:当我们在 Spring MVC 的配置文件中对 CommonsMultipartResolver 的 Bean 进行定义时,必须指定这个 Bean 的 id 为 multipartResolver,否则就无法完成文件的解析和上传工作。
由于 CommonsMultipartResolver 是 Spring MVC 内部通过 Apache Commons FileUpload 技术实现的,因此我们还需要将 Apache Commons FileUpload 组件的相关依赖引入到项目中。
commons-fileupload-xx.xx.xx.jar
commons-io-x.x.x.jar
在完成上面的所有步骤后,接下来,我们只需要在 Controller 中编写文件上传的方法即可实现文件的上传。
@Controller
public class FileUploadController {
@RequestMapping("/uplaod")
public String upload(MultipartFile file) {
if (!file.isEmpty()) {
return "success";
}
return "error";
}
}
在该控制器方法中包含一个 org.springframework.web.multipart.MultipartFile 接口类型的形参,该参数用来封装被上传文件的信息。MultipartFile 接口是 InputStreamSource 的子接口,该接口中提供了多个不同的方法,如下表。
名称 |
作用 |
byte[] getBytes() |
以字节数组的形式返回文件的内容。 |
String getContentType() |
返回文件的内容类型。 |
InputStream getInputStream() |
返回一个 input 流,从中读取文件的内容。 |
String getName() |
返回请求参数的名称。 |
String getOriginalFillename() |
返回客户端提交的原始文件名称。 |
long getSize() |
返回文件的大小,单位为字节。 |
boolean isEmpty() |
判断被上传文件是否为空。 |
void transferTo(File destination) |
将上传文件保存到目标目录下。 |
代码演示:http://c.biancheng.net/spring_mvc/9683.html
文件下载的含义十分简单,它指的就是将服务器中的文件下载到本机上。
下面就结合一个实例,来演示下如何在 Spring MVC 中实现文件的下载功能,可以分为以下步骤。
1. 在《Spring MVC文件上传》中创建的 springmvc-file-demo 的工程中,修改 success.html 的代码,在每个图片下面添加一个文件下载的超链接,代码如下。
Title
学生信息上传成功
学号:
姓名:
年龄:
照片:
点击下载图片
2. 在net.biancheng.c.controller 包下新建一个名为 DownLoadController 的控制器类,代码如下。
package net.biancheng.c.controller;
import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@Controller
public class DownLoadController {
/**
* 文件下载
*
* @param request
* @param fileName
* @return
* @throws IOException
*/
@RequestMapping("/downLoadFile")
public ResponseEntity downLoadFile(HttpServletRequest request, String fileName) throws IOException {
//得到图片的实际路径
String realPath = request.getServletContext().getRealPath("/upload/" + fileName);
//创建该图片的对象
File file = new File(realPath);
//将图片数据读取到字节数组中
byte[] bytes = FileUtils.readFileToByteArray(file);
//创建 HttpHeaders 对象设置响应头信息
HttpHeaders httpHeaders = new HttpHeaders();
//设置图片下载的方式和文件名称
httpHeaders.setContentDispositionFormData("attachment", toUTF8String(fileName));
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
}
/**
* 下载保存时中文文件名的字符编码转换方法
*/
public String toUTF8String(String str) {
StringBuffer sb = new StringBuffer();
int len = str.length();
for (int i = 0; i < len; i++) {
// 取出字符中的每个字符
char c = str.charAt(i);
// Unicode码值为0~255时,不做处理
if (c >= 0 && c <= 255) {
sb.append(c);
} else { // 转换 UTF-8 编码
byte b[];
try {
b = Character.toString(c).getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
b = null;
}
// 转换为%HH的字符串形式
for (int j = 0; j < b.length; j++) {
int k = b[j];
if (k < 0) {
k &= 255;
}
sb.append("%" + Integer.toHexString(k).toUpperCase());
}
}
}
return sb.toString();
}
}
在 DownLoadController 类中共包含以下 2 个方法:
downLoadFile() 方法:负责文件的下载工作,我们首先根据文件路径和文件名称创建一个 File 对象,然后对响应头中文件的打开方式和下载方式进行了设置,并通过 ResponseEntity 对下载结果对象进行封装。
toUTF8String() 方法:负责完成中文文件名的字符编码转换。
ResponseEntity 对象与前面的章节中介绍的 @ResponseBody 注解相似,它也是用来直接返回结果对象的。