前言
包括请求映射、常用参数获取与基本注解、参数处理、POJO对象封装等。
一、请求映射
在学习SpringMVC的时候,@RequestMapping、@GetMapping、@PostMapping、@PutMapping、@DeleteMapping、Rest风格大家都一定学会了。这里我们就大致讲解一下在SpringBoot的使用,跟SpringMVC有些出入。
- @xxxMapping;
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
- Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
- 以前:
- /getUser 获取用户
- /deleteUser 删除用户
- /editUser 修改用户
- /saveUser保存用户
- 现在(SpringBoot): /user
- GET-获取用户
- DELETE-删除用户
- PUT-修改用户
- POST-保存用户
- 核心Filter;HiddenHttpMethodFilter
需要讲解一下:SpringMVC中我们用到的Rest:
但是在SpringBoot在底层做了更加精简的操作,现在Rest风格就是上述/user就可以是get、post、delete、put请求任意一种。而SpringBoot如何区分呢,我们先回顾一下SpringMVC的解决办法如下:
大家如果对SpringMVC的Rest风格有点遗忘,可以阅读我前面的文章回顾一下
SpringMVC之RESTful
SpringBoot同样是用的HiddenHttpMethodFilter,这一点是毋庸置疑的,但是SpringBoot的Rest功能默认是关闭的,所以我们需要手动开启。
- 用法
- 开启页面表单的Rest功能
- 页面 form的属性method=post,隐藏域 _method=put、delete等(如果直接get或post,无需隐藏域)
- 编写请求映射
spring: mvc: hiddenmethod: filter: enabled: true #开启页面表单的Rest功能
1.实例
html页面:
<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>
Controller层:
@GetMapping("/user") //@RequestMapping(value = "/user",method = RequestMethod.GET) public String getUser(){ return "GET-张三"; } @PostMapping("/user") //@RequestMapping(value = "/user",method = RequestMethod.POST) public String saveUser(){ return "POST-张三"; } @PutMapping("/user") //@RequestMapping(value = "/user",method = RequestMethod.PUT) public String putUser(){ return "PUT-张三"; } @DeleteMapping("/user") //@RequestMapping(value = "/user",method = RequestMethod.DELETE) public String deleteUser(){ return "DELETE-张三"; }
2.Rest原理
- Rest原理(表单提交要使用REST的时候)
- 表单提交会带上
_method=PUT
- 请求过来被
HiddenHttpMethodFilter
拦截
- 请求是否正常,并且是POST
- 获取到
_method
的值。 - 兼容以下请求;PUT.DELETE.PATCH
- 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
- 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
底层源码:
public class HiddenHttpMethodFilter extends OncePerRequestFilter { private static final List<String> ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name())); /** Default method parameter: {@code _method}. */ public static final String DEFAULT_METHOD_PARAM = "_method"; private String methodParam = DEFAULT_METHOD_PARAM; /** * Set the parameter name to look for HTTP methods. * @see #DEFAULT_METHOD_PARAM */ public void setMethodParam(String methodParam) { Assert.hasText(methodParam, "'methodParam' must not be empty"); this.methodParam = methodParam; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest requestToUse = request; if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) { String paramValue = request.getParameter(this.methodParam); if (StringUtils.hasLength(paramValue)) { String method = paramValue.toUpperCase(Locale.ENGLISH); if (ALLOWED_METHODS.contains(method)) { requestToUse = new HttpMethodRequestWrapper(request, method); } } } filterChain.doFilter(requestToUse, response); } /** * Simple {@link HttpServletRequest} wrapper that returns the supplied method for * {@link HttpServletRequest#getMethod()}. */ private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper { private final String method; public HttpMethodRequestWrapper(HttpServletRequest request, String method) { super(request); this.method = method; } @Override public String getMethod() { return this.method; } } }
在底层WebMvcAutoConfiguration类中:
@Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration { ... @Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { return new OrderedHiddenHttpMethodFilter(); } ... }
有个@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
意味着在没有HiddenHttpMethodFilter时,才执行hiddenHttpMethodFilter()。因此,我们可以自定义filter,改变默认的_method:
@Configuration(proxyBeanMethods = false) public class WebConfig{ @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter() { HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter(); methodFilter.setMethodParam("_m"); return methodFilter; }
原来的_method失效,应该用_m。
3.补充
所有的请求映射都在HandlerMapping中:
- SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
- SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
- 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
- 如果有就找到这个请求对应的handler
- 如果没有就是下一个 HandlerMapping
- 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
二、常用参数注解
注解:
@PathVariable
路径变量@RequestHeader
获取请求头@RequestParam
获取请求参数(指问号后的参数,url?a=1&b=2)@CookieValue
获取Cookie值@RequestAttribute
获取request域属性@RequestBody
获取请求体[POST]@MatrixVariable
矩阵变量@ModelAttribute
1.@RequestAttribute
@RequestAttribute中value属性值是要在requset请求域中获取的参数;required = false代表该请求域中非必须有该参数,即使没有也不会报错。
@Controller public class RequestController { @GetMapping("/goto") public String goToPage(HttpServletRequest request){ request.setAttribute("msg","成功了..."); request.setAttribute("code",200); return "forward:/success"; //转发到 /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<>(); map.put("reqMethod_msg",msg1); map.put("annotation_msg",msg); return map; } }
2.@MatrixVariable与UrlPathHelper
- 语法: 请求路径:
/cars/sell;low=34;brand=byd,audi,yd
- SpringBoot默认是禁用了矩阵变量的功能
- 手动开启:原理。对于路径的处理。UrlPathHelper的removeSemicolonContent设置为false,让其支持矩阵变量的。
- 矩阵变量必须有url路径变量才能被解析:
手动开启矩阵变量:
- 实现
WebMvcConfigurer
接口,创建返回WebMvcConfigurer
Bean:
@Configuration(proxyBeanMethods = false) public class WebConfig{ @Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer() { @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper(); // 不移除;后面的内容。矩阵变量功能就可以生效 urlPathHelper.setRemoveSemicolonContent(false); configurer.setUrlPathHelper(urlPathHelper); } } } }
@MatrixVariable
的用例
@RestController public class ParameterTestController { ///cars/sell;low=34;brand=byd,audi,yd @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; } // /boss/1;age=20/2;age=10 @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; } }
html页面:
<a href="/cars/sell;low=34;brand=byd,audi,yd">测试MatrixVariable1</a> <a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">测试MatrixVariable2</a> <a href="/boss/1;age=20/2;age=10">测试MatrixVariable3</a>
/boss/1;age=20/2;age=10
对应的@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge, @MatrixVariable(value = "age",pathVar = "empId") Integer empAge
的参数:首先路径请求是/1;age=20
和/2;age=10
两部分,矩阵变量是根据分号进行分割,针对每一个Parh Variable绑定一个Matrix Variable,然后使用 value 和 pathVar属性就能找到该值。
3.其他注解
@PathVariable、@RequestHeader、@RequestParam、@CookieValue、@RequestBody
@RestController public class ParameterTestController { // car/2/owner/zhangsan @GetMapping("/car/{id}/owner") public Map<String,Object> getCar(@PathVariable("id") Integer id, @PathVariable Map<String,String> pv, @RequestHeader("User-Agent") String userAgent, @RequestHeader Map<String,String> header, @RequestParam Map<String,String> params, @CookieValue("_ga") String _ga, @CookieValue("_ga") Cookie cookie){ Map<String,Object> map = new HashMap<>(); map.put("id",id); map.put("pv",pv); map.put("userAgent",userAgent); map.put("headers",header); map.put("params",params); map.put("_ga",_ga); System.out.println(cookie.getName()+"===>"+cookie.getValue()); return map; } @PostMapping("/save") public Map postMethod(@RequestBody String content){ Map<String,Object> map = new HashMap<>(); map.put("content",content); return map; } }
html页面:
<ul> <a href="/car/3/owner?username=lisi">car/3/owner/lisi</a> <li>@PathVariable(路径变量)</li> <li>@RequestHeader(获取请求头)</li> <li>@RequestParam(获取请求参数)</li> <li>@CookieValue(获取cookie值)</li> <li>@RequestBody(获取请求题)</li> </ul> <form action="/save" method="post"> 测试@RequestBody</br> 用户名:<input name="username" type="text"><br> 邮箱:<input name="email" type="text"><br> <input type="submit" value="提交"> </form>
上面的@CookieValue("_ga") String _ga,@CookieValue("_ga") Cookie cookie
前提是你浏览器有这个cookie对象。我这里就不再测试这俩。
4.感受model、map对象获取
能够直接在request请求域中直接获取
@Controller public class RequestController { @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"; } @ResponseBody @GetMapping("/success") public Map success(HttpServletRequest request, @CookieValue("c1") String c1){ Map<String,Object> map = new HashMap<>(); 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); map.put("cookie",c1); return map; } }
补充
底层源码:
public class InternalResourceView extends AbstractUrlBasedView { @Override//该方法在AbstractView,AbstractUrlBasedView继承了AbstractView public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { ... Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); //看下一个方法实现 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); } @Override protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose the model object as request attributes. // 暴露模型作为请求域属性 exposeModelAsRequestAttributes(model, request);//<---重点 // Expose helpers as request attributes, if any. exposeHelpers(request); // Determine the path for the request dispatcher. String dispatcherPath = prepareForRendering(request, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); ... } //该方法在AbstractView,AbstractUrlBasedView继承了AbstractView protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception { model.forEach((name, value) -> { if (value != null) { request.setAttribute(name, value); } else { request.removeAttribute(name); } }); } }
exposeModelAsRequestAttributes方法看出,Map<String,Object> map,Model model这两种类型数据可以给request域中放数据,用request.getAttribute()获取。
三、POJO自动封装(自定义Converter)
SpringBoot能够将POJO对象自动封装,其实我们在SpringMVC学习中就已经自动自动封装了。这里补充一下自定义参数绑定封装。
Controller层:
@RestController public class ParameterTestController { /** * 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定 * @param person * @return */ @PostMapping("/saveuser") public Person saveuser(Person person){ return person; } }
POJO:
@Data public class Person { private String userName; private Integer age; private Date birth; private Pet pet; } @Data public class Pet { private String name; private Integer age; }
html页面:
<form action="/saveuser" method="post"> 姓名: <input name="userName" value="zhangsan"/> <br/> 年龄: <input name="age" value="20"/> <br/> 生日: <input name="birth" value="2003/11/22"/> <br/> 宠物姓名:<input name="pet.name" value="阿毛"/><br/> 宠物年龄:<input name="pet.age" value="5"/><br> <!-- 宠物:<input name="pet" value="阿毛,3"/>--> <input value="保存"type="submit" /> </form>
但如果是这种:
我们就要自己写一个Converter:
SpringBoot中WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中。在过程当中,用到GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型。所以我们可以给WebDataBinder里面放自己的Converter。
@Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer() { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new Converter<String, Pet>() { //Converter<String, Pet>String是传入要转换值类型,Pet是要转换的类型 @Override public Pet convert(String source) {//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; } }); } }; }
总结
以上就是请求映射、POJO对象封装、常用参数注解的讲解。