SpringMVC概述
1. SpringMVC概念
SpringMVC是Spring3.0发布以后内置的一个MVC框架,解决WEB开发过中常见的问题,与SPring无缝集成,支持RESTful风格的URL请求,采用松散耦合可插拔组件结构,比其他MVC框架更具扩展性和灵活性。
2. SpringMVC原理
SpringMVC就是对Servlet进行深层次的封装
3. 优势
基于MVC分工明确、轻量级、能够使用Spring的Ioc和AOP、方便整合其他优秀框架
4. MVC模式回顾
Model:模型层 javaBean、数据访问和业务处理 dao service pojo
View:视图 JSP技术,负责展示数据
Controller:控制器 servlet技术 中间调度
控制器的工作:1.接收客户端的请求 2.调用模型层中的业务逻辑处理 3.页面导航
5.实例
- 创建maven工程,补齐目录结构
- 添加依赖 SpringMVC、Servlet
- 创建Spring和SpringMVC配置文件,添加包扫描,SpringMVC注解驱动,视图解析器,静态文件处理器…
- 在web.xml中进行Spring和SpringMVC配置
- 创建控制器
- 创建jsp页面
- 测试
SpringMVC工作流程
SpringMVC工作流程图
- 用户通过浏览器发送请求到前端处理器DispatcherServlet
- 前端处理器直接将请求转发给处理器映射器HandleMapping
- 处理器映射器会根据请求, 找到负责处理器该请求的处理器, 并将其封装为处理器执行链handerExecutionChain返回给前端控制器
- 前端处理器会根据处理器执行链找到对应的处理器适配器HandleAdaptor
- 处理器适配器调用执行处理器Controller
- 处理器将处理结果和要跳转的视图封装到ModelAndView对象中返回
- 适配器将ModleAndView返回给前端控制器
- 前端控制器将ModelAndView传给视图解析器ViewResolve
- 视图解析器根据视图名称封装为视图对象View返回
- 前端控制器调用视图对象让其进行渲染
- 视图对象将渲染结果返回
- 前端控制器响应给客户浏览器
SpringMVC组件
**DispatcherServlet: ** 前端控制器, 中央空控制器, 用户请求的入口控制器, 它的存在降低了组件之间的耦合性.
**HandleMapping: ** 处理器映射器, 负责根据用户请求找到处理器Controller, SpringMVC提供了不同的映射器映射方式, 例如: 配置文件方式, 实现接口方式, 注解方式 (实际开发中最常用)
**Handler(Controller): ** 具体的用户请求处理器, 涉及到业务请求, 程序员根据业务需求开发
**HandleAdaptor: ** 处理器适配器, 适配器的应用, 可通过扩展处理器适配器, 支持更多类型的处理器
**ViewResolver: ** 视图解析器, 根据逻辑视图名解析成物理视图路径, 封装为View视图对象, 视图类型包括: jstlView、freemarkerView、pdfView 等.
@RequestMapping注解
注解使用规则
ElementType.TYPE 和 ElementType.METHOD 指定该注解可定义类上, 也可以定义在方法上, 定义在类上表示全局路径, 定义在方法上表示具体的路径
测试
常用的提交方式
请求方式 | 提交方式 |
---|
地址栏请求 | get请求 | 超链接请求 | get请求 | 表单请求 | 默认get, 可以指定post | AJAX请求 | 默认get, 可以指定post |
DispatcherServlet的url-pattern解析
web.xml中配置SpringMVC地址解析中一般有两种写法
-
*.do 没有特殊要求的请情况下, DispatcherServlet一般常使用的后缀 -
/ (推荐这么写, 在使用地址栏传值时必须这么写) , 可以写成/, 但在静态资源请求时DispatcherSerlvet会当成一个普通的Controller请求, 前端控制器会调用处理器映射器查找响应的处理器, 当然找不到, 会报404错误, 因此必须在springmvc.xml中配置静态资源处理器, 两种方法:
处理器方法的参数
1. 参数名称匹配接收
参数的名称要和前端请求的参数name保持一致, 数值类型可以自动转换
(若不一致匹配失败, 则以null进行参数填充)
前端提交表单:
后端处理器:
2. 接收日期类型的参数
日期类型的参数需添加@DateTimeFormat注解进行转换
(若转错误会报400错误)
前端请求
后端处理器
3. 使用对象接收参数
接收对象 的属性名称要和请求的参数名称name保持一致
(若不一致匹配失败, 则以null进行参数填充)
POJO实体对象
后端处理器
前端请求
4. 矫正参数名称
通过注解@RequestParam进行矫正, value属性填入矫正后的名称
required属性填true(默认),矫正失败会报400错误 , required属性填false, 矫正失败不报错, 以null填充参数
前端请求
后端处理
5. 通过URL地址传参
URL地址传参需要@RequestMapping中的value属性使用/{name} 进行匹配
URL可以匹配多个地址
@PathVariable
- value属性与@RequestMapping中括号名称一致
- required属性为true(默认): 表示必须匹配此参数, 没有则报500错 false: 非必须匹配, 没有则用null填充
URL匹配参数失败, 报404错
前端请求
后端处理器
6. 使用原生HttpServletRequest
接收单个参数用getParameter()方法
接收数组用getParameterValues()方法
7. 接收数组类型参数
1. 通过匹配参数名称
2. 通过原生HttpServletRequest
前端请求
后端处理器
8. 接收集合类型参数
1. 简单集合类型通过@RequestParam注解绑定
2. 自定义对象的集合类型不支持直接获取, 必须封装到一个类中, 作为一个属性操作
前端请求
POJO实体类
封装的中间VO对象
后端处理器
乱码解决方案
SpringMVC中有同意字符集的过滤器对象CharacterEncodingFilter, 在web.xml中配置过滤器filter
源码解析
- 如果设置了encoding, 没有强制request或者response使用, 则使用自行设置的encoding
- 如果设置了encoding, 并且强制request或者response使用, 则自行设置的encoding失效, 使用web.xml中配置的encoding
在静态资源请求中存在乱码问题
-
若使用Web应用服务器的默认处理器可能会出现乱码, 因此尽量改为Spring专用的静态资源处理器<mvc:resource> -
过滤器的url-pattern明确指明过滤的路径, 排除静态html路径即可避免
处理方法的返回值
1. 返回ModelAndView
@RequestMapping("modelandview")
public ModelAndView modelAndView(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("result/index");
modelAndView.addObject("result", new Result(200, "经过ModelAndView返回", null));
return modelAndView;
}
如果是前后端不分的开发, 大部分情况下, 使用ModelAndView, 既有数据的携带还有资源的跳转
如果只是需要传递数据或者跳转之一, ModelAndView并不是最好的选择
2. 返回String
视图解析器会将要跳转的视图名称转换为物理路径
视图解析器
@RequestMapping("string")
public String string(HttpServletRequest request){
Result result = new Result(200, "经过String返回", null);
request.setAttribute("result", result);
return "result/index";
}
3. 返回对象类型
1. 当处理器返回Object对象时, 仅作为数据返回展示, 不进行视图跳转, 一般前端发起Ajax使用
2. 返回对象的时候, 需要使用@ResponseBody 注解, 将转换后的JSON数据放入到响应体中
pom.xml中添加两个依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
前端ajax请求
后端处理器
@ResponseBody
@RequestMapping("obj")
public Object obj(){
Result result = new Result(200, "经过Object返回", null);
return result;
}
void返回值
void不是没有返回数据, 是通过HttpServletRequest, sevlet中的处理方案
导航页面
页面的导航分为两种: 1. 转发 2. 重定向
转发一个页面
字符串方式转发
@RequestMapping("forwardstring")
public String string(HttpServletRequest request){
Result result = new Result(200, "通过String转发", null);
request.setAttribute("nav", result);
return "forward:/jsp/nav/index.jsp";
}
ModelAndView方式转发
@RequestMapping("forwardmodelandview")
public ModelAndView modelAndView(){
ModelAndView modelAndView = new ModelAndView();
Result result = new Result(200, "通过ModelAndView转发", null);
modelAndView.addObject("nav", result);
modelAndView.setViewName("forward:/jsp/nav/index.jsp");
return modelAndView;
}
重定向一个页面
字符方式重定向
@RequestMapping("redirectstring")
public String redirectstring(HttpServletRequest request){
request.setAttribute("nav", "通过String重定向");
return "redirect:/jsp/nav/index.jsp";
}
ModelAndView方式重定向
@RequestMapping("redirectmodelandview")
public ModelAndView redirectmodelandview(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("nav", "通过ModelAndView转重定向");
modelAndView.setViewName("redirect:/jsp/nav/index.jsp");
return modelAndView;
}
重定向或者转发到控制器
1. ModelAndView转发到处理器, 通过request直接传参
2. Spring转发到处理器, 通过request直接传参
3. ModelAndView重定向处理器, 通过request直接传参, 但只能传字符串
@RequestMapping("forwardtohandle")
public ModelAndView forwardtohandle(){
ModelAndView modelAndView = new ModelAndView();
Result result = new Result(200, "通过ModelAndView转发到处理器", null);
modelAndView.addObject("nav", result);
modelAndView.setViewName("forward:/nav/converge");
return modelAndView;
}
@RequestMapping("forwardtohandle2")
public String forwardtohandle2(HttpServletRequest request){
Result result = new Result(200, "通过ModelAndView转发到处理器2", null);
request.setAttribute("nav", result);
return "forward:/nav/converge";
}
@RequestMapping("redirecttohandle")
public ModelAndView redirecttohandle(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("nav", "通过ModelAndView重定向到处理器,参数只能是字符串");
modelAndView.setViewName("redirect:/nav/converge");
return modelAndView;
}
@RequestMapping("converge")
public String converge(HttpServletRequest request){
System.out.println("request: " + request.getAttribute("nav"));
String nav2 = request.getParameter("nav");
System.out.println("request param: " + nav2.toString());
if(nav2 != null)
request.setAttribute("nav", nav2);
return "forward:/jsp/nav/index.jsp";
}
异常处理
@ExceptionHandler注解
@ExceptionHandler注解是将一个方法指定为异常处理方法
被注解的方法也是一个处理器Controller, 方法的参数可以是Exception及其子类对象, 系统会自动为这些方法参数赋值
@ControllerAdvice
增强Controller, 可实现全局异常捕获处理
@ControllerAdvice
public class MyGlobalExceptionHandle {
@ExceptionHandler(value = {Exception.class})
public ModelAndView globalExceptions(Exception e){
ModelAndView mv = new ModelAndView();
mv.addObject("message", e.getMessage());
mv.setViewName("myerr");
return mv;
}
}
自定义异常
自定义异常包, 类
抛出自定义异常
@RequestMapping("err")
public String err(Integer num){
System.out.println(1/num);
return "err/ok";
}
@RequestMapping("iderr")
public String iderr(Integer id) throws IdErr {
if(id < 10)
throw new IdErr("id不能小于10");
return "err/ok";
}
@RequestMapping("nameerr")
public String nameerr(String name) throws IdErr, NameErr {
if("".equals(name))
throw new NameErr("name不能为空串");
return "err/ok";
}
异常处理器
@ControllerAdvice
public class MyGlobalExceptionHandle {
@ExceptionHandler(value = {Exception.class})
public ModelAndView globalExceptions(Exception e){
ModelAndView mv = new ModelAndView();
mv.addObject("message", e.getMessage());
mv.setViewName("err/err");
return mv;
}
@ExceptionHandler(value = {IdErr.class})
public ModelAndView idErr(IdErr e){
ModelAndView mv = new ModelAndView();
mv.addObject("message", e.getMessage());
mv.setViewName("err/err");
return mv;
}
@ExceptionHandler(value = {NameErr.class})
public ModelAndView nameErr(NameErr e){
ModelAndView mv = new ModelAndView();
mv.addObject("message", e.getMessage());
mv.setViewName("err/err");
return mv;
}
}
拦截器
SpringMVC中的拦截器主要作用就是拦截用户请求, 进行相应的预处理和后处理
三种方法的执行时间
preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
- 执行时间: 在Controller执行前拦截, 若返回true继续执行Controller, 返回false则不执行后续
- 使用场景: 登陆验证
postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
- 执行时间: 在Controller执行之后, 可修改ModelAndView
- 使用场景: 日志记录, 登陆IP记录, 时间记录
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
- 执行时间: View视图已经渲染完成之后, 修改ModelAndView无效
- 使用场景: 全局资源的操作, 清除资源等
注: 存在多个拦截器时, 预处理顺序, 后处理逆序
创建拦截器
实现接口 HandlerInterceptor
重写三个方法
springmvc.xml中进行拦截器配置
mapping: 匹配要拦截的路径, 最后为**
bean: 指定执行的拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.jumaojiang.interceptor.MyInterceptor2"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.jumaojiang.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
文件上传下载
SpringMVC为文件上传提供直接支持, 通过MultipartResolver, 实现类CommonsMultipartResolver
实例步骤
文件上传
- 添加依赖
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
- springmvc.xml中配置MultipartResolver
注: id一定要填multipartResolver
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
- 前端表单提交
注: 1. 类型enctype=“multipart/form-data” 2. method="post"
- 创建后端文件上传处理器
UUID.randomUUID() 方法获取随机值给文件重命名(一开发都需要, 避免冲突)request.getServletContext().getRealPath("/uploadFile") 创建存储路径在项目运行的web容器下file.transferTo() 存储文件
@RequestMapping("upload")
public String upload(HttpServletRequest request, MultipartFile file) throws IOException {
String reName = FileRenameUtil.fileRename(file);
String realPath = request.getServletContext().getRealPath("/uploadFile");
File path = new File(realPath);
if(!path.exists())
path.mkdir();
file.transferTo(new File(path,reName));
System.out.println("上传成功: " + realPath + "/" + reName);
request.setAttribute("file", realPath + "/" + reName);
return "file/index";
}
文件下载
- 前端表单提交
enctype=“multipart/form-data” method="post"
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wHB6Ez3r-1631961564520)(…/AppData/Roaming/Typora/typora-user-images/image-20210917204359970.png)]
- 后端处理器
- 创建响应头信息
- 标记以流的方式做出响应
- 以附件的形式响应给用户
- 响应数据
@ResponseBody
@RequestMapping("download")
public ResponseEntity<byte[]> download(HttpServletRequest request) throws Exception {
String file = request.getParameter("file");
if(! new File(file).exists())
throw new Exception("指定路径的文件不存在");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
String fileName = URLEncoder.encode(file.substring(file.lastIndexOf("/") + 1), "utf-8");
httpHeaders.setContentDispositionFormData("attachment", fileName);
File file1 = new File(file);
ResponseEntity<byte[]> responseEntity =
new ResponseEntity<>(FileUtils.readFileToByteArray(file1), httpHeaders, HttpStatus.CREATED);
return responseEntity;
}
限定文件的类型和大小
限定文件的类型
创建文件类型拦截器
- 判断请求是否是文件上传
- 遍历所有上传文件
- 判断是否是允许上传类型之一, 不存则则抛出异常
springmvc.xml配置拦截器
<mvc:interceptor>
<mvc:mapping path="/file/**"/>
<bean class="com.jumaojiang.interceptor.FileInterceptor"></bean>
</mvc:interceptor>
限定文件大小
springmvc.xml中配置MultipartResolver
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="5242880"/>
<property name="defaultEncoding" value="utf-8"/>
</bean>
RESTful风格
REST(Representational State Transfer, 简称REST)
它是一种软件架构风格, 设计风格, 提供了一组设计原则和约束条件
使用RESTful操作资源:
GET /expresses #查询所有的快递信息列表
GET /express/1006 #查询一个快递信息
POST /express #新建一个快递信息
PUT /express/1006 #更新一个快递信息(全部更新)
PATCH /express/1006 #更新一个快递信息(部分更新)
DELETE /express/1006 #删除一个快递信息
API设计/URL设计
RESTful的核心思想就是客户端的用户发出的数据操作指令都是"动词 + 宾语" (一眼看到请求的参数就大致知道需要做什么)
1.根据 HTTP 规范, 动词一律大写
2.一些代理只支持POST和GET方法, 为了使用这些有限的方法支持RESTful API, 需要一种办法覆盖原来的方法. 使用定制的HTTP头 X-HTTP-Method-Override来覆盖POST方法
- 宾语就是API的URL, 应该只能是名词, 不能包含动词
- 比如/expresses这个URL是正确的 /getExpress包含了动词, 不推荐
避免多级URL
例如: 查询所有还未取出的快递:
GET /expresses/statu/false #不推荐
GET /expresses?statu=false #推荐
HTTP状态码
客户端每发出一次响应, 都会返回HTTP状态码和数据
状态码三位数, 分成五个类别
五类状态码分别如下:
1xx: 相关信息 (API不需要,直接忽略)
2xx: 操作成功
3xx: 资源重定向
4xx: 客户端错误
5xx: 服务器错误
2xx状态码
GET: 200 OK #表示一切正常
POST: 201 Created #表示新的资源已经成功创建
PUT: 200 OK
PATCH: 200 OK
DELETE: 204 No Content #表示资源已经成功删除
3xx状态码
API用不到301状态码(永久重定向)和302,307状态码(都表示暂时重定向), API级别可以不考虑这两种情况
API用到的3xx状态码主要有: 303 See Other 表示参考另一个URL, 用于POST PUT DELETE 请求, 收到303 以后, 浏览器不会自动跳转, 而会上用户自己决定下一步怎么办
#只需要关注304状态码就可以了
304: Not Modified #客户端使用缓存数据
4xx状态码
400 Bad Request: 服务器不理解客户端的请求, 未做任何处理
401 Unauthorized: 用户未提供身份验证凭据, 或者没有通过身份验证
403 Forbidden: 用户通过了身份验证, 但是不具有访问资源所需的权限
405 Method Not Allowed: 用户已经通过身份验证, 但是所用的HTTP方法不在他的权限之内
410 Gone: 所请求的资源已从这个地址转移, 不再可用
415 Unsupported Media Type: 客户端要求的返回格式不支持. 比如, API 只能返回JSON格式, 但是客户端要求返回XML格式
422 Unprocessable Entity: 客户端上传的福建无法处理, 导致请求失败
429 Too Many Requests: 客户端的请求次数超过限额
5xx状态码
表示服务器端错误, 一般来说, API不会向用户透露服务器的详细信息, 所以状态码只要两个就够了
500 Internal Server Error: 客户的短请求有效, 服务器处理时发生了意外
503 Service Unavailable: 服务器无法处理请求, 一般用于网站维护状态
服务器响应
服务器返回的信息建议选择JSON对象, 这样才能返回标准的结构数据
- 服务器回应的HTTP头的
Content-Type 属性要设为appliation/json - 客户端请求时, 也要明确告诉服务器可以接收JSON格式, 请求的HTTP头的
ACCEPT 属性也要设成application/json - 当发生错误的时候, 除了返回状态码之外, 也要返回错误信息, 所以要自己封装要返回的信息
封装响应结果
public class AjaxResultVo<T> {
private Integer code;
private String msg;
private Object obj;
private List<T> list;
public AjaxResultVo() {
this.code = 200;
this.msg = "ok";
this.obj = null;
this.list = null;
}
public AjaxResultVo(Integer code, String msg) {...}
public AjaxResultVo(Integer code, String msg, List<T> list) {...}
public AjaxResultVo(Integer code, String msg, Object obj) {...}
...
}
RESTful风格的更新和删除问题
问题:
在Ajax中,采用Restful风格PUT, PATCH和DELETE请求传递参数无效, 传递到后台的参数值为null
产生的原因:
Tomcat封装请求的过程:
1.将请求体中的数据封装的成map
2.request.getParameter(key)会从map中取值
3.SpringMVC会从request.getParamter()中取值
AJAX发送PUT, PATCH, DELETE请求时, Tomcat不会封装成map, 导致最终SpirngMVC拿不到值,为null
解决方案:
使用HiddenHttpMethodFilter将普通的post请求转换为指定的PUT, PATCH, DELETE请求
前端部分
- 使用POST类型
- 参数中添加_method属性, 赋值为PUT / PATCH / DELETE
web.xml中配置过滤器filter
<filter>
<filter-name>httpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>httpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
|