SpringBoot2学习笔记
四、Web开发
4.3)请求参数处理
4.3.1)rest使用与原理
Rest【@RequestMapping】风格支持(使用HTTP请求方式动词来表示对资源的操作)
-
以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
-
现在: /user GET-获取用户 DELETE- 删除用户 PUT-**修改用户 POST-**保存用户
-
核心Filter;HiddenHttpMethodFilter
用法: 表单method=post,隐藏域 _method=put
SpringBoot中手动开启
4.3.1.1)请求映射示例
工程SpringBootDemo3中修改HelloController.java,代码如下:
@RestController
public class HelloController {
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String getUser() {
return "GET-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.POST)
public String saveUser() {
return "POST-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.PUT)
public String putUser() {
return "PUT-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.DELETE)
public String deleteUser() {
return "DELETE-张三";
}
}
修改配置文件application.yaml,代码如下:
spring:
mvc:
# 开启 HiddenHttpMethodFilter
hiddenmethod:
filter:
#开启页面表单的Rest功能,选择性开启,无页面交互即可关闭【前后端分离】
enabled: true
修改首页index.html,代码如下:
测试REST风格;
<form action="/user" method="get">
<input value="REST-GET 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input value="REST-POST 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="delete"/>
<input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT"/>
<input value="REST-PUT 提交" type="submit"/>
</form>
测试,启动工程,浏览器访问首页:http://localhost:8080/,
4.3.1.2)Rest原理
相关源码如下:
WebMvcAutoConfiguration.java中的 OrderedHiddenHttpMethodFilter方法,源码如下:
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"},
matchIfMissing = false
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
HiddenHttpMethodFilter.java中的 doFilterInternal方法,源码如下:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
Rest原理(表单提交要使用REST的时候):
表单提交会带上_method=PUT
请求过来被HiddenHttpMethodFilter拦截,执行下列操作:
请求是否正常,并且是POST
获取到_method的值
兼容以下请求【PUT.DELETE.PATCH】
原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值;
过滤器链放行的时候用wrapper,以后的方法调用getMethod是调用requesWrapper的
执行过程如下图:
Rest使用客户端工具,如PostMan直接发送Put、delete等方式请求,则无需Filter
扩展点:如何把 _method 修改成自定义的?
新建配置类 WebConfig.java,代码如下:
@Configuration
public class WebConfig {
@Bean
// 开发者实现 HiddenHttpMethodFilter()方法,将 _method修改为 _m
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
}
修改首页index.html,代码如下:
<form action="/user" method="post">
<input name="_method" type="hidden" value="delete"/>
<input name="_m" type="hidden" value="delete"/>
<input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT"/>
<input value="REST-PUT 提交" type="submit"/>
</form>
测试:delete添加"m",可以执行;put未添加 "m",不可以执行,页面如下所示
4.3.2)请求映射原理
SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch() 开始
SpingBoot中的调用顺序如下图:
DispatcherServlet.java中的doDispatch() 源码如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 找到当前请求使用哪个Handler(Controller的方法)处理
mappedHandler = getHandler(processedRequest);
//HandlerMapping:处理器映射。/xxx->>xxxx
...
getHandler() 源码如下:
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
HandlerMapping mapping = (HandlerMapping)var2.next();
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
执行过程中,this.handlerMappings【处理器映射有5个】,如下图所示:
RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。
所有的请求映射【每个请求由谁负责处理】都在HandlerMapping中:
-
SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping ,访问 /能访问到index.html;
-
SpringBoot自动配置了默认的 RequestMappingHandlerMapping
-
请求进来,挨个尝试所有的HandlerMapping看是否有请求信息:
-
-
如果有就找到这个请求对应的handler
-
如果没有就是下一个 HandlerMapping
-
4.3.3)SpringMVC处理Web请求
4.3.3.1)注解
4.3.3.1.1)@PathVariable
@PathVariable:获取到路径中变量的值
工程SpringBootDemo3中修改ParameterTestController.java,代码如下:
@RestController
public class ParameterTestController {
// @PathVariable:获取到路径中变量的值
// car/3/owner/lisi
@GetMapping("/car/{id}/owner/{username}")
public Map<String, Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String, String> pv) {
Map<String, Object> map = new HashMap<>();
map.put("路径中变量id ",id);
map.put("路径中变量name ",name);
map.put("路径中所有变量Map",pv);
return map;
}
修改首页index.html,代码如下:
测试基本注解:
<ul>
<a href="car/3/owner/lisi">car/{id}/owner/{username}</a>
<li>@PathVariable(路径变量)</li>
</ul>
测试:工程启动,浏览器访问首页http://localhost:8080/,点击超链接“car/{id}/owner/{username}”,页面跳转到:http://localhost:8080/car/3/owner/lisi,页面如下所示:
4.3.3.1.2)@RequestHeader
@RequestHeader:获取请求头信息中的值
修改ParameterTestController.java,代码如下:
@GetMapping("/car/{id}/owner/{username}")
public Map<String, Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String, String> pv,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String, String> header) {
Map<String, Object> map = new HashMap<>();
map.put("路径中变量id ", id);
map.put("路径中变量name ", name);
map.put("路径中所有变量Map", pv);
map.put("请求头中变量userAgent", userAgent);
map.put("请求头中所有变量Map", header);
return map;
}
修改首页i
ndex.html,代码如下:
测试基本注解:
<ul>
<a href="car/3/owner/lisi">car/{id}/owner/{username}</a>
<li>@PathVariable(路径变量)</li>
<li>@RequestHeader(获取请求头)</li>
</ul>
测试:工程启动,浏览器访问首页http://localhost:8080/,点击超链接“car/{id}/owner/{username}”,页面跳转到:http://localhost:8080/car/3/owner/lisi,页面如下所示:
4.3.3.1.3)@RequestParam
@RequestParam:获取请求参数信息中的值
修改ParameterTestController.java,代码如下:
@GetMapping("/car/{id}/owner/{username}")
public Map<String, Object> getCar(@RequestParam("age") Integer age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String, String> params) {
Map<String, Object> map = new HashMap<>();
map.put("请求参数中变量age", age);
map.put("请求参数中集合inters", inters);
map.put("请求参数中所有变量Map", params);
return map;
}
修改首页index.html,代码如下:
测试基本注解:
<ul>
<a href="car/3/owner/lisi?age=18&inters=basketball&inters=game">car/{id}/owner/{username}</a>
<li>@PathVariable(路径变量)</li>
<li>@RequestHeader(获取请求头)</li>
<li>@RequestParam(获取请求参数)</li>
</ul>
测试:工程启动,浏览器访问首页http://localhost:8080/,点击超链接“car/{id}/owner/{username}”,页面跳转到:http://localhost:8080/car/3/owner/lisi?age=18&inters=basketball&inters=game,页面如下所示:
4.3.3.1.4)@CookieValue
@CookieValue:获取cookie信息中的值
修改ParameterTestController.java,代码如下:
@GetMapping("/car/{id}/owner/{username}")
public Map<String, Object> getCar( @CookieValue("_ga") String _ga,
@CookieValue("_ga") Cookie cookie) {
Map<String, Object> map = new HashMap<>();
map.put("cookie信息中变量_ga", _ga);
map.put("cookie信息的所有内容", cookie);
System.out.println(cookie.getName() + "===>" + cookie.getValue());
return map;
}
修改首页index.html,代码如下:
测试基本注解:
<ul>
<a href="car/3/owner/lisi?age=18&inters=basketball&inters=game">car/{id}/owner/{username}</a>
<li>@PathVariable(路径变量)</li>
<li>@RequestHeader(获取请求头)</li>
<li>@RequestParam(获取请求参数)</li>
<li>@CookieValue(获取cookie值)</li>
</ul>
测试:工程启动,浏览器访问首页http://localhost:8080/,点击超链接“car/{id}/owner/{username}”,页面跳转到:http://localhost:8080/car/3/owner/lisi?age=18&inters=basketball&inters=game,页面如下所示:
控制台输出:
_ga===>GA1.1.1657823623.1653548910
4.3.3.1.5)@RequestBody
@RequestBody:获取请求体信息中的值
修改ParameterTestController.java,代码如下:
// @RequestBody:获取请求体信息中的值
@PostMapping("/save")
public Map postMethod(@RequestBody String content) {
Map<String, Object> map = new HashMap<>();
map.put("请求体信息的所有内容", content);
return map;
}
修改首页index.html,代码如下:
<br/>
<form action="/save" method="post">
测试@RequestBody获取数据 <br/>
用户名:<input name="userName"/> <br>
邮箱:<input name="email"/>
<input type="submit" value="提交"/>
</form>
测试:工程启动,浏览器访问首页http://localhost:8080/,输入用户名和邮箱信息,页面跳转到:http://localhost:8080/save,页面如下所示:
4.3.3.1.6)@RequestAttribute
@RequestAttribute:获取request域中保存的属性【request域中设置属性用于页面转发时可以获取当前请求的数据】
新建RequestController.java,代码如下:
@Controller
public class RequestController {
// request域中保存属性
@GetMapping("/goto")
public String goToPage(HttpServletRequest request){
request.setAttribute("msg","成功了...");
request.setAttribute("code",200);
//转发到 /success请求
return "forward:/success";
}
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute(value = "msg",required = false) String msg,
@RequestAttribute(value = "code",required = false)Integer code,
HttpServletRequest request){
Object msg1 = request.getAttribute("msg");
Map<String,Object> map = new HashMap<>();
// 方式一:@RequestAttribute获取request中的属性值
map.put("@RequestAttribute获取request",msg1);
// 方式二:原生HttpServletRequest获取request中的属性值
map.put("原生HttpServletRequest获取request",msg);
return map;
}
}
测试:工程启动,浏览器访问跳转页 :http://localhost:8080/goto,页面如下所示:
4.3.3.1.7)@MatrixVariable
@MatrixVariable:矩阵变量
获取请求中的变量值:
-
@RequestParam:/cars/{path}?xxx=xxx&aaa=ccc;
-
矩阵变量【; 表示矩阵变量】:/cars/sell;low=34;brand=byd,audi,yd
矩阵变量需要在SpringBoot中手动开启
根据RFC3986的规范,矩阵变量应当绑定在路径变量中
若是有多个矩阵变量,应当使用英文符号;进行分隔
若是一个矩阵变量有多个值,应当使用英文符号,进行分隔,或之命名多个重复的key即可
手动开启矩阵变量
-
方式一:实现WebMvcConfigurer接口,重写configurePathMatch方法,设置RemoveSemicolonContent为false;修改配置类WebConfig.java,代码如下:
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
// 手动开启矩阵变量方式一:实现WebMvcConfigurer接口,重写configurePathMatch方法,设置RemoveSemicolonContent为false
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效【手动开启矩阵变量】
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
-
方式二:重写WebMvcConfigurer方法定制化SpringMVC的功能,设置RemoveSemicolonContent为false,修改配置类WebConfig.java,代码如下:
@Configuration(proxyBeanMethods = false)
public class WebConfig {
// 手动开启矩阵变量方式二:重写WebMvcConfigurer方法定制化SpringMVC的功能,设置RemoveSemicolonContent为false
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
}
修改ParameterTestController.java,代码如下:
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path) {
Map<String, Object> map = new HashMap<>();
map.put("矩阵变量中的变量low", low);
map.put("矩阵变量中的变量brand", brand);
map.put("路径中变量path", path);
return map;
}
修改首页index.html,代码如下:
测试基本注解:
<ul>
<a href="car/3/owner/lisi?age=18&inters=basketball&inters=game">car/{id}/owner/{username}</a>
<li>@PathVariable(路径变量)</li>
<li>@RequestHeader(获取请求头)</li>
<li>@RequestParam(获取请求参数)</li>
<li>@CookieValue(获取cookie值)</li>
<li>@RequestBody(获取请求体[POST])</li>
<li>@RequestAttribute(获取request域属性)</li>
<li>@MatrixVariable(矩阵变量)</li>
</ul>
<br/>
<a href="/cars/sell;low=34;brand=byd,audi,yd">@MatrixVariable(矩阵变量)</a><br/>
<a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">@MatrixVariable(矩阵变量)</a><br/>
测试:工程启动,浏览器访问首页 :http://localhost:8080/,点击超链接“1)@MatrixVariable(矩阵变量)”,页面跳转至:http://localhost:8080/cars/sell;low=34;brand=byd,audi,yd ,点击超链接“2)@MatrixVariable(矩阵变量)”,页面跳转至:http://localhost:8080/cars/sell;low=34;brand=byd;brand=audi;brand=yd,页面如下所示:
如果有多条访问请求,请求中的参数如何确定是那一条请求对应的?【pathVar用来区分不同的请求】
修改ParameterTestController.java,代码如下:
@GetMapping("/boss/{bossId}/{empId}")
public Map boss(@MatrixVariable(value = "age", pathVar = "bossId") Integer bossAge,
@MatrixVariable(value = "age", pathVar = "empId") Integer empAge) {
Map<String, Object> map = new HashMap<>();
map.put("bossAge", bossAge);
map.put("empAge", empAge);
return map;
}
修改首页index.html,代码如下:
<a href="/cars/sell;low=34;brand=byd,audi,yd">1)@MatrixVariable(矩阵变量)</a><br/>
<a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">2)@MatrixVariable(矩阵变量)</a><br/>
<a href="/boss/1;age=20/2;age=10">@MatrixVariable(矩阵变量)/boss/{bossId}/{empId}</a><br/>
测试:工程启动,浏览器访问首页 :http://localhost:8080/,点击超链接“@MatrixVariable(矩阵变量)/boss/{bossId}/{empId}”,页面跳转至:http://localhost:8080/boss/1;age=20/2;age=10,页面如下所示:
4.3.3.2)Servlet API
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
ServletRequestMethodArgumentResolver 以上的部分参数
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
Principal.class.isAssignableFrom(paramType) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
4.3.3.3)复杂参数
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
修改RequestController.java,代码如下:
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute(value = "msg", required = false) String msg,
@RequestAttribute(value = "code", required = false) Integer code,
HttpServletRequest request) {
Object msg1 = request.getAttribute("msg");
Map<String, Object> map = new HashMap<>();
// 方式一:@RequestAttribute获取request中的属性值
map.put("@RequestAttribute获取request", msg1);
// 方式二:原生HttpServletRequest获取request中的属性值
map.put("原生HttpServletRequest获取request", msg);
Object hello = request.getAttribute("hello");
Object world = request.getAttribute("world");
Object message = request.getAttribute("message");
map.put("hello",hello);
map.put("world",world);
map.put("message",message);
return map;
}
@GetMapping("/params")
public String testParam(Map<String, Object> map,
Model model,
HttpServletRequest request,
HttpServletResponse response) {
map.put("hello", "world666");
model.addAttribute("world", "hello666");
request.setAttribute("message", "HelloWorld");
Cookie cookie = new Cookie("c1", "v1");
response.addCookie(cookie);
return "forward:/success";
}
测试:工程启动,浏览器访问首页 :http://localhost:8080/params,页面如下所示:
Map、Model类型的参数,会返回 mavContainer.getModel();---> BindingAwareModelMap 是Model 也是Map
ModelAndViewContainer.java的源码如下:
public class ModelAndViewContainer {
private boolean ignoreDefaultModelOnRedirect = false;
@Nullable
private Object view;
private final ModelMap defaultModel = new BindingAwareModelMap();
@Nullable
private ModelMap redirectModel;
private boolean redirectModelScenario = false;
...
}
mavContainer.getModel(); 获取到值的
public ModelMap getModel() {
if (this.useDefaultModel()) {
return this.defaultModel;
} else {
if (this.redirectModel == null) {
this.redirectModel = new ModelMap();
}
return this.redirectModel;
}
}
如下图:
4.3.3.4)自定义对象参数
可以自动类型转换与格式化,可以级联封装。
新建Bean对象 Person.java,代码如下:
@Data
public class Person {
private String userName;
private Integer age;
private Date birth;
private Pet pet;
}
新建Bean对象 Pet.java,代码如下:
@Data
public class Pet {
private String name;
private Integer age;
}
修改ParameterTestController.java,代码如下:
@PostMapping("/saveuser")
public Person saveuser(Person person) {
return person;
}
修改首页index.html,代码如下:
测试封装POJO;
<form action="/saveuser" method="post">
姓名: <input name="userName" value="zhangsan"/> <br/>
年龄: <input name="age" value="18"/> <br/>
生日: <input name="birth" value="2019/12/10"/> <br/>
<!--级联属性-->
宠物姓名:<input name="pet.name" value="阿猫"/><br/>
宠物年龄:<input name="pet.age" value="5"/>
<input type="submit" value="保存"/>
</form>
测试:工程启动,浏览器访问首页 :http://localhost:8080/,点击“保存”按钮,页面跳转至:http://localhost:8080/saveuser;页面如下所示:
自定义Converter,不使用级联属性
修改首页index.html,代码如下:【直接点击“保存”按钮会报错400】
测试封装POJO;
<form action="/saveuser" method="post">
姓名: <input name="userName" value="zhangsan"/> <br/>
年龄: <input name="age" value="18"/> <br/>
生日: <input name="birth" value="2019/12/10"/> <br/>
<!--自定义Converter-->
宠物: <input name="pet" value="啊猫,3"/>
<input type="submit" value="保存"/>
</form>
自定义Converter,修改配置文件WebConfig.java,代码如下:
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
// 自定义对象参数 --》自定义Converter,不使用级联属性
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
// 啊猫,3
if (!StringUtils.isEmpty(source)) {
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
测试:工程启动,浏览器访问首页 :http://localhost:8080/,点击“保存”按钮,页面跳转至:http://localhost:8080/saveuser;页面如下所示: