2023官方发布最新SpringMVC
创始人
2025-05-31 12:46:32

文章目录

  • 1、SpringMVC概述
    • 1.1 SpringMVC概念
    • 1.2 SpringMVC原理
    • 1.3 SpringMVC优势
  • 2、MVC模式
  • 3、入门程序
    • 3.1 创建maven项目
    • 3.2 pom.xml文件添加依赖和插件
    • 3.3 创建Spring和SpringMVC的配置文件
      • 3.3.1 创建Spring配置文件applicationContext.xml文件
      • 3.3.2 创建SpringMVC的配置文件springmvc.xml
    • 3.4 在web.xml中进行Spring和SpringMVC配置
    • 3.5 创建控制器
    • 3.6 配置视图解析器
    • 3.7 编写index.jsp页面
    • 3.8 测试
    • 3.9 解析
  • 4、SpringMVC工作流程
    • 4.1 工作流程分析
    • 4.2 SpringMVC组件
      • 1.DispatcherServlet:前端控制器,也称为中央控制器或者核心控制器。
      • 2.HandlerMapping:处理器映射器
      • 3.Handler:处理器
      • 4.HandlAdapter:处理器适配器
      • 5.ViewResolver:视图解析器
  • 5 @RequestMapping 注解
    • 5.1@RequestMapping出现的位置
    • 5.2 指定请求提交方式
    • 5.3 补充url-pattern解析
      • 5.3.1 url-pattern解析
      • 5.3.2 静态资源访问
        • 5.3.2.1使用< mvc:default-servlet-handler/ >
        • 5.3.2.2 使用 < mvc:resources/ >
  • 6、处理器方法的参数
    • 6.1 直接使用方法的参数逐个接收
    • 6.2 使用对象接收多个参数
    • 6.3 请求参数和方法名称的参数不一致
    • 6.4 使用HttpServletRequest 对象获取参数
    • 6.5 直接使用URL地址传参
    • 6.6 获取日期类型的参数
    • 6.7 获取数组类型的参数
    • 6.8 获取集合类型的参数
  • 7、请求参数中文乱码
    • 7.1 乱码解决方案
    • 7.2 解决方案原理
  • 7、处理器方法的返回值
    • 7.1 返回ModelAndView
    • 7.2 返回String
    • 7.3 返回对象类型
      • 7.3.1 返回基础类型
      • 7.3.2 返回自定义对象类型
      • 7.3.3 返回集合List
      • 7.3.4 返回集合Map
    • 7.4 无返回值 void-了解
  • 8、页面导航的方式
    • 8.1 转发到一个jsp页面
      • 8.1.1 字符串方式转发
      • 8.1.2 ModelAndView转发
    • 8.2 重定向到一个jsp页面
      • 8.2.1 字符串方式重定向
      • 8.2.2 ModelAndView重定向方式
    • 8.3 重定向或者转发到控制器
  • 9、异常处理
    • 9.1@ExceptionHandler 注解
    • 9.2 实现步骤
      • 9.2.1 自定义异常类
    • 9.3优化
  • 10、拦截器
    • 10.1 自定义拦截器
    • 10.2 配置拦截器
  • 11、文件上传和下载
    • 11.1 文件上传
    • 11.2 文件下载
  • 12、RESTful风格
    • 12.1 REST概念
    • 12.2 RESTful概念
    • 12.3 API设计/URL设计
      • 12.3.1 动词+宾语
      • 12.3.2 宾语必须是名词
      • 12.3.3 避免多级URL
    • 12.4 HTTP状态码
      • 12.4.1 状态码2xx
      • 12.4.2 状态码3xx
      • 12.4.3 状态码4xx
      • 12.4.4 状态码5xx
    • 12.5 服务器响应
    • 12.6 案例
      • 12.6.1RESTful风格的查询
      • 12.6.2RESTful风格的添加
      • 12.6.3 RESTful风格的更新
      • 12.6.4RESTful风格的删除
      • 12.6.5 RESTful风格的更新和删除遇到的问题
        • 12.6.5.1 遇到的问题:
        • 12.6.5.2 产生的原因:
        • 12.6.5.3 解决方案:
    • 12.7 自己封装响应结果
      • 12.7.1 封装的实体类
      • 12.7.2 修改控制器的返回值
      • 12.7.3 修改前端页面的脚步

1、SpringMVC概述

1.1 SpringMVC概念

SpringMVC 也叫 Spring web mvc。是 Spring内置的一个MVC框架,在 Spring3.0 后发布。SpringMVC框架解决了WEB开发中常见的问题(参数接收、文件上传、表单验证等等),而且使用简单,与Spring无缝集成。支持 RESTful风格的URL请求。采用了松散耦合可插拔组件结构,比其他 MVC 框架更具扩展性和灵活性。

1.2 SpringMVC原理

在没有使用SpringMVC之前我们都是使用Servlet在做Web开发。但是使用Servlet开发在接收请求参数,数据共享,页面跳转等操作相对比较复杂。servlet是java进行web开发的标准,既然springMVC是对servlet的封装,那么很显然SpringMVC底层就是Servlet,SpringMVC就是对Servlet进行深层次的封装。

1.3 SpringMVC优势

1、基于 MVC 架构,功能分工明确。解决页面代码和后台代码的分离。
2、简单易用。SpringMVC 也是轻量级的,jar 很小。不依赖的特定的接口和类就可以开发一个注解的SpringMVC 项目。
3、作 为 Spring 框 架 一 部 分 , 能 够 使 用Spring的IoC和AOP 。 方 便 整 合MyBatis,Hiberate,JPA等其他框架。
4、springMVC的注解强大易用。

2、MVC模式

模型1:jsp+javabean模型—在jsp页面中嵌入大量的java代码
模型2:jsp+servlet+javabean模型—jsp页面将请求发送给servlet,由servlet调用javabean,再由servlet将制定jsp页面响应给用户。

模型2一般就是现在的MVC模式,也是我们一直使用的。
Model-View-Controller:模型–视图–控制器
Model: 模型层 javaBean 负责数据访问和业务处理 dao service pojo
View: 视图 JSP技术 负责收集和展示数据
Controller: 控制器 servlet技术 中间调度

控制器的工作:
1、接受客户端的请求(包括请求中携带的数据)
2、处理请求:调用后台的模型层中的业务逻辑
3、页面导航:处理完毕给出响应:JSP页面

3、入门程序

3.1 创建maven项目

创建项目并补齐目录结构在这里插入图片描述

3.2 pom.xml文件添加依赖和插件


warorg.springframeworkspring-webmvc5.2.13.RELEASEjavax.servletjavax.servlet-api4.0.1providedorg.apache.maven.pluginsmaven-compiler-plugin3.8.01.81.8org.apache.tomcat.maventomcat7-maven-plugin2.2/8080

3.3 创建Spring和SpringMVC的配置文件

我们一般将除了 Controller 之外的所有 Bean 注册到 Spring 容器中,而将 Controller 注册到SpringMVC 容器中。所以我们在 resources 目录下添加 applicationContext.xml 作为 spring 的配置,添加springmvc.xml作为springmvc的配置文件。

3.3.1 创建Spring配置文件applicationContext.xml文件




3.3.2 创建SpringMVC的配置文件springmvc.xml




3.4 在web.xml中进行Spring和SpringMVC配置


contextConfigLocationclasspath:applicationContext.xmlorg.springframework.web.context.ContextLoaderListener

dispatcherServletorg.springframework.web.servlet.DispatcherServletcontextConfigLocationclasspath:springmvc.xml1
dispatcherServlet*.do

3.5 创建控制器

package com.kkb.controller;
import com.kkb.service.TeamService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/**
* ClassName: TeamController
*
* @author wanglina
* @version 1.0
*/
@Controller
public class TeamController {@Autowiredprivate TeamService teamService;@RequestMapping("hello.do")public ModelAndView add(){System.out.println("TeamController----add---");teamService.add();ModelAndView mv=new ModelAndView();mv.addObject("teamName","湖人");//相当于
request.setAttrubuite("teanName","湖人");mv.setViewName("index");//未来经过springmvc的视图解析器处理,转换成物理资源路径,
相当于request.getRequestDispatcher("index.jsp").forward();//经过InternalResourceViewResolver对象的处理之后加上前后缀就变为了 
/jsp/index.jspreturn mv;}
}
package com.kkb.service;
import org.springframework.stereotype.Service;
@Service
public class TeamService {public void add(){System.out.println("TeamService---- add-----");}
}

