目录
GET方式,url参数中有+、空格、=、%、&、#等特殊符号的问题解决
@RequestBody与@RequestParam()同时使用
实际上,@RequestBody之外的参数可以是2-8上提到的任意方式,相当于请求体和url上的参数是隔离的
概述
springMVC是spring框架体系的一部分,功能和struts2类似,可以完美替代struts2,可以认为struts2已经被淘汰
随着前后端分离和springboot的普及,我们不再需要关注xml配置,也不关注Model,View相关的接口返回值,此处只关注springMVC本身
springMVC做了什么
简化了请求的接收和处理,不再需要开发者针对每个请求编写Servlet,重写方法接收请求,也不再需要关注参数转换,大量减少了代码量
springMVC与struts2区别
1.springmvc入口是一个servlet,即前端控制器;struts2入口是filter过滤器(配置很多拦截器)
2.springmvc基于方法开发,请求参数传递到方法,可以为单例或者多例;struts2是基于类开发,方法只能通过类的参数接收,只能为多例
3.springmvc解析request请求内容,把请求参数和映射方法的形参绑定,将数据封装成ModelAndView返回给前端解析;struts2是通过值栈方式存储请求和响应数据,通过OGNL存取数据
springMVC整个流程是一个单向闭环
用户(输入)--->控制器controller,将用户输入分发给业务模型--->模型model,进行业务逻辑判断,数据库存取--->视图view,根据需要渲染不同的视图--->用户(获得反馈)
springMVC具体的处理流程
1.用户请求-被前端控制器拦截DispatcherServlet
2.DispatcherServlet接收到请求以后调用HandlerMapping处理器映射器
3.HandlerMapping根据请求的url找到对应的Handler处理器,生成处理器对象和拦截器返回给dispatcher
4.DispatcherServlet通过HandlerAdapter处理器适配器调用Handler处理器
5.执行处理器(即Controller)
6.执行结果ModelAndView通过HandlerAdapter返回给DispatcherServlet
7.DispatcherServlet将ModelAndView传给ViewReslover视图解析器
8.ViewReslover解析后返回具体的View给DispatcherServlet
9.DispatcherServlet对View进行渲染(填充数据)
10.DispatcherServlet将最终的结果返回给客户
springMVC的组成部分
部分组件会默认加载,程序员需要开发的是Handler和View,也就是接收请求后的业务处理和前端页面
DispatcherServlet 前端控制器:这是mvc流程的核心,通过DispatcherServlet调用其他组件处理请求,降低了组件间的耦合
HandlerMapping 处理器映射器:根据请求的url找到对应的Handler
Handler 处理器:在DispatcherServlet控制下进行请求的业务操作,这是程序员开发的主要内容
HandlAdapter 处理器适配器:DispatcherServlet通过HandlerAdapter处理器适配器调用Handler处理器
ViewResolver 视图解析器:ViewResolver负责将处理结果生成View视图,ViewResolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,DispatcherServlet对View进行渲染将处理结果通过页面展示给用户
View 视图:springmvc框架提供了很多的View视图类型的支持,包括:jstlView、freemarkerView、pdfView等
请求映射
@RequestMapping
定义请求映射规则,接收并处理请求
用法
1.注解在类上:访问该类所有方法都要加上该前置路径
2.注解在方法上:访问该方法的映射路径
属性
1.value
映射路径,如果只有value属性,则value="/xxxx"的value=可以省略
@RequestMapping(value="/xx/xx"),@RequestMapping("/xx/xx")
value的值是数组,可以将多个url映射到同一个方法
@RequestMapping(value={"/xx/xx","xx/xxx"})
2.method
限制请求方法类型
RequestMethod.GET,RequestMethod.POST
java
@RequestMapping(value = "/xxx/person/get/{id}", method = RequestMethod.GET)
public ApiResult getPersonById(@PathVariable Integer id) {
...
Person person = ...
return ApiResult.success(person);
}
GET方式和POST方式
概述
GET方式和POST方式是HTTP协议中两种发送请求的方式
HTTP是基于TCP/IP的,关于数据如何在网络中传递的协议
因此,GET和POST本质上都是基于TCP/IP的发送请求的方式,在底层上说,并没有本质的区别
GET和POST的区别是由HTTP协议规定的,HTTP这么规定是为了给各式各样的请求分类
换句话说,并不是GET请求和POST有什么区别,你如果想要GET请求使用RequestBody传参,并不是不行,所谓的区别只是HTTP协议做出的规定
HTTP给GET和POST做了哪些规定
参数传递渠道:
GET:拼接到url上
POST:放在请求体中(RequestBody)
长度限制:
GET:由浏览器决定,通常为2k,最多64k
POST:理论上无限制
TCP数据包:
GET:发送一次请求,产生一个TCP数据包
POST:发送两次请求,先发送请求头Header,获取到响应后再发送RequestBody,产生两个TCP数据包,但要注意并不是所有浏览器都严格遵循这样的规定,即有的浏览器POST也只发送一次
GET方式,url参数中有+、空格、=、%、&、#等特殊符号的问题解决
问题:Url出现了有+,空格,/,?,%,#,&,=等特殊符号的时候,可能在服务器端无法获得正确的参数值
问题原因:为何Url中有这些字符就会出现问题,这涉及到URL编码与解码
URL编码与解码,网络标准RFC 1738做了硬性规定:
只有字母和数字[0-9a-zA-Z]、一些特殊符号"$-_.+!*'(),"[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL
这意味着,如果URL中有汉字,等特殊字符的时候,就必须编码后使用。而+,空格,/,?,%,#,&,=,这些字符(不安全),当把他们直接放在Url中的时候,可能会引起解析程序的歧义,因此也必须经过编码才能使用
解决办法:将这些字符转化成服务器可以识别的字符,对应关系如下:
- URL中+号表示空格 %2B
空格 URL中的空格可以用+号或者编码 %20
/ 分隔目录和子目录 %2F
? 分隔实际的URL和参数 %3F
% 指定特殊字符 %25
表示书签 %23
& URL 中指定的参数间的分隔符 %26
= URL 中指定参数的值 %3D
参数绑定
1.默认支持的参数类型
处理器形参中添加如下类型的参数处理适配器会默认识别并进行赋值
HttpServletRequest 通过域对象传递参数,包含请求的详细信息,不仅仅是参数
HttpServletResponse 处理响应信息
HttpSession 获取session中存放的对象
2.绑定简单类型自动绑定
整形:Integer、int
字符串:String
单精度:Float、float
双精度:Double、double
布尔型:Boolean、boolean
只要Controller形参和请求参数名称能匹配上就可以接收值
通常使用包装类,因为基本数据类型不能为null值
通常有多个参数,会封装成POJO,可以方便数据的进一步使用
java
@RequestMapping(value = "/xxx/person/getById", method = RequestMethod.GET)
public ApiResult getPersonById(Integer id) {
...
Person person = ...
return ApiResult.success(person);
}
3.@RequestParam手动绑定
将Controller形参和请求参数名称手动绑定,字段名称不一致时可以这样,但通常不这样
java
@RequestMapping(value = "/xxx/person/getByName", method = RequestMethod.GET)
public ApiResult getPersonById(@RequestParam(value="paramName")String name) { //这样就将Controller的形参name和请求传递的参数paramName绑定了
...
Person person = ...
return ApiResult.success(person);
}
属性
value 指定绑定的请求参数名称
required 是否必须有值,false/true,默认是true,此时入参为空会报错TTP Status 400 - Required parameter 'XXXX' is not present
defaultValue 默认值,没有值的时候赋默认值
4.实体类属性自动绑定
请求参数名称和实体类属性名一致,会自动将请求参数赋值给实体类的属性
java
@RequestMapping("/updateCustomer")
public String update(Customer customer) {
...
}
5.Map绑定
java
@RequestMapping(value = "/getCustomerListPage")
@ResponseBody
public ApiResult getCustomerListPage(@RequestParam(required = false) Map<Object, Object> customerParams) {
List<Customer> customerList = customerService.getCustomerListPage(customerParams);
return ApiResult.success();
}
6.绑定数组类型
页面选中多个checkbox向controller方法传递,本身属于一个form表单,此时需要用数组参数接收该请求参数,参数名是checkbox的name
java
@RequestMapping(value = "/xxx/person/getByIds")
public ApiResult getPersonById(Integer[] ids) {
...
List<Person> personList = ...
return ApiResult.success(personList);
}
7.绑定List类型
List是一个接口,不能直接实例化,因此无法直接绑定,要使用实现类来绑定,比如ArrayList,如果直接绑定ArrayList,还需要加上@RequestParam,这是由参数解析器决定的;
通常我们不这么做,而是通过将List放入实体类中进行绑定
java
//通过@RequestParam绑定
@RequestMapping(value = "/getCustomerListPage")
@ResponseBody
public ApiResult getCustomerListPage(@RequestParam(required = false) ArrayList<Integer> idList) {
List<Customer> customerList = customerService.getCustomerListPage(idList);
return ApiResult.success();
}
//通过实体类绑定
@RequestMapping(value = "/xxx/person/getByIds")
public ApiResult getPersonById(PersonParams personParams) {
...
List<Person> personList = ...
return ApiResult.success(personList);
}
pulic class PersonParams {
private List<Integer> idList;
public List<Integer> getIdList() {
...
}
...
}
8.绑定日期类型
日期比较特殊,因为前台控件在提交的时候都是String,所以接收的时候不能直接用日期类型
当然可以自定义转换类来实现自定义参数绑定,但通常不这么做,而是使用实体类接收,在对应的成员上通过注解实现自动转换
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
9.@RequestBody
上面的2到8种参数绑定,都需要我们把参数放到请求上,如果我们的参数是放在请求体RequestBody中,会发现接收到的都是null,那么怎么获取RequestBody中的参数呢
通过@RequestBody注解
注意:
请求体RequestBody是以JSON字符串的方式传递
一个接口只能定义一个@RequestBody,因为浏览器只会发送一个RequestBody
@RequestBody虽然不可以定义两个,但除了@RequestBody之外,还可以定义别的参数来接收url上的参数
绑定String,直接接收到请求体JSON串
不可以直接绑定单个Integer之类的参数,只能以String传递
java
@RequestMapping(value = "/getCustomerListPage")
@ResponseBody
public ApiResult getCustomerListPage(@RequestBody String i) {
//这里的i是请求体的整个JSON串,比如{
"i": "1"
}
return ApiResult.success();
}
绑定Map
java
@RequestMapping(value = "/getCustomerListPage")
@ResponseBody
public ApiResult getCustomerListPage(@RequestBody Map map) {
return ApiResult.success();
}
绑定实体类
入参key要和实体类中属性名一致
java
@RequestMapping(value = "/getCustomerListPage")
@ResponseBody
public ApiResult getCustomerListPage(@RequestBody CustomerParams customerParams) {
return ApiResult.success();
}
绑定数组/集合
元素可以是基本类型包装类,实体类,Map
java
@RequestMapping(value = "/getCustomerListPage")
@ResponseBody
public ApiResult getCustomerListPage(@RequestBody List<Integer> idList) {
return ApiResult.success();
}
@RequestMapping(value = "/getCustomerListPage")
@ResponseBody
public ApiResult getCustomerListPage(@RequestBody CustomerParams[] customerParamsArr) {
return ApiResult.success();
}
@RequestMapping(value = "/getCustomerListPage")
@ResponseBody
public ApiResult getCustomerListPage(@RequestBody List<CustomerParams> customerParamsList) {
return ApiResult.success();
}
@RequestBody与@RequestParam()同时使用
即请求体和url上都有入参
java
@RequestMapping(value = "/getCustomerListPage")
@ResponseBody
public ApiResult getCustomerListPage(@RequestBody CustomerParams customerParams, @RequestParam String token) {
return ApiResult.success();
}
//@RequestParam可以省略
@RequestMapping(value = "/getCustomerListPage")
@ResponseBody
public ApiResult getCustomerListPage(@RequestBody CustomerParams customerParams, String token) {
return ApiResult.success();
}
实际上,@RequestBody之外的参数可以是2-8上提到的任意方式,相当于请求体和url上的参数是隔离的
java
@RequestMapping(value = "/getCustomerListPage")
@ResponseBody
public ApiResult getCustomerListPage(@RequestBody CustomerParams customerParams, CustomerParams customerParams2) {
return ApiResult.success();
}
总结
url传参,使用参数直接绑定,可以使用多种方式绑定
请求体传参,使用@RequestBody方式绑定,@RequestBody可以直接绑定多种形式
换句话说,实际上数据绑定方式与请求方式没有必然关系,直接绑定参数是接收url参数,@RequestBody是接收请求体参数
而因为浏览器对GET,POST的规定,显然,POST请求需要用@RequestBody接收,GET请求需要直接绑定的方式接收
JSON
1.什么是JSON
JSON(JavaScript Object Notation),是一种轻量级的数据交换格式,是一个字符串
简单的说就是javascript中的对象和数组
2.结构
1.对象:{}括起来的内容,结构为键值对,可以嵌套,通过对象名.key取值
2.数组:[]括起来的内容,结构为["xxx","xxxx",...],通过索引取值
java
{
"animals": {
"dog": [
{
"name": "wangcai",
"age":15
},
{
"name": "Marty",
"age": null
}
]
}
}
animals.dog[0].name为wangcai
响应(接口返回值)
再次强调,在前后端分离的背景下,此处不过多关注Model,View,static页面
java
@RequestMapping("/index")
public String toIndex() {
return "/index"; //返回static/index页面
}
@RequestMapping("/index")
public String toIndex() {
return "redirect:/index"; //重定向到static/index页面
}
@RequestMapping("/index")
public String toIndex(Model model) {
model.addAttribute("message", "这是index页面"); //可以通过Model传递数据
return "/index"; //返回static/index页面
}
@RequestMapping(value = "/index", method = RequestMethod.GET)
public ModelAndView toIndex() {
ModelAndView mv = new ModelAndView();
mv.addObject("message", "这是index页面");
mv.setViewName("/index");
return mv; //通过ModelAndView返回
}
转发和重定向
重定向,重定向到别的url,通常是个页面,操作完这一步打开一个新的页面
重定向会使用新的request和response
return "redirect:/newPage?userId=" + vo.getId();
转发,执行后继续执行另一个url,通常为Controller
request和response仍然为最初的,所以数据还在,不需要像重定向那样跟参数
return "forward:/edit";
void
接口可以没有返回值
java
@RequestMapping("/save")
public void save() {
sout("save");
}
@ResponseBody
在前后端分离的背景下,我们通常编写的都是直接返回数据的接口,如果我们要直接返回数据,而不是页面,也不通过Model去渲染,要怎么做
通过@ResponseBody注解
这个注解会通过springmvc提供的HttpMessageConverter接口转换为指定格式的数据,默认是String,然后通过Response响应给客户端
通过这个注解,我们可以实现将返回值转化为JSON字符串响应给浏览器,这个过程可以理解为数据序列化和反序列化的过程,通常我们还会定义一个类作为所有接口的统一返回格式
实际上,使用这个注解需要导入spring-web这个依赖,而如果我们需要返回JSON而不是String,还需要导入JSON相关依赖,但springboot的背景下,我们只需要导入一个spring-boot-starter-web启动器就已经包含了所需要的所有依赖和配置
java
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
import java.io.Serializable;
public class ApiResult implements Serializable {
// 返回编码
private String code;
// 返回信息
private String msg;
// 返回数据封装
private Object data = null;
public static ApiResult failure(String msg) {
ApiResult apiResult = new ApiResult();
apiResult.setCode(ResultCodeEnum.SYS_ERROR.k());
apiResult.setMsg(msg);
return apiResult;
}
...
}
@RequestMapping("/save")
@ResponseBody
public ApiResult save() {
return ApiResult.failure("保存失败!");
}
restful风格
restful是一种软件开发风格,并不是什么开发规范,或者什么语法规则,只是一种约定,如果你写成这个样子,那么我们就叫你这种代码是restful
特点
URI等价于资源
每个资源对应一个URI,要获取这个资源那么访问它的URI即可,因此URI只能包含名词,URL是最典型的URI
要求资源是无状态的,对某个资源的请求不依赖其他资源/请求
即访问URI就可以获得资源,比如在OA上查看某个员工的信息,需要输入账号密码登陆系统再查看就是有状态的,而输入URI直接就可以获取就是无状态的
统一HTTP方法分类
GET 查询SELECT: 从服务器获取资源
POST 新增CREATE: 在服务器新建资源
PUT 更新UPDATE: 在服务器更新资源(客户端提供改变后的完整资源)
PATCH 更新UPDATE: 在服务器更新资源(客户端提供改变的属性)
DELETE 删除DELETE: 从服务器删除资源
统一返回数据格式
使用JSON串返回
针对不同操作,服务器向用户返回的结果应该符合以下规范
GET 返回单个资源对象/资源对象的列表(数组)
POST 返回新生成的资源对象
PUT 返回完整的资源对象
PATCH 返回完整的资源对象
DELETE 返回空
从URL上获取参数
restful风格要求资源通过URI直接定位,所以参数会拼写在URL中
使用占位符{}进行这样的拼接,使用@PathVariable()获取url路径上的参数
@Pathvariable()常用属性
name/value 绑定占位符名称
required 早期不支持这个属性
如果配置fasle,意味着url中的路径那个占位符就为空了,将不会拼接到url
那么@RequestMapping就需要映射多个url,变为@RequestMapping(value = "xxx/people/age/{age}", "xxx/people/age")
java
@RequestMapping("xxx/people/age/{age}")
public List<people> getPeopleByAge(@PathVariable() Integer age) {
...
}
POJO
POJO(plain ordinary java object) 简单无规则java对象
最基本的对象,没有实现任何接口,继承任何类,只包含属性,getter,setter,可以迁移和复用,比如生成PO,DTO,VO都可以直接继承POJO
PO(persistant object) 持久对象
数据库映射对象,属性与数据库对应表的字段对应
DAO(data access object 数据访问对象)
提供数据库的CRUD操作
DTO (Data Transfer Object)数据传输对象
输入:接口接收传入对象
输出:接口返回数据,此时会被改写为VO
VO( View Object 显示层对象)
页面需要很多DTO之外的信息,比如code,message等等,VO=DTO+其它信息
响应状态码
1xx
信息,1XX类型的状态码是临时响应,代表着请求已经被接受,但需要继续处理
100 Continue 服务器仅接收到部分请求,但是一旦服务器并没有拒绝该请求,客户端应该继续发送其余的请求
101 Switching Protocols 服务器转换协议:服务器将遵从客户的请求转换到另外一种协议。
102 Processing 由WebDAV(RFC 2518)扩展的状态码,代表处理将被继续执行
2xx
成功,2XX类型的状态码代表着请求已经被服务器接收、理解、并接受
200 OK 请求成功(其后是对GET和POST请求的响应文档)
201 Created 请求被创建完成,同时新的资源被创建
202 Accepted 请求已被接受,但是处理未完成
203 Non-authoritative Information 文档已经正常地返回,但一些响应头可能不正确,因为使用的是文档的拷贝
204 No Content 没有新文档,浏览器应该继续显示原来的文档,如果用户定期地刷新页面,而Servlet可以确定用户文档足够新,这个状态代码是很有用的
205 Reset Content 没有新文档,但浏览器应该重置它所显示的内容,用来强制浏览器清除表单输入内容
206 Partial Content 客户发送了一个带有Range头的GET请求,服务器完成了它
207 Multi-Status 由WebDAV(RFC 2518)扩展的状态码,代表之后的消息体将是一个XML消息,并且可能依照之前子请求数量的不同,包含一系列独立的响应代码
3xx
重定向,3XX这类状态码代表着客户端需要采取进一步的操作才能完成请求,通常这些状态码是用来重定向的
300 Multiple Choices 多重选择,链接列表,用户可以选择某链接到达目的地,最多允许五个地址
301 Moved Permanently 所请求的页面已经转移至新的url
302 Found 所请求的页面已经临时转移至新的url
303 See Other 所请求的页面可在别的url下被找到
304 Not Modified 未按预期修改文档,客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用
305 Use Proxy 客户请求的文档应该通过Location头所指明的代理服务器提取
306 Unused 此代码被用于前一版本,表示当前功能目前已不再使用,但是代码依然被保留
307 Temporary Redirect 被请求的页面已经临时移至新的url
4xx
客户端错误,4XX类型的状态码代表着客户端可能发生了错误,阻碍了服务器的处理
400 Bad Request 服务器未能理解请求或是请求参数有误
401 Unauthorized 被请求的页面需要用户名和密码
402 Payment Required 此代码尚无法使用(为了将来可能的需求而预留的)
403 Forbidden 对被请求页面的访问被禁止
404 Not Found 服务器无法找到被请求的页面,url有误
405 Method Not Allowed 请求中指定的方法不被允许
406 Not Acceptable 服务器生成的响应无法被客户端所接受
407 Proxy Authentication Required 用户必须首先使用代理服务器进行验证,这样请求才会被处理
408 Request Timeout 请求超出了服务器的等待时间
409 Conflict 由于冲突,请求无法被完成
410 Gone 被请求的页面不可用
411 Length Required "Content-Length" 未被定义,如果无此内容,服务器不会接受请求
412 Precondition Failed 请求中的前提条件被服务器评估为失败
413 Request Entity Too Large 由于所请求的实体的太大,服务器不会接受请求
414 Request-url Too Long 由于url太长,服务器不会接受请求。当post请求被转换为带有很长的查询信息的get请求时,就会发生这种情况
415 Unsupported Media Type --- 由于媒介类型不被支持,服务器不会接受请求
416 服务器不能满足客户在请求中指定的Range头
417 Expectation Failed
5xx
服务器错误
500 Internal Server Error 请求未完成。服务器遇到不可预知的情况
501 Not Implemented 请求未完成。服务器不支持所请求的功能
502 Bad Gateway 请求未完成。服务器从上游服务器收到一个无效的响应
503 Service Unavailable 请求未完成。服务器临时过载或当机
504 Gateway Timeout 网关超时
505 HTTP Version Not Supported 服务器不支持请求中指明的HTTP协议版本
springMVC拦截器
springmvc的拦截器可以对处理器进行预处理和后处理
创建和配置
implements HandlerInterceptor,重写方法
java
public class HandlerInterceptor1 implements HandlerInterceptor {
/**
* Controller方法执行前调用此方法
* 返回true表示继续执行,返回false中止执行
*/
@Override
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
System.out.println("HandlerInterceptor1....preHandle");
// 设置为true,测试使用,设置为false请求就被拦截了
return true;
}
/**
* controller方法执行后但未返回视图前调用此方法
* 这里可以对返回数据进行统一的二次处理,比如多个页面都需要某些同样的处理
*/
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception {
System.out.println("HandlerInterceptor1....postHandle");
}
/**
* controller方法执行后且视图返回后调用此方法(页面渲染后)
* 这里可得到执行controller时的异常信息,可以记录请求日志
*/
@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception {
System.out.println("HandlerInterceptor1....afterCompletion");
}
}
@Configuration
public class mvcConfig implements WebMvcConfigurer {
@Autowired
private HandlerInterceptor1 handlerInterceptor1;
@Override
public void add Interceptors(InterceptorRegistry registry) {
// 注册拦截器,并指定要拦截的URL模式
registry.addInterceptor(handlerInterceptor1).addPathPatterns("/");
}
}
配置多个拦截器时的执行顺序
preHandle按拦截器定义顺序调用
postHandler按拦截器定义逆序调用
afterCompletion按拦截器定义逆序调用
postHandler在拦截器链内所有拦截器preHandle返回true才会调用
afterCompletion只要对应的preHandle返回true就调用