Spring MVC
一、Spring MVC流程
Spring MVC的流程是围绕 DispatcherServlet 而工作的,所以在 Spring MVC 中 DispatcherServlet 就是其最重要的内容 。 在 DispatcherServ let 的基础上,还存在其他的组件, 掌握流程和组件就是 SpringMVC 开发的基础。 关于Spring MVC的流程如下图所示
首先,在 Web 服务器启动的过程中,如果在 Spring Boot 机制下启 用 Spring MVC , 它就开始初始化一些重要的组件,如 DispactherServlet、 HandlerAdapter 的实现类 RequestMappingHandlerAdapter等组件对 象 。 关于这些组件的初始化,我们可以 看到 spring-webmvc-xxx.jar 包的属性文件DispatcherServlet.properties, 它定义的对象都是在 SpringMVC 开始时就初始化,并且存放在 Spring IoC容器中
其次是开发控制器(Controller)
package com.demo.controller
@Controller
@RequestMapping("/user")
public class UserController{
@AutoWired
private UserService userService=null;
@RequestMapping("details")
public ModelAndView details(Long id){
User user=Uservice.getUser(id);
ModelAndView mv=new ModelAndView();
mv.setViewName("user/details");
mv.addObject("user",user);
return mv;
}
}
@Controller 表明这是一个控制器,然后@RequestMapping 代表请求路径和控制器(或其方法)的映射关系,它会在 Web 服务器启动 Spring MVC 时,就被扫描到 HandlerMapping 的机制中存储,之后在用户发起请求被 DispatcherServlet拦截后,通过 U阳和其他的条件 , 通过 HandlerMapper机制就能找到对应的控制器(或其方法)进行响应 .是通过 HandlerMapping 返回的 是一个HandlerExecutionChain 对象
HandlerExecutionChain对象包含一个处理器(handler),这里的处理器是对控制器(controller)的包装,因为我们的控制器方法可能存在参数,那么处理器就可以读入HTTP和上下文的相关参数,传递给控制器方法。而在处理器包含了控制器方法的逻辑。此外还有处理器的拦截器(interceptor),这样就能够通过拦截器进一步的增强处理器的功能。
得到了处理器( handler ),还需要去运行,但是我们有普通 HTTP 请求,也有按 BeanName 的请求,甚至是 WebSocket 的请求,所以它还需要一个适配器去运行 HandlerExecutionChain 对象包含的处理器,这就是 HandlerAdapter 接口定义的实现类 。HttpRequestHandlerAdapter 是最常用的 HandlerAdapter 的实现类。通过请求的类型,DispatcherServlet 就会找到它来执行 Web 请求的 HandlerExecutionChain 对象包含的内容,这样就能够执行我们的处理器( handler)了 。
在处理器调用控制器时,它首先通过模型层得到数据,再放入数据模型中,最后将返回模型和视图( ModelAndView )对象,这里控制器设置的视图名称设置为“ user/details”,这样就走到了视图解析器( ViewResolver ),去解析视图逻辑名称了。
可以在application.properites.进行配置。
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
通过修改这样的配置,就能在 Spring Boot 的机制下定制InternalResourceViewResolver 这个视图解析器的初始化,也就是在返回视图名称之后,它会以前缀( prefix )和后缀( suffix )以及视图名称组成全路径定位视图 。视图解析器定位到视图后,视图的作用是将数据模型( Model )渲染,这样就能够响应用户的请求。这一步就是视图将数据模型植染( View )出来,用来展示给用户查看。按照我们控制器的返回,就是/WEB-INF/jsp/user/details .jsp 作为我们的视图
<% @ page pageEncoding="UTF- 8" %>
<% @ taglib prefix="c" uri= "http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title >用户详情</title>
</head>
<body>
<center>
<table border=”1 ” >
<tr>
<td>标签</td>
<td>值</td>
</tr>
<tr>
<td>用户编号</td>
<td><c:out value="${user.id}"></ c : out></td>
</tr>
<tr>
<td>用户名称</td>
<td><c:out value="${user.userName}"></c:out></td>
</tr>
<tr>
<td>用户备注</td>
<td><c:out value="${user.note}"></c:out></td>
</tr>
</table>
</center>
</body>
</html>
spring boot启动文件
@SpringBootApplication(scanBasePackages="com.demo")
@MappperScann(basePackages="com.demo",
annotationClass=Repository.class)
public class DemoApplication{
public static void main(String []args){
SpringApplication.run(DemoApplication.class,args);
}
}
二、处理器映射器
果 Web 工程使用 了 Spring MVC , 那么它在启动阶段就会将注解@RequestMapping 所配置的 内 容保存到处理器映射( HandlerMapping ) 机制 中去 , 然后等待请求的到来,通过拦截请求信息与 HandlerMapping 进行匹配,找到对应的处理器(它包含控制器的逻辑) , 并将处理器及其拦截器 保 存 到 HandlerExecutionChain 对 象中 , 返回给 DispatcherServlet ,这样DispatcherServlet 就可以运行它们 了。 从论述 中可以看 到, HandlerMapping 的主要任务是将请求定位到具体的处理器上 。
@RepuestMapping的源码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.web.bind.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
//配置请求映射名称
String name() default "";
//通过路径映射
@AliasFor("path")
String[] value() default {};
//通过路径映射回path配置项
@AliasFor("value")
String[] path() default {};
//限定只响应HTTP请求类型,如GET、POST、HEAD、OPTIONs、PUT、TRACE等
//默认的情况下,可以相应所有的请求类型
RequestMethod[] method() default {};
//等存在对应的HTTP参数时才响应请求
String[] params() default {};
//限定请求头存在对应的参数才响应
String[] headers() default {};
//限定HTTP请求提交类型,如“application/json”,"text/html"
String[] consumes() default {};
//限定返回类型,仅当HTTP请求头中的(Accept)类型中包含指定参数类型时才返回
String[] produces() default {};
}
- value和path来设置请求的URL
- method限定HTTP的请求类型,为简化method配置项的配置,新增了@GetMapping 、@PostMapping、@PatchMappiing、@PutMapping、@DeleteMapping。可以看出@GetMapping对应的是HTTP的GET方法,@PostMapping 对应的是 Hπp 的 POST 方法。
三、获取控制器参数
处理器是对控制器的包装 ,在处理器运行的过程中会调度控制器的方法,只是
它在进入控制器方法之前会对 HTTP 的参数和上下文进行解析,将它们转换为控制器所需的参数。
1.在无注解下获取参数
在没有注解的情况下,springMVC 也可以获取参数,且允许参数为空,唯一的要求是参数名称和HTTP请求的参数名称保持一致。
package com.demo.cotroller;
@RequestMapping("/my")
@Controller
public class MyController{
@GetMapping("/no/annotation")
@ResponseBody
public Map<String,Object> noAnnotation(Integer intVal,Long longVal,String str){
Map<String,Object> paramsMap=new HashMap();
paramsMap.put("intVal",intVal);
paramsMap.put("longVal",longVal);
paramsMap.put("str",str);
return paramsMap;
}
}
浏览器输入
http : //localhost : 8080lmylnolannotation ?intVal=10&longVal=200
从代码中可以看出控制器方法参数中还有一个字符串参数 str,但因为参数在默认的规则下可以为空 ,所以这个请求并不会报锚,因为方法标注了@ResponseBody ,所以控制器返回的结果就会转化为 JSON 数据集。
2.使用@RequestParam获取参数
SpringMVC提供了@RequestParam来确定前后端参数的映射关系
package com.demo.cotroller;
@RequestMapping("/my")
@Controller
public class MyController{
@GetMapping("/annotation")
@ResponseBody
public Map<String,Object> requestParam(
@RequestParam("int_val")Integer intVal,
@RequestParam("long_val")Long longVal,
@RequestParam("str_val")String str){
Map<String,Object> paramsMap=new HashMap();
paramsMap.put("intVal",intVal);
paramsMap.put("longVal",longVal);
paramsMap.put("str",str);
return paramsMap;
}
}
在浏览器地址栏输入
http://localhost:8080/my/annotation?int_val=l & long_va1=2 & str_val=str
就能够看到请求的结果了 。 但如果把 3 个 HTTP 参数中的任意一个删去,就会得到异常报锚的信息,因为在默认的情况下@RequestParam 标注的参数是不能为空的
3.传递数组
SpringMVC中可以传递数组
@GetMapping("/requestArray")
@ResponseBody
public Map<String,Object> requestArray(
int[] intarr,
Long[] longArr,
String[] strArr){
Map<String,Object> paramsMap=new HashMap();
paramsMap.put("intarr",intarr);
paramsMap.put("longArr",longArr);
paramsMap.put("strArr",strArr);
return paramsMap;
}
h忧p ://localhost:8080/my/requestArray?intArr= l ,2,3& longArr=4 , 5,6&strr= str 1 ,str2,str3
可以 看 到 需要传递数组参数时, 每个参数的数组元素只需要通过逗号分隔即可 。
4.传递JSON(@RequestBody)
在当前前后端分离 的趋势下 ,使用 JSON 已经是十分普遍了 。 对于前端的页面或者于机应用,可以通过请求后端获取 JSON 数据集,这样它们就能很方便地将数据渲染到视图中 。 有时前端也需要提交较为复杂的数据到后端,为了更好组织和提高代码的可读性 , 可 以将数据转换为 JSON 数据集 ,通过 HTTP 请求体提交给后端 , 对此 SpringMVC 也提供了良好的支持 。
先搭建一个表单
<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<heand>
<meta charset="UTF-8">
<title>新增用户</title>
<script src="https://code.jquery.com/jquery-3.2.0"></script>
<script type="text/javascript">
$(document).ready(function(){
$("#submit").click(function(){
var userName=$("#userName").val();
var note=$("#note").val();
if($.trim(userName)==''){
alert("用户名不能为空")
return;
}
var params={
userName:userName,
note:note
};
$.post({
url="./insert",
contentType:"application/json";
data:JSON.Stringify(params),
successs:function(result){
if(result==null||result==null){
alert("插入失败")
return;
}
alert("插入成功")
}
})
});
});
</script>
</heand>
<body>
<div style="margin:20px 0;"></div>
<form id="insertForm">
<table>
<tr>
<td>用户名称</td>
<td><input id="userName" name="userName"</td>
</tr>
<tr>
<td>备注</td>
<td><input id="note" name="note"</td>
</tr>
<tr>
<td>用户名称</td>
<td align=right><input id="submit" type="button" value="提交"</td>
</tr>
</table>
</form>
</body>
</html>
这里定义了 一个简易的表单,它使用了 jQuery 进行 Ajax 提交。注意到加粗的代码,它指定了提交的请求地址 Curl )、数据( data )、提交类型( contentType )和事后事件( success ) 。 从脚本来看,这里先组织了一个 JSON 数据集, 而且把提交类型也设置为了 JSON 类型 ,然后才提交到控制器。这样控制器就可以得到一个 JSON 数据集的请求体了
为了打开这个表单,需要在 UserController 中编写一个 add 方法,它将返回一个字符串 , 映射到这个表单上,这样就能通过视图解析器( ViewResolver)找到它了 。然后再写一个相应新增用户的请求 insert 方法,它将从 HTTP 请求体中读出这个 JSON
package com.deml.controller
@Controller
@RequestMapping("/user")
public class UserController{
@Autowired
private UserService userService=null;
@GetMapping("/add")
public String add(){
return "/user/add";
}
@PostMapping("/insert")
@ResponseBody
public User insert(@RequestBody User user){
userService.insettUser(user);
return user;
}
}
接着录入表单 ,点击提交按钮, 这样通过 JavaScript 脚本提交 JSON 消息 , 就可以请求到控制器的 insert 方法 。 这个方法的参数标注为@RequestBody , 意味着它将接收前端提交的 JSON 请求体,而在 JSON 请求体与 User 类之间的属性名称是保持一致的,这样 Spring MVC 就会通过这层映射关系将 JSON 请求体转换为 User 对象
@RequestBody 标注在参数上,表示接收的是前端的JSON请求,同时会实现Json请求体到实参的转化
5.通过URL传递参数(@PathVariable)
SpringMVC 对此也提供 了 良好的支持 ,可以通过处理器映射和注解@PathVariable 的组合获取 URL 参数。首先通过处理器映射可以定位参数的位置和名称,而@PathVariable 则可以通过名称来获取参数
@GetMapping("/{id}")
@ResponseBody
public User get(@PathVariable("id")Long id){
return userService.getUser(id);
}
代码中首先通过@GetMapping 指定一个 URL , 然后用 {...}来标明参数的位置和名称。这里指定名称为 id , 这样 Spring MVC 就会根据请求去匹配这个方法。@PathVariable 配置的字符串为 id
6.获取格式化数据
在一些应用中往往需要格式化数据,其中最为典型的当属日期和货币。
springMVC也对此提供了支持@DataTimeFormat和@NumberFormat
@GetMapping ("/format/form")
public String showFormat () {
return "/format/formatter";
}
//获取提交参数
@PostMapping( "/format/commit")
@ResponseBody
public Map<String,Object> format(
@DateTimeFormat(iso=ISO.DATE) Date date ,
@NumberFormat(pattern ="# ,### .##") Double number ) {
Map<String , Object> dataMap =new HashMap<>() ;
dataMap .put ("date", date) ;
dataMap . put ("number", number) ;
return dataMap;
}
四、自定义参数转换规则
SpringMVC 提供的处理器会先以一套规则来实现参数的转换,在开发自定义转换规则时,就很有必要掌握这套转换规则了 。 而实际上处理器的转换规则还包含控制器返回后的处理,只是这节先讨论处理器是如何获取和转换参数的内容,其他的则留到后面再讨论,到时会揭开为什么使用注解@ResponseBody标注方法后,就能够把控制器返回转变为 JSON 数据集的秘密。
1.处理器获取参数逻辑
当一个请求来到时,在处理器执行的过程中,它首先会从HTTP请求上下文环境来得到参数。如果是简单的参数它会以简单的转发器进行转换,而这些简单的转发器是Spring MVC自身已经提供了的。但是如果是转化HTTP请求体(Body),它就会调用HttpMessageConverter接口的方法对请求体的信息进行转换。首先会先判断是否能对请求体进行转化,如果可以就会转化为Java类型
package org.springframework.http.converter;
import java.io.IOException;
import java.util.List;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
public interface HttpMessageConverter<T> {
//是否可读,其中var1为Java类型,var2为HTTP请求类型
boolean canRead(Class<?> var1, @Nullable MediaType var2);
//判断var1类型能否转化为var2类型,其中var1为Java类型,var2为HTTP请求类型
boolean canWrite(Class<?> var1, @Nullable MediaType var2);
//可支持的媒体类型列表
List<MediaType> getSupportedMediaTypes();
//当canRead验证通过后,读入HTTP请求信息
T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException;
//当canWrite方法验证通过后,写入响应
void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;
}
之前代码中控制器方法的参数标注了@RequestBody,所以处理器会采用请求体( Body )的 内容进行参数转换,而前端的请求体为 JSON 类型,所以首先它会调用 canRead 方法来确定请求体是否可读 。 如果判定可读后,接着就是使用 read 方法,将前端提交的用户 JSON 类型的请求体转换为控制器的用户( User ) 类参数,这样控制器就能够得到参数了。
上面的 HttpMessageConveter 接口只是将 HTTP 的请求体转换为对应的 Java 对象,而对于 HTTP参数和其他内容,还没有进行讨论
为了讨论自定义的参数规则,很有必要先了解处理器转换参数的过程 。 在 Spring MVC 中, 是通过 WebDataBinder 机制来获取参数的 ,它的主要作用是解析 HTTP 请求的上下文, 然后在控制器的调用之前转换参数并且提供验证的功能,为调用控制器方法做准备 。 处理器会从 HTTP 请求中读取数据,然后 通过 三 种接口来进行各类参数转换,这 三 种接口是 Converter 、 Formatter 和GenericConverter 。 在 Spring MVC 的机制中这三种接 口的实现类都采用了注册机的机制, 默认的情况下 SpringMVC 己经在注册机内注册了许多的转换器,这样就可以实现大部分的数据类型的转换 , 所以在大部分的情况下无须开发者再提供转换器,这就是在上述章节中可以得到整型( Integer ) 、 长整型( Long )、字符串 C String )等各种各样参数的原因。同样地,当需要自定义转换规则时, 只需要在注册机上注册自己的转换器就可以了
WebDataBinder 机制还有一个重要的功能,那就是验证转换结果。关于验证机制,后面会再讨论。有了参数的转换和验证,最终控制器就可 以得到合法的参数。得到这些参数后,就可以调用控制器的方法了 。下图展示的是 HTTP 请求体( Body )的消息转换全流程图。
对于数据类型转换, SpringMVC 提供了 一个服务机制去管理,它就是 ConversionService 接口 。在默认的情况下,会使用这个接口的子类 DefaultFormattingConversionService 对象来管理这些转换类, 即注册机
在 Spring Boot 中还提供了特殊的机制来管理这些转换器。 Spring Boot 的 自动配置类 WebMvcAutoConfiguration 还定义了 一个内部WebMvcAutoConfigurationAdapter 其源码为
public void addFormatters(FormatterRegistry registry) {
Iterator var2 = this.getBeansOfType(Converter.class).iterator();
while(var2.hasNext()) {
Converter<?, ?> converter = (Converter)var2.next();
registry.addConverter(converter);
}
var2 = this.getBeansOfType(GenericConverter.class).iterator();
while(var2.hasNext()) {
GenericConverter converter = (GenericConverter)var2.next();
registry.addConverter(converter);
}
var2 = this.getBeansOfType(Formatter.class).iterator();
while(var2.hasNext()) {
Formatter<?> formatter = (Formatter)var2.next();
registry.addFormatter(formatter);
}
}
通过这个方法,可以看到在 Spring Boot 的初始化中 , 会将
对应用户自定义的 Converter、 Formatter 和 GenericConverter 的实现类所创建的 Spring Bean 自动地注册到 DefaultForma忧ingConversionService 对象中 。 这样对于开发者只需要自定义 Converter 、 Formatter和 GenericConverter 的接口 的 Bean, Spring Boot 就会通过这个方法将它们注册到 ConversionService对象中
2.一对一转换器(Converter)
Converter是一对一转发器,也就是从一种类转化为另一种类型.接口为
package org.springframework.core.convert.converter;
import org.springframework.lang.Nullable;
@FunctionalInterface
public interface Converter<S, T> {
@Nullable
//S为源类型,T为目标类型
T convert(S var1);
}
自定义转换器
package com.demo.converter
@Component
public class StringToUserConverter implements Converter<String ,user>{
@Override
public User convert(String userStr){
User user=new User();
String []strArr=userStr.split("-");
Long id=Long.praseLong(strArr[0]);
String userName=strArr[1];
String note=StrArr[2];
user.setId(id);
user.setUserrName(userName);
user.setNote(note);
return user;
}
}
类标注为@Component,并且实现了 Converter 接口,这样 Spring 就会将这个类扫描并装配到 IoC 容器中 。 对于 Spring Boot,之前分析过它会在初始化时把这个类 自动地注册到转换机制中,所以注册这步并不需要人工再处理。 这里泛型指定为 String 和 User,这样 SpringMVC 就会通过 HTTP的参数类型(String)和控制器的参数类型( User)进行匹配,就可以从注册机制中发现这个转换类,这样就能够将参数转换出来.
### 3.GenericConverter 集合和数组转换
GenericConverter 是数组转换器。因为 Spring MVC 自身提供了 一些数组转换器,需要自定义 的并不多 ,所以这里只介绍 SpringMVC 自定义的数组转换器。假设需要 同时新增多个用户,这样便需要传递一个用户列表 C List<User>)给控制器。此时SpringMVC 会使用 StringToCollectionConverter 转换它,这个类实现了 GenericConverter 接 口,并且是 Spring MVC 内部己经注册的数组转换器 。 它首
先会把字符串用逗号分隔为一个个的子宇符串,然后根据原类型泛型为 String、目标类型泛型为 User类,找到对应的 Converter 进行转换,将子字符串转换为 User 对象
五、数据模型
在 Spring MVC 流程中,控制器是业务逻辑核心内 容 ,而控制器的核心内容之一就是对数据的处理 。 SpringMVC 全流程的学习,可以看到允许控制器自定义模型和视图( ModelAndView ),其中模型是存放数据的地方,视图则是展示给用户 。
数据模型的作用是绑定数据。为后面的视图渲染做准备。SpringMVC使用的模型接口和类进行探讨
类 ModelAndView 中存在一个 Mode!Map 类型的属性, Mode!Map 继承
了 Linked.HashMap 类 , 所以它具备 Map 接口的一切特性,除此之外它还可以增加数据属性 。 在 SpringMVC 的应用中,如果在控制器方法的参数中使用 ModelAndView 、 Model 或者 ModelMap 作为参数类型, SpringMVC 会自动创建数据模型对象 .
package com.demo.controller;
@RequestMapping("/data")
@Controller
public class DataModelController{
// 注入用户服务类
@Autowired
private UserService userService=null;
//Model
@GetMapping("/model")
public String useModel(Long id, Model model){
User user=userService.getUser(id);
model.addAttribute("user",user);
return "data/user";
}
//ModeMap
@GetMapping("/modelMap")
public ModelAndView userModelMap(Long id,ModelMap modelMap){
User user =userService.getUser(id);
ModelAndView mv=new ModelAndView();
mv.setViewName("data/user");
modelMap.put("user",user);
return mv;
}
//ModelAndView
@GetMapping("/mav")
public ModelAndView useModelAndView(Long id,ModelAndView mv){
User user=userService.getUser(id);
mv.addObject("user",user);
mv.setViewName("data/user");
return mv;
}
}
六、视图与视图解析器
视图是渲染数据模型展示给用户的组件 , 在 Spring MVC 中又分为逻辑视图和非逻辑视图。逻辑视图是需要视图解析器( ViewResolver ) 进行进一步定位的 。 例如, 之前的例子所返回 的字符串之所以能找到对应 的 JSP , 就是因为使用了逻辑视 图 , 经 由视图解析器 的定位后 ,才能找到视图将数据模型进行渲染展示给用户查看。对于非逻辑视图 ,则并不需要进一步地定位视图 的位置 , 它只需要直接将数据模型渲染出来即可
在实际的工作中视图解析器 Interna!ResourceViewResolver 是 比较常用的
1.视图设计
对于视图,除了JSON和JSP视图之外,还有其他类型的视图如Excel、PDF。他们都会实现Spring MVC 定义的视图接口View,其源码如图所示。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.web.servlet;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
public interface View {
//响应状态属性
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
//路径变量
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
//选择内容类型
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
//相应类型
@Nullable
default String getContentType() {
return null;
}
//渲染方法
void render(@Nullable Map<String, ?> var1, HttpServletRequest var2, HttpServletResponse var3) throws Exception;
}
- getContentType 方法是获取 HTTP 响应类型的 ,它可以返回的
类型是文本、 JSON 数据集或者文件等 - render方法则是将数据模型渲染到视图的,这是视图的核心方法,在它 的参数中 , model 是数据模型,实际就是从控制器(或者由处理器自动绑定)返回的数据模型,这样 render 方法就可以把它渲染出来。渲染视图是比较复杂的过程,为了简化视图渲染的开发,在 SpringMVC 中已经给开发者提供了许多开发好的视图类 , 所以在大部分的情况下并不需要自己开发自己的视图
七、文件上传
1.Spring MVC对文件上传的支持
首先,DispatcherServlet会使用适配器模式,将HttpServletRequest接口转化为MultipartHttpServletRequest对象。MultipartHeepSrvletRequest接口扩展了HttpServletRequest接口的所有方法。而且定义了一些操作文件的方法。这样通过这些方法就可以实现对上传文件的操作
对于文件上传的场景,spring MVC 将HttpServletRequest对象转化为MultipartHttpServletRequest对象。MultipartHttpServletRequest接口存在许多的方法用来处理文件。在使用时放需要配置Spring MVC上传文件时,还需要配置MultipartHeepServletRequest,这个任务时通过MultipartResolver接口实现的,它包含两个实现类StandardSrvletMultipartResolver和CommonsMultipartResolver,这两个类都可以实现文件上传,但是推荐使用StandardServletMultipartResolver.
在Spring boot 中如果你没有自定义的MultipartResover对象。那么自动配置机制会自动创建MultipartResolver对象。
application.properties中关于文件上传的配置
#MULTIPART(MultiartProperties)
#是否启用SpringMVC多分部上传gongn
spring.servlet.multipart.enable=true
#将文件写入磁盘的阈值。值可以使用后缀”MB“或者”KB“来表示兆字节大小
spring.servlet.multipart.file-size-threshold=0
#指定默认上传的文件夹
spring.servlet.multipart.location=
#限制单个文件最大大小
spring.servlet.multipart.max-file-size=1MB
#限制所有文件最大大小
spring.servlet.multipart.max-request-size=10MB
#是否延迟多部件文件请求的参数和文件的解析
sping.servlet.multipart.resolve-lazily=false
根据配置Spring Boot会自动生成StandardServletMultipartResolver对象,这样就能够对上传的文件进行配置。对于文件的上传可以使用Servlet API 提供的Part接口或者Spring Mvc提供的MultipartFile接口作为参数。其实无论使用哪种类都是允许的。推荐使用Part
2.开发上传功能
applicaiton.properties配置
spring.servlet.multipart.location=d:/demo
spring.servlet.multipart.max-file-size=524880
spring.servlet.multipart.max-request-size=20Mb
上传文件的JSP页面
<%@ page language="java" contentType="text/html";charset="UTF-8”
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD html 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dta">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html";charset="UTF-8">
<tiltle>文件上传</tiltle>
</head>
<body>
<form method="post" action="./request" enctype="multipart/form-data">
<input type="file" name="file" value="请选择上传的文件">
<input type="submit" value="提交" />
</form>
</body>
</html>
注意
表单声明为multipart/form-data,如果没有这个声明,Spring MVC就会解析文件请求出错。从而导致上传文件失败。上传控制器
package com.demo.controller
@Controller
@RequestMapping("/file")
public class FileController{
//打开文件上传请求页面
@GetMapping("/upload/page")
public String uploadPage(){
return "/file/upload";
}
//处理文件上传结果
private Map<String,Object> dealResultMap(boolean sucess,String msg){
Map<String ,Object> result=new HashMap<String,Object>();
result.put("success",success);
result.pur("msg",msg);
return result;
}
//使用HttpServletRequest作为参数
@PostMapping("/upload/request")
@ResponseBody
public Map<String,Object> uploadRequest(HttpServletRequest request){
boolean flag=false;
MutipartHttpServlet=mreq=null;
//强制转换为MultipartHttpServletRequest对象
if(request instance of MultipartHttpServletRequest){
mreq = (MultipartHttpServletRequest) request;
}else{
return dealResultMap(false,"上传失败");
}
//获取MultipartFile文件信息
MultipartFile mf=mreq.getFile("file");
//获取源文件名称
String fileName=mf.getOriginalFilename();
File file=new File(fileName);
try{
//保存文件
mf.transferTo(file);
}catch(Exception e){
e.printStrackTrace();
return dealResultMap(false,"上传失败")
}
return dealResultMap()
}
//使用Spring MVC的MMultipartFile类作为参数
@PostMapping("/upload/multipart")
@ResponseBody
public Map<String,Object> uploadMultipartFile(MultipartFile){
Sting fileName=file.getOriginalFilename();
File dest=new File(fileName);
try{
//保存文件
file.transferTo(dest);
}catch(Exception e){
e.printStrackTrace();
return dealResultMap(false,"上传失败")
}
return dealResultMap()
}
//使用Part作为参数
@PostMapping("/upload/part")
@ResponseBody
public Map<String,Object> uploadPart(Part file){
String fileName=file.getSubmittedFileName();
try{
//写入文件
file.write(fileName);
}atch(Exception e){
e.printStrackTrace();
return dealResultMap(false,"上传失败")
}
return dealResultMap()
}
}
八、拦截器
当请求来到DispatcherServlet时,他会根据HandlerMapping的机制找到处理器,这样就会返回一个HandlerExecutionChain对象,这个对象包含处理器和拦截器。这里的拦截器会对处理器进行拦截,这样通过拦截器就可以增强处理器的功能。
1.拦截器的设计
首先所有的拦截器都需要实现HandlerInterceptor接口。
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
public interface HandlerInterceptor {
//处理器执行前方法
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
//处理器执行后方法
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
//所有内容完成后执行方法,一般在视图处理之后
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
2.开发拦截器
自定义拦截器
package com.demo.interceptor
public class Interceptor1 extends HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception{
System.out.println("处理前方法")
return true;//如果返回False不在执行处理器执行链以后的方法。
}
@override
public void postHandle(HttpServletRequest request,HttpServletResponse response Object handler,ModelAndView)throws Exception{
System.out.println("处理器后方法")
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
System.out.println("处理器完成方法");
}
}
注册拦截器
需要在配置文件中实现WebMvcConfigurer接口,覆盖addInterceprors方法,进行拦截器注册
package com.demo.main
@Configuration
@SpringBootApplication(scanBasePackages="com.demo")
public class DemoApplication implements WebMvcConfigurer{
public static void main(String[] args){
SpringApplication.run(DemoApplication.class,args);
}
@Override
public void addInterceptors(InterceptorRegistry registry){
//注册拦截器到Spring MVC机制,然后它会返回一个拦截器注册
InterceptorRegistration ir=registry.addInterceptor(new Interceptor1());
//指定拦截匹配模式,限制拦截器拦截请求
ir.addPathPatterns("/intercepror/*")
}
}
控制器
package com.springboot.controller;
@Controller
@RequestMapping("/interceptor")
public class InterceptorController{
@GetMapping("/start")
@ResponseBody
public String start(){
System.out.println("执行处理器逻辑");
return "/welcome"
}
}
3.多个拦截器
责任链模式的规则,对于处理器前方法采用先注册先执行,而处理器后方法和完成
方法则是先注册后执行的规则。
处理器前( preHandle )方法会执行,但是一旦返回 false ,则后续的拦截器、 处理器和l所有拦截器的处理器后( postHandle ) 方法都不会被执行
九、其他
1.ResponseBody转换为JSON
当想把某个控制器的返回转变为 JSON 数据集时 , 只需要在方法上标注
@Respons巳Body 注解即可,在进入控制器方法前 , 当遇到标注的@ResponseBody 后,处理器就会记录这个方法的响应类型为 JSON 数据集。当执行完控制器返回后,处理器会启用结果解析器( ResultResolver)去解析这个结果,它会去轮询注册
给 Spring MVC 的 HttpMessageConverter 接口的实现类 。 因为 MappingJackson2HttpMessageConverter这个实现类己经被 Spring MVC 所注册 ,加上 Spring MVC 将控制器的结果类型标明为 JSON ,所以就匹配上了 , 于是通过它就在处理器内部把结果转换为了 JSON 。当然有时候会轮询不到匹配的
HttpMessageConverter ,那么它就会交由 S pring MVC 后 续流程去处理。如果控制器返回结果被MappingJackson2HttpMessageConverter 进行了转换,那么后续的模型和视图( Mode!AndView )就返回 null,这样视图解析器和视图渲染将不再被执行 ,
2.重定向
重定向( Redirect )就是通过各种方法将各种网络请求重新定个方向转到其他位置 。
通过 以“ redirect:”开头的字符串,然后后续的字符串指向 shouUser方法请求的 URL
sprngMVC提供RedirectAttributes ,可以将它作为控制器的参数,它扩展了 ModelMap 的接口,它有一个 addFlashAttribute 方法,这个方法可以保存需要传递给重定位的数据 。
@GetMapping("/redirect")
public ModelAndRiew redirectConroller(ModelAndView){
mv.setViewName("redirect:/user/show")
}
@GetMapping("/redirect2")
public String redirect2(RedirectAttriutes ra){
ra.addFlashAttribute("num:",1)
return "redirect:/user/show"
}
3.操作会话对象
在 W巳b 应用中,操作会话( HttpSession )对象是十分普遍的,对此 Spring MVC 也提供了支持 。主要是两个注解用来操作 H即Session 对象,它们是@SessionAttribute 和@SessionAttributes 。
- @SessionAttribute 应用于参数,它的作用是将 HttpSession 中的属性读出,赋予控制器的参数——————取
- @SessionAttributes 则 只能用于类的注解,它会将相关数据模型的属性保存到 Session 中 。 ——————存
package com.demo.controller;
@SessionAttributes(names={"user"},types=Long.class)
@Controller
@RequestMapping("/session")
public class SessionController{
@Autowired
private UserService userService=null;
@GetMapping("/test")
public String test(@SessionAttribute("id")Long id ,Model model){
Model.addAttribute("id_new",id);
return "session/test";
}
}
4.给控制器添加增强
Spring MVC 可以给控制器添加增强,用于在控制器方法的前后和异常发生时去执行不同的处理。
- @ControllerAdvice 定义一个控制器的通知类 类似于@Aspect
- @InitBinder 定义控制器参数绑定规则
- @ExceptionHander: 定义控制器发生异常后的操作
- @ModelAttribute:可以在控制器方法执行执行之前,对数据模型进行操作
package com.demo.controller.advice
@ControllerAdvice(
//指定拦截的包
basePackages={"com.demo.advice.test.*"}
//限定被标注为@Controller的类才被拦截
anontations=Controller.class
)
public class MyControllerAdvice{
//绑定格式化、参数转换规则和增加验证器
@InitBinder
public void initDataBinder(WebDataBinder binder){
//自定义日期编辑器,限定格式为yyyy-MM-dd,且参数不允许为空
CustomDateEditor dateEditor=new CustomDateEditor(new SimpleDataForm("yyyy-MM-dd"),false);
binder.registerCustomEditor(Date.class,dateEditor);
}
//在执行控制器前先执行,可以初始化数据模型
@ModelAttribute
public void projectModel(Model model){
model.addAttribute("Project_name","demo")
}
@ExceptionHandler(value=Exception.class)
public String exception(Model model,Exception ex){
model.addAttrbute("exception_message",ex.getMessage());
return "exception";
}
}
5.获取请求头参数
可以使用@RequestHander
@RequestHander:通过@RequestHander接收请求头
@PostMapping("/header/user")
@ResponsseBody
public User headerUser(@RequestHeader("id") Long id){
User user=userService.getUser(id);
return user;
}