3.6 配置视图解析器

在springmvc.xml配置文件中添加视图解析器的配置




3.7 编写index.jsp页面

webapp文件夹下面创建文件夹jsp,然后jsp文件夹中添加index.jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

index

index---------------${teamName}

3.8 测试

在这里插入图片描述
将刚刚的地址复制到浏览器之后追加控制中add方法的访问路径hello.do,看到如下页面表示运行成功!
在这里插入图片描述

3.9 解析

当 Spring 和 SpringMVC 同时出现,我们的项目中将存在两个容器,一个是 Spring 容器,另一个是SpringMVC 容器,Spring 容器通过 ContextLoaderListener 来加载,SpringMVC 容器则通过DispatcherServlet 来加载,这两个容器不一样:在这里插入图片描述
如图所示:

  • ContextLoaderListener 初始化的上下文加载的 Bean 是对于整个应用程序共享的,不管是使用什么表现层技术,一般如 dao层、service层 的bean;
  • DispatcherServlet 初始化的上下文加载的 bean 是只对 Spring Web MVC 有效的 bean,如Controller、HandlerMapping、HandlerAdapter 等等,该初始化上下文应该只加载 Web相关组件。

1.Spring容器中不能扫描所有Bean嘛?
不可以。当用户发送的请求达到服务端后,会寻找前端控制器DispatcherServlet去处理,只在SpringMVC容器中找,所以Controller必须在SpringMVC容器中扫描。
2.SpringMVC容器中可以扫描所有Bean嘛?
可以。可以在SpringMVC容器中扫描所有Bean。但是实际开发中一般不会这么做,原因如下:
(1)为了方便配置文件的管理
(2)未来在 Spring+SpringMVC+Mybatis组合中,要写的配置内容很多,一般都会根据功能分开编写

4、SpringMVC工作流程

4.1 工作流程分析

在这里插入图片描述
(1)用户通过浏览器发送请求到前端控制器DispatcherServlet。
(2)前端控制器直接将请求转给处理器映射器HandleMapping。
(3)处理器映射器HandleMapping会根据请求,找到负责处理该请求的处理器,并将其封装为处理器执行链HandlerExecutionChina后返回给前端控制器DispatcherServlet。
(4)前端控制器DispatcherServlet根据处理器执行链中的处理器,找到能够执行该处理器的处理器适配器HandlerAdaptor。
(5)处理器适配器HandlerAdaptor调用执行处理器Controller。
(6)处理器Controller将处理结果及要跳转的视图封装到一个对象 ModelAndView 中,并将其返回给处理器适配器HandlerAdaptor。
(7)处理器适配器直接将结果返回给前端控制器DispatcherServlet。
(8)前端控制器调用视图解析器,将 ModelAndView 中的视图名称封装为视图对象。
(9)视图解析器ViewResolver将封装了的视图View对象返回给前端控制器DispatcherServlet.
(10)前端控制器DispatcherServlet调用视图对象,让其自己进行渲染,即进行数据填充,形成响应对象。
(11)前端控制器响应浏览器。

4.2 SpringMVC组件

1.DispatcherServlet:前端控制器,也称为中央控制器或者核心控制器。

用户请求的入口控制器,它就相当于 mvc 模式中的c,DispatcherServlet 是整个流程控制的中心,相当于是 SpringMVC 的大脑,由它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。SpringMVC框架提供的该核心控制器需要我们在web.xml文件中配置。

2.HandlerMapping:处理器映射器

HandlerMapping也是控制器,派发请求的控制器。我们不需要自己控制该类,但是他是springmvc运转历程中的重要的一个控制器。 HandlerMapping负责根据用户请求找到 Handler 即处理器(也就是我们所说的 Controller),SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等,在实际开发中,我们常用的方式是注解方式。

3.Handler:处理器

Handler 是继 DispatcherServlet 前端控制器的后端控制器,在DispatcherServlet 的控制下 Handler 对具体的用户请求进行处理。由于 Handler 涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发 Handler。(这里所说的 Handler 就是指我们的 Controller)

4.HandlAdapter:处理器适配器

通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展处理器适配器,支持更多类型的处理器,调用处理器传递参数等工作。

5.ViewResolver:视图解析器

ViewResolver 负责将处理结果生成 View 视图,ViewResolver 首先根据逻辑视图名解析成物理视图名称,即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。 SpringMVC 框架提供了很多的 View 视图类型,包括:jstlView、freemarkerView、pdfView 等。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。

5 @RequestMapping 注解

5.1@RequestMapping出现的位置

过@RequestMapping 注解定义了处理器对于请求的映射规则。该注解可以定义在类上,也可以定义在方法上,但是含义不同。
在这里插入图片描述
一个@Controller 所注解的类中,可以定义多个处理器方法。当然,不同的处理器方法所匹配的 URI 是不同的。这些不同的 URI 被指定在注解于方法之上的@RequestMapping 的value 属性中。但若这些请求具有相同的 URI 部分,则这些相同的 URI,可以被抽取到注解在类之上的RequestMapping 的 value属性中。此时的这个 URI 表示模块的名称。URI 的请求是相对于 Web 的根目录。在类的级别上的注解会将一个特定请求或者请求模式映射到一个控制器之上。之后你还可以另外添加方法级别的注解来进一步指定到处理方法的映射关系。
示例:修改TeamController.java 如下

package com.kkb.controller;
import com.kkb.service.TeamService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
/**
* ClassName: TeamController
*
* @author wanglina
* @version 1.0
*/
@Controller
@RequestMapping("team")
public class TeamController {@Autowiredprivate TeamService teamService;@RequestMapping(value = "add.do")public ModelAndView addTeam(){System.out.println("TeamController----addTeam---");ModelAndView mv=new ModelAndView();mv.setViewName("team/add");//  映射成为物理资源路径:/jsp/team/add.jspreturn mv;}@RequestMapping(value = "update.do")public ModelAndView updateTeam(){System.out.println("TeamController----updateTeam---");ModelAndView mv=new ModelAndView();mv.setViewName("team/update");//映射成为物理资源路径:/jsp/team/update.jspreturn mv;}@RequestMapping("hello.do")public ModelAndView hello(){System.out.println("TeamController----add---");teamService.add();ModelAndView mv=new ModelAndView();mv.addObject("teamName","湖人");//相当于request.setAttrubuite("teanName","湖人");mv.setViewName("index");//未来经过springmvc的视图解析器处理,转换成物理资源路径,相当于request.getRequestDispatcher("index.jsp").forward();//经过InternalResourceViewResolver对象的处理之后加上前后缀就变为了 
/jsp/index.jspreturn mv;}
}

在这里插入图片描述
浏览器中访问:
在这里插入图片描述

5.2 指定请求提交方式

@RequestMapping的method属性,用来对被注解方法所处理请求的提交方式进行限制,即只有满足method 属性指定的提交方式的请求,才会执行该被注解方法。
Method 属性的取值为 RequestMethod 枚举常量。常用的为 RequestMethod.GET 与
RequestMethod.POST,分别表示提交方式的匹配规则为 GET 与 POST 提交。在这里插入图片描述
在这里插入图片描述
以下表格列出了常用的提交方式:

请求方式提交方式
地址栏请求get请求
超链接请求get请求
表单请求默认get,可以指定post
AJAX请求默认get,可以指定post

5.3 补充url-pattern解析

5.3.1 url-pattern解析

在web.xml配置SpringMVC的前端控制器时有这个节点。这个节点中的值一般有两种写法:

1、.do
在没有特殊要求的情况下,SpringMVC 的前端控制器 DispatcherServlet 的常使用后辍匹配方式,可以写为
.do 或者 *.action, *.mvc 等。
2、/
可以写为/,但是 DispatcherServlet 会将向静态内容–例如.css、.js、图片等资源的获取请求时,也会当作是一个普通的 Controller 请求。前端控制器会调用处理器映射器为其查找相应的处理器。肯定找不到啊,所以所有的静态资源获取请求也均会报 404 错误。

