概念:是 Spring 内置的一个框架,解决了 WEB 开发中的常见问题(参数接收,文件上传,表单验证),支持 Restful 风格的 URL 请求,采用了松散耦合可插拔组件结构,比其他 MVC 框架更具扩展性和灵活性。
优势:
- 基于 MVC 架构,功能分工明确。解决页面代码和后台代码的分离。
- 简单易用。SpringMVC 也是轻量级的,jar 很小。不依赖的特定的接口和类就可以开发一个注解的 SpringMVC 项目。
- 作 为 Spring 框架一部分 , 能够使用 Spring 的 IoC 和 AOP 。 方便整合 MyBatis,Hiberate,JPA 等其他框架。
- SpringMVC 的注解强大易用。
1. MVC 模式回顾

2. SpringMVC 工作流程

配置视图解析器
<!-- 配置视图解析器 --><bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/"/><property name="suffix" value=".jsp"/></bean>
3. @RequestMapping 注解解析
3.1 出现的位置
在类的级别上的注解
会将一个特定请求或者请求模式映射到一个控制器之上。
之后你还可以另外添加方法级别的注解来进一
步指定到处理方法的映射关系。
3.2 指定请求提交方式
@RequestMapping(value = “hello.do”, method = RequestMethod.GET) 只能处理 get 方式提交的请求
@RequestMapping(value = “hello.do”) 都可以
地址栏请求 get 请求
超链接请求 get 请求
表单请求 默认 get,可以指定 post
AJAX请求 默认 get,可以指定 post
3.3 url-pattern
3.3.1 解析
在web.xml配置SpringMVC的前端控制器时有这个节点。这个节点中的值一般有两种写法:
1. *.do在没有特殊要求的情况下,SpringMVC 的前端控制器 DispatcherServlet 的常使用后辍匹配方式,可以写为*.do 或者 *.action, *.mvc 等。2. /可以写为/,但是 DispatcherServlet 会将向静态内容--例如.css、.js、图片等资源的获取请求时,也会当作是一个普通的 Controller 请求。前端控制器会调用处理器映射器为其查找相应的处理器。肯定找不到啊,所以所有的静态资源获取请求也均会报 404 错误。
3.3.2 静态资源访问
如果的值配置为/后,静态资源可以通过以下两种方法解决。
- 使用< mvc:default-servlet-handler/ >
在 SpringMVC 的配置文件下配置以下内容
<mvc:default-servlet-handler/><!--声 明 了 <mvc:default-servlet-handler /> 后 ,springmvc框架会在容器中创建DefaultServletHttpRequestHandler处理器对象。该对象会对所有进入 DispatcherServlet的URL进行检查。如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet 处理。一般的服务器都有默认的 Servlet。例如咱们使用的Tomcat服务器中,有一个专门用于处理静态资源访问的 Servlet 名叫 DefaultServlet。其<servlet-name/>为default。可以处理各种静态资源访问请求。该Servlet注册在 Tomcat 服务器的 web.xml 中。在 Tomcat安装目录/conf/web.xml。-->
- 使用 < mvc:resources/ >
在 SpringMVC 的配置文件下配置以下内容
<mvc:resources location="/images/" mapping="/images/**" /><!--location: 表示静态资源所在目录。当然,目录不要使用/WEB-INF/及其子目录。mapping: 表示对该资源的请求。注意,后面是两个星号**。-->
4. 处理器方法的参数
处理器方法可以包含以下四类参数,这些参数会在系统调用时由系统自动赋值.所以我们可以在方法内直 接使用。以下是这四类参数:
- HttpServletRequest
- HttpServletResponse
- HttpSession
- 请求中所携带的请求参数
4.1 直接使用方法的参数直接接收
好处:传递参数的格式不会改变
@RequestMapping(value = "test01.do", method= RequestMethod.POST)public ModelAndView test01(Integer id, String name, String address){System.out.println("test01.do---------");System.out.println(id);System.out.println(name);System.out.println(address);return new ModelAndView("ok");}
<h1> 直接使用方法的参数直接接收 </h1><form action="test01.do" method="post"><input name="id" type="text" placeholder="id"/><br><input name="name" type="text" placeholder="name"/><br><input name="address" type="text" placeholder="address"/><br><button type="submit">提交</button></form>
4.2 使用对象接收多个参数
注意:要求用户请求中携带的参数名称必须与实体类中的属性保持一致,否则获取不到
@RequestMapping(value = "test02.do", method= RequestMethod.POST)public ModelAndView test02(Team team){System.out.println("test02.do---------");System.out.println(team);return new ModelAndView("ok");}
<h1> 2. 直接使用对象直接接收 </h1><form action="/param/test02.do" method="post"><input name="id" type="text" placeholder="id"/><br><input name="name" type="text" placeholder="name"/><br><input name="address" type="text" placeholder="address"/><br><button type="submit">提交</button></form>
4.3 请求参数和方法参数不一致
使用 @RequestParam 注解
- value:表单中的参数名
- required true:必须赋值,否则报400错误;false:可以不赋值,结果是 null。
@RequestMapping(value = "test03.do", method= RequestMethod.POST)public ModelAndView test03(@RequestParam("id") Integer a,@RequestParam("name") String b,@RequestParam("address") String c){System.out.println("test03.do---------");System.out.println(a);System.out.println(b);System.out.println(c);return new ModelAndView("ok");}
<h1> 3. 请求参数和方法参数不一致 </h1><form action="/param/test03.do" method="post"><input name="id" type="text" placeholder="id"/><br><input name="name" type="text" placeholder="name"/><br><input name="address" type="text" placeholder="address"/><br><button type="submit">提交</button></form>
4.4 使用HttpServletRequest 对象获取参数
与 java web 当中一样
4.5 直接使用URL地址传参: 借助@PathVariable 注解
4.6 获取日期类型的参数
4.7 获取数组类型的参数
4.8 获取集合类型的参数
简单类型的可以通过@RequestParam注解实现;对象集合不支持直接获 取,必须封装在类中,作为一个属性操作
5. 请求参数中文乱码
web.xml 中进行如下配置
<filter><filter-name>characterEncodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param><init-param><param-name>forceRequestEncoding</param-name><param-value>true</param-value></init-param><init-param><param-name>forceResponseEncoding</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>characterEncodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
6. 处理器方法的返回值
6.1 返回 ModelAndView
数据模型+视图
当处理器方法处理完后,需要跳转到其它资源的同时传递数据,选择返回 ModelAndView 比较好,但是
如果只是需要传递数据或者跳转之一,这个时候ModelAndView 就不是最优选择。
//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;}
6.2 返回 String
//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}
<h3>test02---request作用域获取:---${requestScope.team.teamName}---${requestScope.team.teamId}---${requestScope.team.location}</h3><h3>test02---session作用域获取:---${sessionScope.team.teamName}---${sessionScope.team.teamId}---${sessionScope.team.location}</h3>
6.3 返回对象类型
6.3.1 返回基础类型
6.3.2 返回自定义对象类型
6.3.3 返回 List
6.3.4 返回 Map
6.4 无返回值 void
7. 页面导航的方式
7.1 转发
7.1.1 String
@RequestMapping("d1.do")public String demo01(){return "ok";// 默认方式,由视图解析器处理return "forward:/ok.jsp";// 添加forward之后,视图解析器失效,需要添加绝对路径}
7.1.2 ModelAndView
@RequestMapping("d2.do")public ModelAndView demo02(){ModelAndView mav = new ModelAndView();//mav.setViewName("ok");mav.setViewName("forward:/ok.jsp");return mav;}
7.2 重定向
7.2.1 String
@RequestMapping("d3.do")public String demo03(){//return "ok";// 默认方式,由视图解析器处理return "redirect:/ok.jsp";// 添加 redirect 之后,视图解析器失效,需要添加绝对路径}
7.2.2 ModelAndView
@RequestMapping("d4.do")public ModelAndView demo04(){ModelAndView mav = new ModelAndView();// 使用 ModelAndView 返回解决了重定向数据丢失的问题// 数据显示在地址栏中mav.addObject("teamName", "Real Madrid");mav.addObject("teamRank", "63");// <h3>teamName:${param.teamName}</h3> 可以获取到!// mav.setViewName("ok");mav.setViewName("redirect:/ok.jsp");return mav;}
7.3 转发或重定向到控制器
@RequestMapping("d5.do")public ModelAndView demo05(){System.out.println("转发~~~~~~~~~~~");ModelAndView mav = new ModelAndView();//mav.setViewName("ok");mav.setViewName("forward:/n/d1.do");return mav;}@RequestMapping("d6.do")public ModelAndView demo06(){System.out.println("重定向~~~~~~~~~~~");ModelAndView mav = new ModelAndView();//mav.setViewName("ok");mav.setViewName("redirect:/n/d1.do");return mav;}
8. 异常处理
8.1 @ExceptionHandler
@ExceptionHandler 可以将一个方法指定为异常处理方法。
被注解的方法,其返回值可以是 ModelAndView、String,或 void,方法名随意,方法参数可以是
Exception 及其子类对象、HttpServletRequest、HttpServletResponse 等。系统会自动为这些方法参
数赋值。
对于异常处理注解的用法,也可以直接将异常处理方法注解于 Controller 之中.
@Controller@RequestMapping("e")public class ExceptionController {@RequestMapping("d1.do")public ModelAndView test01(Integer teamId, String teamName) throws TeamIdException, TeamNameException {ModelAndView mav = new ModelAndView();if (teamId<1000){throw new TeamIdException("teamId 不可以小于 1000!");}if ("test".equals(teamName)){throw new TeamNameException("teamName不可以是 test!");}mav.setViewName("ok");return mav;}@ExceptionHandler(value = {TeamIdException.class, TeamNameException.class, Exception.class})public ModelAndView handler(Exception e){ModelAndView mav = new ModelAndView();mav.addObject("message", e.getMessage());if (e instanceof TeamIdException){mav.setViewName("idError");}else if (e instanceof TeamNameException){mav.setViewName("nameError");}else{mav.setViewName("error");}return mav;}}
8.2 优化
将异常处理方法集中到一个类中,更简洁,方便维护。
@ControllerAdvicepublic class GlobalExceptionHandler {@ExceptionHandler(value = TeamIdException.class)public ModelAndView idHandler(Exception e){ModelAndView mav = new ModelAndView();mav.addObject("message", e.getMessage());mav.setViewName("idError");return mav;}@ExceptionHandler(value = TeamNameException.class)public ModelAndView nameHandler(Exception e){ModelAndView mav = new ModelAndView();mav.addObject("message", e.getMessage());mav.setViewName("nameError");return mav;}@ExceptionHandler(value = Exception.class)public ModelAndView Handler(Exception e){ModelAndView mav = new ModelAndView();mav.addObject("message", e.getMessage());mav.setViewName("error");return mav;}}
9. 拦截器
拦截的时间点在“处理器映射器HandlerMapping根据用户提交的请求映射出了所要执行的处理器类,并 且也找到了要执行该处理器类的处理器适配器,在处理器适配器HandlerAdaptor执行处理器之前”。
在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链
HandlerExecutionChain,并返回给了前端控制器。
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方法中加入数据
9.1 自定义拦截器
public class MyInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle ~~~~~~");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle ~~~~~~");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion ~~~~~~");}}
9.2 配置拦截器
<mvc:interceptors><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.zmh.interceptor.MyInterceptor" id="interceptor"/></mvc:interceptor><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.zmh.interceptor.MyInterceptor1" id="interceptor1"/></mvc:interceptor></mvc:interceptors>
10. 文件上传和下载
10.1 上传
pom.xml中添加相关依赖<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload --><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.1</version></dependency>
SpingMVC.xml中手动添加bean<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"/>
创建表单
<body><h1>文件上传</h1><form action="/file/upload.do" method="post" enctype="multipart/form-data">请选择文件<input type="file" name="myFile"><br><button type="submit">上传文件</button></form></body>
代码
@Controller@RequestMapping("file")public class FileController {@RequestMapping("hello.do")public String hello(){return "upload";}@RequestMapping("upload.do")public String upload(@RequestParam("myFile") MultipartFile myFile, HttpServletRequest req) throws IOException {// 1. 获取文件的原始名称String originalFilename = myFile.getOriginalFilename();System.out.println("文件原始名称:"+originalFilename);// 2. 文件重命名 之后存储String end = originalFilename.substring(originalFilename.lastIndexOf("."));System.out.println("文件名称后缀:"+end);String fileName = UUID.randomUUID().toString().replace("-", " ")+end;System.out.println("文件重命名后名称:"+fileName);// 3. 文件存储路径String realPath = req.getServletContext().getRealPath("/upload")+"/";// 4. 上传myFile.transferTo(new File(realPath+fileName));System.out.println("上传成功 位置是 "+realPath+fileName);return "ok";}}
优化(限制)
限制文件大小(异常处理)
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"><property name="maxUploadSize" value="368640"/><property name="defaultEncoding" value="utf-8"/></bean>
@ExceptionHandler(value = MaxUploadSizeExceededException.class)public ModelAndView Handler(Exception e){ModelAndView mav = new ModelAndView();mav.addObject("message", "别给我整这么大的,俺受不了!");mav.setViewName("error");return mav;}
限制文件类型(拦截器)
public class FileInterceptor implements HandlerInterceptor {/*** 在文件上传之前判断是否合乎规则* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {boolean flag = true;// 1. 首先判断请求是否是文件上传的请求if (request instanceof MultipartHttpServletRequest){// 2. request 强制类型转换MultipartHttpServletRequest multipartReq = (MultipartHttpServletRequest) request;// 3. 取出文件集Map<String, MultipartFile> fileMap = multipartReq.getFileMap();System.out.println("map!!! "+fileMap);// 4. 循环遍历 map 中方法Iterator<String> iterator = fileMap.keySet().iterator();while(iterator.hasNext()){String key = iterator.next();MultipartFile file = fileMap.get(key);String originalFilename = file.getOriginalFilename();String substring = originalFilename.substring(originalFilename.lastIndexOf("."));System.out.println("后缀:"+substring);if (! ".png".equals(substring.toLowerCase()) && ! substring.toLowerCase().equals(".jpg")){multipartReq.getRequestDispatcher("/fileError.jsp").forward(request, response);flag = false;}}}return flag;}}
之后在
SpringMVC.xml中配置拦截器
10.2 下载
@RequestMapping("download.do")public ResponseEntity<byte[]> download(HttpServletRequest req) throws IOException {// 1. 指定文件路径String path = req.getServletContext().getRealPath("/upload")+"/cc1b8fc4 5eb5 451d 80b5 6c1870f85b76.md";// 2. 创建响应头信息对象HttpHeaders headers = new HttpHeaders();// 3. 标记以流的方式作出相应headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);// 4. 以附件的形式响应给用户headers.setContentDispositionFormData("attachment",URLEncoder.encode("cc1b8fc4 5eb5 451d 80b5 6c1870f85b76.md", "utf-8"));File file = new File(path);ResponseEntity<byte[]> resp = new ResponseEntity<>(FileUtils.readFileToByteArray(file), headers, HttpStatus.CREATED);return resp;}
11. RESTful
11.1 概念
REST(英文:Representational State Transfer,简称REST,意思:表述性状态转换,描述了一个架构 样式的网络系统,比如web应用)。
REST指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful。
特性:
- 资源
- 表现层
- 状态转换
GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
使用RESTful操作资源:
GET /expresses #查询所有的快递信息列表
GET /express/1006 #查询一个快递信息
POST /express #新建一个快递信息
PUT /express/1006 #更新一个快递信息(全部更新)
PATCH /express/1006 #更新一个快递信息(部分更新)
DELETE /express/1006 #删除一个快递信息
11.2 API 设计
动词+宾语
- 动词大写
- 一些代理只支持POST和GET方法, 为了使用这些有限方法支持RESTful API,需要一种办法覆盖 http原来的方法。使用订制的HTTP头 X-HTTP-Method-Override 来覆盖POST 方法.
宾语必须是名词
- 避免多级 URL
11.3 HTTP 状态码
五类状态码分别如下:
- 1xx:相关信息
- 2xx:操作成功
- 3xx:重定向
- 4xx:客户端错误
- 5xx:服务器错误
- PS:API 不需要1xx状态码,所以这个类别直接忽略。
11.4 服务器响应
服务器返回的信息一般不推荐纯文本,而是建议大家选择JSON 对象,因为这样才能返回标准的结构化数 据。
11.5 案例
11.5.1 查询
jsp页面
<form action="" id="myForm" method="post">球队ID:<input name="id" type="text" id="teamId"/><br>球队名称:<input name="name" type="text"/><br>球队address:<input name="location" type="text"/><br><button type="button" id="getAll">查询所有 GET</button><button type="button" id="getOne">查询单个 GET</button><button type="button" id="post">添加 POST</button><button type="button" id="put">更新 PUT</button><button type="button" id="delete">删除 DELETE</button></form><p id="result"></p><script>$(function(){$("#getAll").click(function(){$.ajax({type: "GET",url: "/teams",data: "",dataType: "json",success:function(data){alert("Data Is Found !");var str = "";for (var i=0; i<data.length; i++){var o = data[i];str +=o.id+"---"+o.name+"---"+o.address+"<br>";}$("#result").html(str);}});});$("#getOne").click(function(){$.ajax({type: "GET",url: "/team/"+$("#teamId").val(),data: "",dataType: "json",success:function(data){alert("Data Is Found !");var str = "";if (data==null){str="没找到..."}else{str +=data.id+"---"+data.name+"---"+data.address+"<br>";}$("#result").html(str);}});});});</script>
@RequestMapping(value = "teams", method = RequestMethod.GET)@ResponseBodypublic List<Team> get1(){System.out.println("getAll ~~~~~~");return teamList;}@RequestMapping(value = "team/{teamId}", method = RequestMethod.GET)@ResponseBodypublic Team get2(@PathVariable("teamId") int id){System.out.println("getOne ~~~~~~");for (Team team : teamList) {if(team.getId()==id){return team;}}return null;}
11.5.2 添加
11.5.3 更新
@RequestMapping(value = "team/{id}", method = RequestMethod.PUT)@ResponseBodypublic int update(@PathVariable("id") int id, Team team){System.out.println("update ~~~~~~");for (Team team1 : teamList) {if(team1.getId()==id){team1.setName(team.getName());team1.setAddress(team.getAddress());return 1;}}return 0;}
$("#put").click(function(){$.ajax({type: "POST",url: "/team/"+$("#teamId").val(),data: $("#myForm").serialize()+"&_method=PUT",dataType: "json",success:function(data){$("#result").html(data==1?"修改成功":"修改失败");}});});
11.5.4 删除
@RequestMapping(value = "team/{id}", method = RequestMethod.DELETE)@ResponseBodypublic int delete(@PathVariable("id") int id) {System.out.println("delete ~~~~~~");for (Team team1 : teamList) {if (team1.getId() == id) {teamList.remove(team1);return 1;}}return 0;}
$("#delete").click(function(){$.ajax({type: "POST",url: "/team/"+$("#teamId").val(),data: "&_method=DELETE",dataType: "json",success:function(data){$("#result").html(data==1?"删除成功":"修改失败");}});});
11.5.5 注意
对于更新和删除 PUT 和 DELETE, 需要在请求的 url 中加上 &_method=PUT 或者 &_method=DELETE
然后在 web.xml 中进行相应过滤器的配置
<filter><filter-name>hiddenHttpMethodFilter</filter-name><filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class></filter><filter-mapping><filter-name>hiddenHttpMethodFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