案例:在index.jsp页面添加一张图片,如果节点中的值为*.do,图片可以正常访问,但是如果为/就不能访问。
1、项目中添加图片,同时修改index.jsp页面在这里插入图片描述2、修改web.xml
在这里插入图片描述3、此时访问图片无法显示

5.3.2 静态资源访问

如果的值配置为/后,静态资源可以通过以下两种方法解决。

5.3.2.1使用< mvc:default-servlet-handler/ >

在springmvc的配置文件中添加如下内容:

mvc:default-servlet-handler/

在这里插入图片描述

5.3.2.2 使用 < mvc:resources/ >

在springmvc的配置文件中添加如下内容:

在 Spring3.0 版本后,Spring 定义了专门用于处理静态资源访问请求的处理器
ResourceHttpRequestHandler。并且添加了< mvc:resources/ >标签,专门用于解决静态资源无法访
问问题。

6、处理器方法的参数

处理器方法可以包含以下四类参数,这些参数会在系统调用时由系统自动赋值.所以我们可以在方法内直接使用。以下是这四类参数:

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • 请求中所携带的请求参数

准备工作:创建新的控制器ParamController.java和前端页面hello.jsp页面

package com.kkb.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("param")
public class ParamController {@RequestMapping("hello")public  ModelAndView hello(){return new ModelAndView("hello);}
}

6.1 直接使用方法的参数逐个接收

6.2 使用对象接收多个参数

6.3 请求参数和方法名称的参数不一致

6.4 使用HttpServletRequest 对象获取参数

6.5 直接使用URL地址传参

6.6 获取日期类型的参数

6.7 获取数组类型的参数

6.8 获取集合类型的参数

(具体内容在代码中体现)
SpringMVC不支持直接从参数中获取对象集合类型,需要将对象集合封装到实体类中。
案例源码:
修改实体类Team.java

package com.kkb.pojo;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
public class Team {private Integer teamId;private String teamName;private String location;@DateTimeFormat(pattern = "yyyy-MM-dd")private Date createTime;@Overridepublic String toString() {return "Team{" +"teamId=" + teamId +", teamName='" + teamName + '\'' +", location='" + location + '\'' +", createTime=" + createTime +'}';}
//省略set get方法
}

创建实体类QueryVO.java

package com.kkb.vo;
import com.kkb.pojo.Team;
import java.util.List;
public class QueryVO {private List teamList;public List getTeamList() {return teamList;}public void setTeamList(List teamList) {this.teamList = teamList;}
}

前端页面hello.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

hello

8、获取集合类型的参数

球队名称1:
球队名称2:
球队名称3:
球队id1:
球队id2:
球队id3:
球队名称1:
球队名称2:
球队名称3:

7、获取数组类型的参数

球队名称1:
球队名称2:
球队名称3:

6、获取日期类型的参数

球队id:
球队名称:
球队位置:
创建日期:

5、直接使用URL地址传参

4、使用HttpServletRequest 对象获取参数

球队id:
球队名称:
球队位置:

3、请求参数和方法名称的参数不一致

球队id:
球队名称:
球队位置:

2、使用对象接收多个参数

球队id:
球队名称:
球队位置:

1、直接使用方法的参数逐个接收

球队id:
球队名称:
球队位置:

控制器ParamController.java

package com.kkb.controller;
import com.kkb.pojo.Team;
import com.kkb.vo.QueryVO;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@Controller
@RequestMapping("param")
public class ParamController {//8、获取集合类型的参数: 简单类型的可以通过@RequestParam注解实现;对象集合不支持直接获取,必须封装在类中,作为一个属性操作@RequestMapping("test08")public ModelAndView test08(@RequestParam("teamName") List nameList){System.out.println("test08-----------------");for (String s : nameList) {System.out.println(s);}return new ModelAndView("ok");}@RequestMapping("test09")public ModelAndView test09(QueryVO vo){System.out.println("test09-----------------");for (Team team : vo.getTeamList()) {System.out.println(team);}return new ModelAndView("ok");}//7、获取数组类型的参数@RequestMapping("test07")public ModelAndView test07(String[] teamName,HttpServletRequest request){System.out.println("test07-----------------");//方式1:for (String s : teamName) {System.out.println(s);}System.out.println("---------------");//方式2:String[] teamNames = request.getParameterValues("teamName");for (String name : teamNames) {System.out.println(name);}return new ModelAndView("ok");}//6、获取日期类型的参数@RequestMapping("test06")public ModelAndView test06(Team team){System.out.println("test06-----------------");System.out.println(team);return new ModelAndView("ok");}//5、直接使用URL地址传参: 借助@PathVariable 注解// 例如http://localhost:8080/param/test05/1001/lacker/las@RequestMapping("test05/{id}/{name}/{loc}")public ModelAndView test05(@PathVariable("id") Integer teamId,@PathVariable("name") String teamName,@PathVariable("loc") String teamLocation){System.out.println("test05-----------------");System.out.println(teamId);System.out.println(teamName);System.out.println(teamLocation);return new ModelAndView("ok");}//4、使用HttpServletRequest 对象获取参数:跟原来的javaWeb项目中使用的方式是一样的@RequestMapping("test04")public ModelAndView test04(HttpServletRequest request){System.out.println("test04-----------------");String teamId = request.getParameter("teamId");String teamName = request.getParameter("teamName");String location = request.getParameter("location");if(teamId!=null)System.out.println(Integer.valueOf(teamId));System.out.println(teamName);System.out.println(location);return new ModelAndView("ok");}//3、请求参数和方法名称的参数不一致:使用@RequestParam进行矫正,// value属性表示请求中的参数名称// required属性表示参数是否是必须的:true:必须赋值,否则报出400错;false:可以不赋值,
结果就是null@RequestMapping("test03")public ModelAndView test03(@RequestParam(value = "teamId",required = false)
Integer id,@RequestParam(value = "teamName",required = true)
String name,@RequestParam("location") String loc){System.out.println("test03-----------------");System.out.println(id);System.out.println(name);System.out.println(loc);return new ModelAndView("ok");}//2、使用对象接收多个参数:要求用户请求中携带的参数名称必须是实体类中的属性保持一致,否则就获取不到@RequestMapping("test02")public ModelAndView test02(Team team){System.out.println("test02-----------------");System.out.println(team);return new ModelAndView("ok");}/*** 1、直接使用方法的参数逐个接收:方法的参数名称必须与用户请求中携带的参数名称保持一致,否则
就获取不到* 好处:不需要类型转换*/@RequestMapping("test01")public ModelAndView test01(Integer teamId,String teamName,String
teamLocation){System.out.println("test01-----------------");System.out.println(teamId);System.out.println(teamName);System.out.println(teamLocation);return new ModelAndView("ok");}@RequestMapping("hello")public  ModelAndView hello(){return new ModelAndView("hello");}
}

7、请求参数中文乱码

对于前面所接收的请求参数,若含有中文,则会出现中文乱码问题。Spring 对于请求参数中的中文乱码问题,给出了专门的字符集过滤器CharacterEncodingFilter 类。如图所示。在这里插入图片描述

7.1 乱码解决方案

在 web.xml 中注册字符集过滤器,推荐将该过滤器注册在其它过滤器之前。因为过滤器的执行是按照其注册顺序进行的。
在web.xml配置文件直接注册字符集

characterEncodingFilterorg.springframework.web.filter.CharacterEncodingFilterencodingUTF-8forceRequestEncodingtrueforceResponseEncodingtruecharacterEncodingFilter/*

7.2 解决方案原理

在这里插入图片描述

7、处理器方法的返回值

使用@Controller 注解的处理器的处理器方法,其返回值常用的有四种类型:

  1. ModelAndView
  2. String
  3. 返回自定义类型对象
  4. 无返回值 void

咱们根据实际的业务选择不同的返回值。
案例准备工作:
创建控制器ResultController.java

package com.kkb.controller;
import com.kkb.pojo.Team;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("result")
public class ResultController {
}

在jsp文件夹中添加页面result.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

result


result----------------

7.1 返回ModelAndView

如果是前后端不分的开发,大部分情况下,我们返回 ModelAndView,即数据模型+视图:
控制器ResultController.java中添加方法:

//1、返回值是ModelAndView: 这种方式既有数据的携带还有资源的跳转,可以选择该种方式@RequestMapping("test01")public ModelAndView test01(){ModelAndView mv=new ModelAndView();//模型与视图//携带数据mv.addObject("teamName","湖人队");//相当于request。
setAttribute("teamName","湖人队“);mv.setViewName("result");// 经过视图解析器InternalResourceViewResolver的处理,将逻辑视图名称加上前后缀变为物理资源路径 /jsp/result.jspreturn mv;}

Model 中,放我们的数据,然后在 ModelAndView 中指定视图名称。
当处理器方法处理完后,需要跳转到其它资源的同时传递数据,选择返回 ModelAndView 比较好,但是如果只是需要传递数据或者跳转之一,这个时候ModelAndView 就不是最优选择。
result.jsp页面中添加如下内容:在这里插入图片描述

7.2 返回String

上一种方式中的 ModelAndView 可以拆分为两部分,Model 和 View,在 SpringMVC 中,Model 我们可以直接在参数中指定,然后返回值是逻辑视图名,视图解析器解析可以将逻辑视图名称转换为物理视图地址。
在这里插入图片描述视图解析器通过内部资源视图解析器InternalResourceViewResolver将字符串与解析器中的prefix和
suffix结合形成要跳转的额URI。
控制器ResultController.java中添加方法:

//2、返回字符串@RequestMapping("test02")public String test02(HttpServletRequest request){Team team=new Team();team.setLocation("迈阿密");team.setTeamId(1002);team.setTeamName("热火");//携带数据request.setAttribute("team",team);request.getSession().setAttribute("team",team);//资源的跳转return "result";// 经过视图解析器InternalResourceViewResolver的处理,将逻辑视图名称加上前后缀变为物理资源路径 /jsp/result.jsp}

result.jsp页面中添加如下内容:

test02---request作用域获取:---${requestScope.team.teamName}-- -${requestScope.team.teamId}---${requestScope.team.location}

test02---session作用域获取:---${sessionScope.team.teamName}-- -${sessionScope.team.teamId}---${sessionScope.team.location}

在这里插入图片描述

7.3 返回对象类型

当处理器方法返回Object对象类型的时候,可以是Integer、String、Map、List,也可以是自定义的对
象类型。但是无论是什么类型,都不是作为逻辑视图出现,而是直接作为数据返回然后展示的。一般前端发起Ajax请求的时候都会使用直接返回对象的形式。
返回对象的时候,需要使用@ResponseBody 注解,将转换后的 JSON 数据放入到响应体中。

pom.xml文件中添加两个依赖


com.fasterxml.jackson.core
jackson-core2.9.0

com.fasterxml.jackson.corejackson-databind2.9.0

7.3.1 返回基础类型

//3、返回对象类型:Integer Double String 自定义类型  List Map 返回的不是逻辑视图的名称,而直接就是数据返回 ,一般是ajax请求搭配使用 ,将json格式的数据直接返回给响应体// 一定要与@ResponseBody@ResponseBody@RequestMapping("test03-1")public Integer test031(){return 666;}@ResponseBody@RequestMapping("test03-2")public String test032(){return "test";}

7.3.2 返回自定义对象类型

@ResponseBody@RequestMapping("test03-3")public Team test033(){Team team=new Team();team.setLocation("迈阿密");team.setTeamId(1002);team.setTeamName("热火");return team;}

ajax请求自定义对象的结果展示

在这里插入图片描述

7.3.3 返回集合List

@ResponseBody@RequestMapping("test03-4")public List test034(){List list=new ArrayList<>(5);for(int i=1;i<=5;i++) {Team team = new Team();team.setLocation("迈阿密"+i);team.setTeamId(1002+i);team.setTeamName("热火"+i);list.add(team);}return list;}

ajax请求自定义对象的结果展示

在这里插入图片描述

7.3.4 返回集合Map

@ResponseBody@RequestMapping("test03-5")public Map test035(){Map map=new HashMap();for(int i=1;i<=5;i++) {Team team = new Team();team.setLocation("金州"+i);team.setTeamId(1000+i);team.setTeamName("勇士"+i);//日期类型,在返回的时候是个数字,如果想要按日期格式展示需要在实体类对应属性添加注解@JsonFormat(pattern = "yyyy-MM-dd")team.setCreateTime(new Date());map.put(team.getTeamId()+"",team);}return map;}

ajax请求Map的结果展示

在这里插入图片描述

7.4 无返回值 void-了解

方法的返回值为 void,并不一定真的没有返回值,我们可以通过其他方式给前端返回。实际上,这种方式也可以理解为 Servlet 中的的处理方案。

//通过 HttpServletRequest 做服务端跳转
@RequestMapping("test04-1")public void test041(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {System.out.println("直接使用HttpServletRequest进行服务器端的转发");request.getRequestDispatcher("/jsp/ok.jsp").forward(request,response);}
//通过 HttpServletResponse 做重定向
@RequestMapping("test04-2")public void test042(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("直接使用HttpServletResponse重定向跳转");response.sendRedirect("/jsp/ok.jsp");
}
//通过 HttpServletResponse 给出响应
@RequestMapping("test04-3")
public void test043(HttpServletResponse response) throws ServletException,IOException {response.setCharacterEncoding("UTF-8");response.setContentType("text/html;charset=UTF-8");PrintWriter writer = response.getWriter();writer.write("返回void类型测试---直接返回字符串");writer.flush();writer.close();
}
//也可以自己手动指定响应头去实现重定向:
@RequestMapping("test04-4")public void test044(HttpServletResponse response) throws ServletException,IOException {response.setStatus(302);//设置响应码,302表示重定向response.setHeader("Location","/jsp/ok.jsp");
}

8、页面导航的方式

页面导航分为两种:1、转发 2、重定向
springMVC有以下两种方式实现页面的转发或重定向:

  • 1、返回字符串
  • 2、使用ModelAndView

在SpringMVC中两种导航进行页面导航的时候使用不同的前缀指定转发还是重定向前缀:
转发: forward:url 默认 地址栏不会改变,数据会转发到新的页面
重定向: redirect:url 地址栏会改变,数据不会转发到新页面

准备工作:创建一个新的控制器NavigationController.java:

package com.kkb.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
/**
* ClassName: NavigationController
* SpringMVC导航的方式
* @author wanglina
* @version 1.0
*/
@Controller
@RequestMapping("navigation")
public class NavigationController {
}

8.1 转发到一个jsp页面

8.1.1 字符串方式转发

@RequestMapping("test01-1")public String test011(HttpServletRequest request){request.setAttribute("teamName","湖人");//return "ok";//默认方式:由视图解析器处理之后将逻辑视图转为物理资源路径return "forward:/jsp/ok.jsp";//当添加了forward前缀之后,视图解析器中的前后缀就失效了,必须自己编写绝对路径
}

8.1.2 ModelAndView转发

@RequestMapping("test01-2")public ModelAndView test012(){ModelAndView mv=new ModelAndView();mv.addObject("teamName","热火");//mv.setViewName("ok");//默认方式:由视图解析器处理之后将逻辑视图转为物理资源路径mv.setViewName( "forward:/jsp/ok.jsp");//当添加了forward前缀之后,视图解析器中的前后缀就失效了,必须自己编写绝对路径return mv;
}

8.2 重定向到一个jsp页面

8.2.1 字符串方式重定向

@RequestMapping("test02-1")public String test021(HttpServletRequest request){request.setAttribute("teamName","勇士");//页面上无法获取到存储在request作用域中的值,请求中断了return "redirect:/jsp/ok.jsp";//当添加了redirect前缀之后,视图解析器中的前后缀就失效了,必须自己编写绝对路径
}

8.2.2 ModelAndView重定向方式

@RequestMapping("test02-2")public ModelAndView test022(){ModelAndView mv=new ModelAndView();mv.addObject("teamName","huangfeng");//存储在request作用域中的值以参数的形式追加在URL后面http://localhost:8080/jsp/ok.jsp?teamName=huangfeng&teamId=1002mv.addObject("teamId","1002");mv.setViewName( "redirect:/jsp/ok.jsp");//当添加了redirect前缀之后,视图解析器中的前后缀就失效了,必须自己编写绝对路径return mv;
}

8.3 重定向或者转发到控制器

@RequestMapping("test03-1")public ModelAndView test031(HttpServletRequest request){System.out.println("test03-1---转发到控制器");ModelAndView mv=new ModelAndView();mv.addObject("teamName","达拉斯小牛");mv.setViewName("forward:/navigation/test01-1");return mv;
}@RequestMapping("test03-2")public ModelAndView test032(HttpServletRequest request){System.out.println("test03-1---重定向到控制器");ModelAndView mv=new ModelAndView();mv.addObject("teamName","kaierteren");mv.addObject("teamId","1003");mv.setViewName("redirect:/navigation/test01-1");//参数值直接追加到URL后面return mv;
}
@RequestMapping("test")
public String test(){System.out.println("test-----------------");return "index";
}

9、异常处理

SpringMVC框架常用@ExceptionHandler 注解处理异常。

9.1@ExceptionHandler 注解

@ExceptionHandler 可以将一个方法指定为异常处理方法。
被注解的方法,其返回值可以是 ModelAndView、String,或 void,方法名随意,方法参数可以是Exception 及其子类对象、HttpServletRequest、HttpServletResponse 等。系统会自动为这些方法参
数赋值。
对于异常处理注解的用法,也可以直接将异常处理方法注解于 Controller 之中.
在这里插入图片描述

9.2 实现步骤

9.2.1 自定义异常类

package com.kkb.exceptions;
/**
* ClassName: TeamException
* 自定义的异常类
* @author wanglina
* * @version 1.0
*/
public class TeamException extends Exception{public TeamException() {}public TeamException(String message) {super(message);}
}
package com.kkb.exceptions;
/**
* ClassName: TeamIdException
*自定义的异常类
* @author wanglina
* @version 1.0
*/
public class TeamIdException extends TeamException{public TeamIdException() {
}public TeamIdException(String message) {super(message);}
}
package com.kkb.exceptions;
/**
* ClassName: TeamException
* 自定义的异常类
* @author wanglina
* @version 1.0
*/
public class TeamNameException extends  TeamException{public TeamNameException() {
}public TeamNameException(String message) {super(message);}
}

编写控制器

package com.kkb.controller;
import com.kkb.exceptions.TeamException;
import com.kkb.exceptions.TeamIdException;
import com.kkb.exceptions.TeamNameException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/**
* ClassName: ExController
* 测试异常处理的控制器
* @author wanglina
* @version 1.0
*/
@Controller
@RequestMapping("ex")
public class ExController {
@RequestMapping("test01/{id}/{name}")
public ModelAndView test01(@PathVariable("id") Integer teamId,@PathVariable("name") String teamName) throws TeamIdException,TeamNameException{ModelAndView mv=new ModelAndView();if(teamId<=1000){throw new TeamIdException("teamId不合法!必须在1000之上!");//抛出异常给异常处理方法处理}if("test".equals(teamName)){throw  new TeamNameException("teamName不合法!不能使用test!"); }System.out.println(10/0);mv.setViewName("ok");return mv; }@ExceptionHandler(value = //接收能够处理的异常,然后在此方法中处理{TeamIdException.class,TeamNameException.class,Exception.class})public ModelAndView exHandler(Exception ex){ModelAndView mv=new ModelAndView();mv.addObject("msg",ex.getMessage());if(ex instanceof TeamIdException)mv.setViewName("idError");else if(ex instanceof TeamNameException)mv.setViewName("nameError");elsemv.setViewName("error");return mv;}
}

编写error.jsp、idError.jsp、nameError.jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
error

默认的错误页面--${msg}

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
id error

teamId Error----${msg}

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
name error

teamName error---${msg}

测试:
http://localhost:8080/ex/test01/100/test1
http://localhost:8080/ex/test01/1004/test
http://localhost:8080/ex/test01/1001/test1

9.3优化

一般将异常处理方法专门定义在一个类中,作为全局的异常处理类。

使用注解@ControllerAdvice,就是“控制器增强”,是给控制器对象增强功能的。使用
@ControllerAdvice 修饰的类中可以使用@ExceptionHandler。

当使用@RequestMapping 注解修饰的方法抛出异常时,会执行@ControllerAdvice 修饰的类中的异常处理方法。

@ControllerAdvice 注解所在的类需要进行包扫描,否则无法创建对象。


定义全局异常处理类:

package com.kkb.exceptions;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
/**
* ClassName: GlobalExceptionHandler
* 自定义全局异常处理类
* @author wanglina
* @version 1.0
*/
@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(value = TeamIdException.class)public ModelAndView exHandler1(Exception ex){ModelAndView mv=new ModelAndView();mv.addObject("msg",ex.getMessage());mv.setViewName("idError");return mv;
}@ExceptionHandler(value = TeamNameException.class)public ModelAndView exHandler2(Exception ex){ModelAndView mv=new ModelAndView();mv.addObject("msg",ex.getMessage());mv.setViewName("nameError");return mv;
}@ExceptionHandler(value = TeamException.class)public ModelAndView exHandler4(Exception ex){ModelAndView mv=new ModelAndView();mv.addObject("msg",ex.getMessage());mv.setViewName("nameError");return mv;
}@ExceptionHandler(value = Exception.class)public ModelAndView exHandler3(Exception ex){ModelAndView mv=new ModelAndView();mv.addObject("msg",ex.getMessage());mv.setViewName("error");return mv;}
}

10、拦截器

SpringMVC 中的 拦截器( Interceptor)是非常重要的,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。
拦截的时间点在“处理器映射器HandlerMapping根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器HandlerAdaptor执行处理器之前”。
在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链
HandlerExecutionChain,并返回给了前端控制器。
自定义拦截器,需要实现 HandlerInterceptor 接口。而该接口中含有三个方法:
在这里插入图片描述

preHandle(request,response, Object handler):
该方法在处理器方法执行之前执行。其返回值为boolean,若为true,则紧接着会执行处理器方法,且会将afterCompletion()方法放入到一个专门的方法栈中等待执行。


postHandle(request,response, Object handler,modelAndView):
该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。由于该方法是在处理器方法执行完后执行,且该方法参数中包含 ModelAndView,所以该方法可以修改处理器方法的处理结果数据,且可以修改跳转方向。


afterCompletion(request,response, Object handler, Exception ex):
当 preHandle()方法返回true时,会将该方法放到专门的方法栈中,等到对请求进行响应的所工作完成之后才执行该方法。即该方法是在前端控制器渲染(数据填充)了响应页面之后执行的,此时对ModelAndView再操作也对响应无济于事。
afterCompletion最后执行的方法,清除资源,例如在Controller方法中加入数据

10.1 自定义拦截器

package com.kkb.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* ClassName: MyInterceptor
* 自定义拦截器
* @author wanglina
* @version 1.0
*/
public class MyInterceptor implements HandlerInterceptor {//执行时间: 控制器方法执行之前,在ModelAndView返回之前//使用场景: 登录验证// 返回值 true : 继续执行控制器方法 表示放行 false: 不会继续执行控制器方法,表示拦截@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle-------------------");return true;
}//执行时间: 控制器方法执行之hou后,在ModelAndView返回之前,有机会修改返回值//使用场景: 日记记录,记录登录的ip,时间@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle-------------------");
}//执行时间: 控制器方法执行之后,在ModelAndView返回之后,没有机会修改返回值//使用场景: 全局资源的一些操作@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion-------------------");}
}
package com.kkb.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* ClassName: MyInterceptor
* 自定义拦截器
* @author wanglina
* @version 1.0
*/
public class MyInterceptor2 implements HandlerInterceptor {//执行时间: 控制器方法执行之前,在ModelAndView返回之前//使用场景: 登录验证// 返回值 true : 继续执行控制器方法 表示放行 false: 不会继续执行控制器方法,表示拦截@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle2-------------------");return true;
}//执行时间: 控制器方法执行之hou后,在ModelAndView返回之前,有机会修改返回值//使用场景: 日记记录,记录登录的ip,时间@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle-2------------------");
}//执行时间: 控制器方法执行之后,在ModelAndView返回之后,没有机会修改返回值//使用场景: 全局资源的一些操作@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion2-------------------");
}
}

10.2 配置拦截器

在springmvc.xml中添加如下配置

!-- 配置拦截器 -->















如果有多个拦截器的时候:(因为放到栈中)
preHandle:按照配置前后顺序执行
postHandle:按照配置前后逆序执行
afterCompletion:按照配置前后逆序执行

11、文件上传和下载

Spring MVC为文件上传提供了直接支持,这种支持是通过即插即用的MultipartResolver实现.
Spring中有一个MultipartResolver的实现类:CommonsMultipartResolver。
在SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下不能处理文件上传工作。
如果想使用Spring的文件上传功能,则需要先在上下文中配置MultipartResolver。

11.1 文件上传

(1)添加依赖

commons-fileuploadcommons-fileupload1.3.1

(2)springmvc.xml文件中配置MultipartResolver:


(3)fileHandle.jsp页面表单

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

文件操作

请选择文件:

(4)配置java代码(注意要创建文件夹保存上传之后的文件)在这里插入图片描述

package com.kkb.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* ClassName: FileController
* 文件操作的控制器
* @author wanglina
* @version 1.0
*/
@Controller
@RequestMapping("file")
public class FileController {/**
* 文件上传
* @param myFile
* @param request
* @return
*/@RequestMapping("upload")public String upload(@RequestParam("myFile") MultipartFile myFile,HttpServletRequest request){//获取文件的原始名称 d:\te.aa\txcat.jpgString originalFilename = myFile.getOriginalFilename();// 实际开发中,一般都要将文件重新名称进行存储// 存储到服务器的文件名称=随机的字符串+根据实际名称获取到源文件的后缀String fileName= UUID.randomUUID().toString().replace("-","")+originalFilename.substring(originalFilename.lastIndexOf("."));System.out.println(fileName);//文件存储路径String realPath =request.getServletContext().getRealPath("/uploadFile")+"/";try {myFile.transferTo(new File(realPath+fileName));//真正的文件上传到服务器指定的位置System.out.println("上传成功!"+realPath+fileName);} catch (IOException e) {e.printStackTrace();}return "ok";}@RequestMapping("hello")public String hello(){return "fileHandle";}
}

优化:如果需要限定文件的大小和类型:


......

package com.kkb.interceptor;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Iterator;
import java.util.Map;
/**
* ClassName: FileInterceptor
* 文件后缀处理的拦截器
* @author wanglina
* @version 1.0
*/
public class FileInterceptor implements HandlerInterceptor {
/**
* 在文件上传之前判断文件后缀是否合法
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponseresponse, Object handler) throws Exception {//判断是否是文件上传的请求boolean flag=true;if(request instanceof MultipartHttpServletRequest){MultipartHttpServletRequest multipartRequest=(MultipartHttpServletRequest) request;Map fileMap = multipartRequest.getFileMap();//遍历文件Iterator iterator = fileMap.keySet().iterator();while(iterator.hasNext()){String key = iterator.next();MultipartFile file = multipartRequest.getFile(key);String originalFilename = file.getOriginalFilename();String hz =originalFilename.substring(originalFilename.lastIndexOf("."));//判断后缀是否合法if(!hz.toLowerCase().equals(".png") && !hz.toLowerCase().equals(".jpg")){request.getRequestDispatcher("/jsp/fileTypError.jsp").forward(request,response);flag=false;}}}return flag;}
}

上传文件类型错误跳转的页面fileError.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

fileError

文件上传的类型有误!后缀必须是.png或者是.jpg

11.2 文件下载

(1 )前端页面

(2) 配置处理类方法

@RequestMapping("download")
public ResponseEntity download(HttpServletRequest request) throws
IOException {//指定文件的路径-- 要保证指定的路径下有该文件才可以Stringpath=request.getServletContext().getRealPath("/uploadFile")+"/4e27abf2c3724985a0877599773143c6.jpg";//创建响应 的头信息的对象HttpHeaders headers=new HttpHeaders();//标记以流的方式作出响应headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);//以附件的形式响应给用户headers.setContentDispositionFormData("attachment",URLEncoder.encode("4e27abf2c3724985a0877599773143c6.jpg","utf-8"));File file=new File(path);ResponseEntity resp=new ResponseEntity<(FileUtils.readFileToByteArray(file),headers,HttpStatus.CREATED);return resp;
}

在这里插入图片描述

12、RESTful风格

12.1 REST概念

REST(英文:Representational State Transfer,简称REST,意思:表述性状态转换,描述了一个架构样式的网络系统,比如web应用)。
它是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件,它主要用于客户端和服务端交互类的软件。基于这个风格设计的软件可以更简介,更有层次,更易于实现缓存等机制。
它本身并没有什么使用性,其核心价值在于如何设计出符合REST风格的网络接口。

12.2 RESTful概念

REST指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful。
RESTful的特性:
资源(Resources):互联网所有的事物都可以被抽象为资源 。它可以是一段文本、一张图片、一首歌
曲、一种服务,总之就是一个具体的存在。可以用一个URI(统一资源定位符)指向它,每种资源对应一个特性的URI。要获取这个资源,访问它的URI就可以,因此URI即为每一个资源的独一无二的识别符。
表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层(Representation)。比如,文
本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式。
状态转换(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转换”(State Transfer)。而这种转换是建立在表现层之上的,所以就是“表现层状态转换”。
具体来说就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。他们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
原来的操作资源的方式:
http://localhost:8080/getExpress.do?id=1
http://localhost:8080/saveExpress.do
http://localhost:8080/updateExpress.do
http://localhost:8080/deleteExpress.do?id=1
原来用的方式当然没有问题,但是如果有更简洁的方式就更好了,此时就是RESTful风格。

使用RESTful操作资源:

GET /expresses #查询所有的快递信息列表
GET /express/1006 #查询一个快递信息
POST /express #新建一个快递信息
PUT /express/1006 #更新一个快递信息(全部更新)
PATCH /express/1006 #更新一个快递信息(部分更新)
DELETE /express/1006 #删除一个快递信息

12.3 API设计/URL设计

12.3.1 动词+宾语

RESTful 的核心思想就是客户端的用户发出的数据操作指令都是"动词 + 宾语"的结构。例如GET
/expresses 这个命令,GET是动词,/expresses 是宾语。

动词通常就是五种 HTTP 方法,对应 CRUD 操作。
GET:读取(Read)
POST:新建(Create)
PUT:更新(Update)
PATCH:更新(Update),通常是部分更新
DELETE:删除(Delete)
PS: 1、根据 HTTP 规范,动词一律大写。
2、一些代理只支持POST和GET方法, 为了使用这些有限方法支持RESTful API,需要一种办法覆盖
http原来的方法。使用订制的HTTP头 X-HTTP-Method-Override 来覆盖POST 方法.

12.3.2 宾语必须是名词

宾语就是 API 的 URL,是 HTTP 动词作用的对象。它应该是名词,不能是动词。
比如,/expresses 这个 URL 就是正确的。

以下这些URL都是不推荐的,因为带上了动词,不是推荐写法。
/getAllExpresses
/getExpress
/createExpress
/deleteAllExpress

ps:不要混淆名词单数和复数,为了保持简单,只对所有资源使用复数。

12.3.3 避免多级URL

如果资源中有多级分类,也不建议写出多级的URL。例如要获取球队中某个队员,有人可能这么写:
GET /team/1001/player/1005
这种写法的语义不够明确,所以推荐使用查询字符串做后缀,改写为
GET /team/1001?player=1005.

再例如查询所有的还未取出的快递,你该如何编写呢?
GET /expresses/statu 不推荐
GET /expresses?statu=false 推荐

12.4 HTTP状态码

客户端的用户发起的每一次请求,服务器都必须给出响应。响应包括 HTTP 状态码和数据两部分。
HTTP 状态码就是一个三位数,分成五个类别。这五大类总包含了100多种状态码(不需要全都记住,不用紧张哈,覆盖了绝大部分可能遇到的情况。每一种状态码都有标准的(或者约定的)解释,客户端只需查看状态码,就可以判断出发生了什么情况,所以服务器应该返回尽可能精确的状态码。

五类状态码分别如下:
1xx:相关信息
2xx:操作成功
3xx:重定向
4xx:客户端错误
5xx:服务器错误
PS:API 不需要1xx状态码,所以这个类别直接忽略。

12.4.1 状态码2xx

200 状态码表示操作成功,但是不同的方法可以返回更精确的状态码。

GET: 200 OK 表示一切正常
POST: 201 Created 表示新的资源已经成功创建
PUT: 200 OK
PATCH: 200 OK
DELETE: 204 No Content 表示资源已经成功删除

12.4.2 状态码3xx

API 用不到 301 状态码(永久重定向)和 302 状态码(暂时重定向, 307 也是这个含义),因为它们可以由应用级别返回,浏览器会直接跳转,API 级别可以不考虑这两种情况。
API 用到的 3xx 状态码,主要是 303 See Other ,表示参考另一个 URL。它与 302 和 307 的含义一
样,也是"暂时重定向",区别在于 302 和 307 用于 GET 请求,而 303 用于 POST 、 PUT 和DELETE 请求。收到 303 以后,浏览器不会自动跳转,而会让用户自己决定下一步怎么办。

我们只需要关注一下304状态码就可以了
304 : Not Modified 客户端使用缓存数据

12.4.3 状态码4xx

4xx 状态码表示客户端错误。

400 Bad Request:服务器不理解客户端的请求,未做任何处理。
401 Unauthorized:用户未提供身份验证凭据,或者没有通过身份验证。
403 Forbidden:用户通过了身份验证,但是不具有访问资源所需的权限。
404 Not Found:所请求的资源不存在,或不可用。
405 Method Not Allowed:用户已经通过身份验证,但是所用的 HTTP 方法不在他的权限之内。
410 Gone:所请求的资源已从这个地址转移,不再可用。
415 Unsupported Media Type:客户端要求的返回格式不支持。比如,API 只能返回 JSON 格式,但是
客户端要求返回 XML 格式。
422 Unprocessable Entity :客户端上传的附件无法处理,导致请求失败。
429 Too Many Requests:客户端的请求次数超过限额。

12.4.4 状态码5xx

5xx 状态码表示服务端错误。一般来说,API 不会向用户透露服务器的详细信息,所以只要两个状态码就够了。

500 Internal Server Error:客户端请求有效,服务器处理时发生了意外。
503 Service Unavailable:服务器无法处理请求,一般用于网站维护状态。

12.5 服务器响应

服务器返回的信息一般不推荐纯文本,而是建议大家选择JSON 对象,因为这样才能返回标准的结构化数据。
所以,服务器回应的 HTTP 头的 Content-Type 属性要设为 application/json 。客户端请求时,也要
明确告诉服务器,可以接受 JSON 格式,即请求的 HTTP 头的 ACCEPT 属性也要设成application/json 。
当发生错误的时候,除了返回状态码之外,也要返回错误信息。所以我们可以自己封装要返回的信息。

12.6 案例

12.6.1RESTful风格的查询

前端页面restful.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

restful

球队ID:
球队名称:
球队位置:

控制器RestfulController.java

package com.kkb.controller;
import com.kkb.pojo.Team;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
/**
* ClassName: RestfulController
* restful风格的控制器
* @author wanglina
* @version 1.0
*/
@Controller
public class RestfulController {private static List teamList;static {teamList=new ArrayList<>(3);for(int i=1;i<=3;i++){Team team=new Team();team.setTeamId(1000+i);team.setTeamName("湖人"+i);team.setLocation("洛杉矶"+i);teamList.add(team);}
}/**
* 查询所有的球队
* @return*/@RequestMapping(value = "/teams",method = RequestMethod.GET)@ResponseBodypublic List getAll(){System.out.println("查询所有GET--发起的请求---------");return teamList;
}/**
* 根据id 查询单个的球队
* @return*/@RequestMapping(value = "/team/{id}",method = RequestMethod.GET)@ResponseBodypublic Team getOne(@PathVariable("id")int id){System.out.println("查询单个GET--发起的请求---------");for (Team team : teamList) {if(team.getTeamId()==id){return team;}
}return null;
}@RequestMapping("hello")public String hello(){return "restful";}
}

12.6.2RESTful风格的添加

前端页面上脚本中添加如下内容:

//给 添加POST 按钮绑定单击事件$("#btnPost").click(function () {alert($("#myForm").serialize());//发起异步请求$.ajax({type: "POST",url: "/team", //RESTful风格的API定义data: $("#myForm").serialize(),//表单的所有数据以?&形式追加在URL后面/team?teamId=1006&teamName=kuaichuan&location=lasdataType:"json",success: function(msg){//alert( "Data Saved: " + msg );$("#showResult").html(msg);}});
});

控制器中添加方法

**
* 添加一个球队*/@RequestMapping(value = "team",method = RequestMethod.POST)@ResponseBodypublic String add(Team team){System.out.println("添加POST--发起的请求---------");teamList.add(team);return "201";
}

12.6.3 RESTful风格的更新

前端页面上脚本中添加如下内容:

$("#btnPut").click(function(){alert($("#myForm").serialize());$.ajax({type: "POST",url: "/team/"+$("#teamId").val(),data: $("#myForm").serialize()+"&_method=PUT",dataType:"json",//headers:{"X-HTTP-Method-Override":"GET"},success: function(msg){$("#showResult").html(msg);}});
});

控制器中添加方法

@RequestMapping(value = "/team/{id}",method = RequestMethod.PUT)@ResponseBodypublic String update(@PathVariable("id") int id,Team newTeam){System.out.println(id);for (Team team : teamList) {if(team.getTeamId()==id){team.setTeamName(newTeam.getTeamName());team.setLocation(newTeam.getLocation());return "204";}}return "404";
}

12.6.4RESTful风格的删除

前端页面上脚本中添加如下内容:

//给 删除DELETE 按钮绑定单击事件$("#btnDel").click(function () {alert($("#myForm").serialize());//发起异步请求$.ajax({type: "POST",url: "/team/"+$("#teamId").val(), //RESTful风格的API定义data: "_method=DELETE",success: function(msg){//alert( "Data Saved: " + msg );$("#showResult").html(msg);}});
});

控制器中添加方法

/**
* 根据ID删除一个球队*/@RequestMapping(value = "team/{id}",method = RequestMethod.DELETE)@ResponseBodypublic String del(@PathVariable("id")int id){System.out.println("删除DELETE--发起的请求---------");for (Team team1 : teamList) {if(team1.getTeamId()==id){teamList.remove(team1);return "204";}}return "500";
}

12.6.5 RESTful风格的更新和删除遇到的问题

12.6.5.1 遇到的问题:

在Ajax中,采用Restful风格PUT和DELETE请求传递参数无效,传递到后台的参数值为null

12.6.5.2 产生的原因:

Tomcat封装请求参数的过程:
1.将请求体中的数据,封装成一个map
2.request.getParameter(key)会从这个map中取值
3.SpringMvc封装POJO对象的时候,会把POJO中每个属性的值进行request.getParamter();
AJAX发送PU或者DELETE请求时,请求体中的数据通过request.getParamter()拿不到。
Tomcat一检测到是PUT或者DELETE就不会封装请求体中的数据为map,只有POST形式的请求才封装请求为map。

12.6.5.3 解决方案:

1、前端页面中的ajax发送请求的时候在url中加 &_method=”PUT” 或者 &_method=”DELETE” 即可在这里插入图片描述
2、web.xml中配置
配置的时候多个过滤器需要注意顺序


httpMethodFilter
org.springframework.web.filter.HiddenHttpMethodFilterhttpMethodFilter/*

12.7 自己封装响应结果

12.7.1 封装的实体类

package com.kkb.vo;
import java.util.List;
/**
* ClassName: AjaxResultVO
* 自己封装的返回的结果类
* @author wanglina
* @version 1.0
*/
public class AjaxResultVO {private Integer code;//返回的状态码private String msg;//返回的信息(一般错误的信息或者异常的信息)private List list; //返回的数据有可能是集合private T obj;//返回的数据有可能是对象public AjaxResultVO() {code = 200;msg = "OK";list = null;obj = null;
}public AjaxResultVO(Integer code, String msg) {this.code = code;this.msg = msg;
}public AjaxResultVO(Integer code, String msg, List list) {this.code = code;this.msg = msg;this.list = list;
}public AjaxResultVO(Integer code, String msg, T obj) {this.code = code;this.msg = msg;this.obj = obj;
}public Integer getCode() {return code;
}public void setCode(Integer code) {this.code = code;
}public String getMsg() {return msg;
}public void setMsg(String msg) {this.msg = msg;
}public List getList() {return list;
}public void setList(List list) {this.list = list;
}public T getObj() {return obj;
}public void setObj(T obj) {this.obj = obj;
}
}

12.7.2 修改控制器的返回值

package com.kkb.controller;
import com.kkb.pojo.Team;
import com.kkb.vo.AjaxResultVO;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
/**
* ClassName: RestfulController
* restful风格的控制器
* @author wanglina
* @version 1.0
* */
@Controller
public class RestfulController {private static List teamList;static {teamList=new ArrayList<>(3);for(int i=1;i<=3;i++){Team team=new Team();team.setTeamId(1000+i);team.setTeamName("湖人"+i);team.setLocation("洛杉矶"+i);teamList.add(team);
}
}/**
* 根据ID删除一个球队*/@RequestMapping(value = "team/{id}",method = RequestMethod.DELETE)@ResponseBodypublic AjaxResultVO del(@PathVariable("id")int id){System.out.println("删除DELETE--发起的请求---------");for (Team team1 : teamList) {if(team1.getTeamId()==id){teamList.remove(team1);return new AjaxResultVO();
}
}return new AjaxResultVO(500,"要删除的ID不存在~~~");
}/**
* 根据ID更新一个球队*/@RequestMapping(value = "team/{id}",method = RequestMethod.PUT)@ResponseBodypublic AjaxResultVO add(@PathVariable("id")int id, Team team){System.out.println("更新PUT--发起的请求---------");for (Team team1 : teamList) {if(team1.getTeamId()==id){team1.setLocation(team.getLocation());team1.setTeamName(team.getTeamName());return new AjaxResultVO();
}
}return new AjaxResultVO(500,"要更新的ID不存在~~~");
}/**
* 添加一个球队*/@RequestMapping(value = "team",method = RequestMethod.POST)@ResponseBodypublic AjaxResultVO add(Team team){System.out.println("添加POST--发起的请求---------");teamList.add(team);return new AjaxResultVO<>(200,"OK");
}
/**
* 查询所有的球队
* @return*/@RequestMapping(value = "teams",method = RequestMethod.GET)@ResponseBodypublic AjaxResultVO getAll(){System.out.println("查询所有GET--发起的请求---------");return new AjaxResultVO<>(200,"OK",teamList);
}/**
* 根据id 查询单个的球队
* @return*/@RequestMapping(value = "team/{id}",method = RequestMethod.GET)@ResponseBodypublic AjaxResultVO getOne(@PathVariable("id")int id){System.out.println("查询单个GET--发起的请求---------");for (Team team : teamList) {if(team.getTeamId()==id){return new AjaxResultVO<>(200,"OK",team);
}
}return null;
}@RequestMapping("hello")public String hello(){return "restful";
}
}

12.7.3 修改前端页面的脚步


相关内容

热门资讯

前端-session、jwt 目录:   (1)session (2&#x...
企业即时通讯怎样为企业实现移动... 对于企业来说,在办公过程中少不了工作人员相互传递信息和数据传输,企业内部...
骑行选择什么自行车 极速百科网... 骑行选择什么自行车目录骑行选择什么自行车骑行选择什么自行车 1. 山地自行车:适合崎岖不平的路...
蓝色都有哪几种,蓝色都有什么颜... 蓝色都有哪几种目录蓝色都有哪几种蓝色都有什么颜色的蓝图片,蓝色都有什么颜色的蓝二年级蓝色有哪些种类蓝...
如何自学游泳要安全的,初学游泳... 如何自学游泳要安全的目录如何自学游泳要安全的初学游泳的人需要准备哪些东西,注意哪些事项?如何自学游泳...
一年级家长的话怎么写评语,一年... 一年级家长的话怎么写评语目录一年级家长的话怎么写评语一年级学生评价手册家长寄语怎么写一年级最佳家长评...
EEG微状态的功能意义 导读大脑的瞬时全局功能状态反映在其电场结构上。聚类分析方法一致地提取了四种头表面脑电场结构ÿ...
docker 镜像管理 查看本地镜像 docker images 可以查看本地下载的镜像 docker images [O...
k8s-1.22.15部署ng... 1.介绍 在前面文章中已经提到,Service对集群之外暴露服务的主要方式有两种&#x...
革命烈士寄语怎么写,清明节缅怀... 革命烈士寄语怎么写目录革命烈士寄语怎么写清明节缅怀先烈的寄语有哪些呢?革命烈士寄语怎么写 革命...
5万元以下新车推荐,5万以下买... 本篇文章极速百科给大家谈谈5万元以下新车推荐,5万以下买什么车好,以及5万以下的新车哪款最好对应的知...
真皮沙发翻新一般多少钱?(真皮... 本篇文章极速百科给大家谈谈真皮沙发翻新一般多少钱?,以及真皮沙发翻新一般多少钱一个对应的知识点,希望...
磨皮什么意思(磨皮是啥?) 磨... 本篇文章极速百科给大家谈谈磨皮什么意思,以及磨皮是啥?对应的知识点,希望对各位有所帮助,不要忘了收藏...
进程间通信【Linux】 1. 进程间通信 1.1 什么是进程间通信 在 Linux 系统中,进程间通信...
从NVIDIA GTC大会,看... 从NVIDIA GTC 2023这场全球行业盛宴,我们可以解读出AI算力行业的哪些重要...
请问什么是童子,什么是童子 极... 请问什么是童子目录请问什么是童子什么是童子古代 童子是什么意思童子是什么意思?请问什么是童子 ...
中招考试考哪些科目,中招考试考... 中招考试考哪些科目目录中招考试考哪些科目中招考试考几门科目一共多少分?中考有哪些科目中考考几科,都什...
做电商如何做,电商怎样做才能赚... 做电商如何做目录做电商如何做电商怎样做才能赚钱?做的好的电商朋友可以教教我怎么做吗新手小白怎么做跨境...
vue常见面试题 一、vue常见面试题 1.vue父组件向子组件传递数据? 通过props 2.v-sh...
翘字的音节是什么,翘字的音序和... 翘字的音节是什么目录翘字的音节是什么翘字的音序和音节是什么?翘字的音节 音序 部首“翘”的偏旁是什么...
计算机组成原理实验1---运算...     本实验为哈尔滨工业大学计算机组成原理实验,实验内容均为个人完成,...
移动应用架构设计:如何转变开发... 移动应用架构设计:如何转变开发流程 2023 年掌握移动应用程序架构的指南࿰...
ModalForm的使用 一、ModalForm销毁配置了modalProps={{ destroyOnClose: ...
东北话哈拉是什么意思(东北话哈... 本篇文章极速百科给大家谈谈东北话哈拉是什么意思,以及东北话哈拉少对应的知识点,希望对各位有所帮助,不...
吾这个字是什么意思,“吾”字是... 吾这个字是什么意思目录吾这个字是什么意思“吾”字是什么意思?吾这个字是什么意思吾字是什么意思?吾这个...
qq闪照已销毁怎么恢复 极速百... qq闪照已销毁怎么恢复目录qq闪照已销毁怎么恢复qq闪照已销毁怎么恢复如何恢复QQ闪照闪照已销毁怎么...
过期茶叶有哪些妙用,过期的茶叶... 过期茶叶有哪些妙用目录过期茶叶有哪些妙用过期的茶叶有什么用处过期的茶叶有何用处?过期茶叶千万别扔11...
Scala中Array常用的方...         在scala中,Array有大量的方法。定义一个数组arr后ÿ...
JDK源码系列:ThreadL... 在前面的关于ThreadLocal的文章中提到了所谓的内存泄漏问题,同时也提到了Thr...
gan实战(DCGAN、) 一、DCGAN 1.1 参数 (1)输入:会被放缩到64...