目录
4.7、@MatrixVariable与UrlPathHelper
1、请求处理-【源码分析】-Rest映射及源码解析
1.1、请求映射
- @xxxMapping;
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
以前:不同功能使用不同路径
- /getUser 获取用户
- /deleteUser 删除用户
- /editUser 修改用户
- /saveUser保存用户
Rest风格支持(使用HTTP请求方式动词来表示对资源的操作):保持访问路径不变
现在: /user
- GET-获取用户
- DELETE-删除用户
- PUT-修改用户
- POST-保存用户
- 核心Filter;HiddenHttpMethodFilter
1.2、用法
- 开启页面表单的Rest功能,因为默认时关闭的
- 页面 form的属性method=post,隐藏域 _method=put、delete等(如果直接get或post,无需隐藏域)
- 编写请求映射
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的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>
1.3、Rest原理(表单提交要使用REST的时候)
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);
}
PUT,
PATCH,
DELETE,
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
public String getMethod() {
return this.method;
}
}
}
- 1、表单提交会带上. _method=PUT _method=DELETE
- 2、请求过来被HiddenHttpMethodFilter拦截
- 请求是否正常,并且是POST
- 获取到\_method的值
- 兼容以下请求;PUT、DELETE、PATCH
- 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
- 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
注意:
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)。 看出rest风格默认时关闭的,使用时需要手动开启
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
public class WebMvcAutoConfiguration {
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
}
public class OrderedHiddenHttpMethodFilter extends HiddenHttpMethodFilter implements OrderedFilter {
}
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()));
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = 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);
}
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;
}
}
}
1.4、Rest使用客户端工具。
- 如PostMan可直接发送put、delete等方式请求。
- 表单只能写get、post方式,所以需要走过滤器:HiddenHttpMethodFilter。
- @RequestMapping(value = "/user",method = RequestMethod.GET) 等价与 @GetMapping("/user")
2、请求处理-【源码分析】-怎么改变默认的_method
@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{
//自定义filter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
}
将\_method
改成_m
<form action="/user" method="post">
<input name="_m" type="hidden" value="DELETE"/>
<input value="REST-DELETE 提交" type="submit"/>
</form>
3、请求处理-【源码分析】-请求映射原理
SpringMVC功能分析都从 org.springframework.web.servlet.
DispatcherServlet处理-> 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) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
this.handlerMappings
在Debug模式下展现的内容:有5个handlerMapping
其中,RequestMappingHandlerMapping:保存了所有@RequestMapping
和handler
的映射规则。
所有的映射:
所有的请求映射都在HandlerMapping中:
- SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
- SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
- 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
- 如果有就找到这个请求对应的handler
- 如果没有就是下一个 HandlerMapping
- 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping,可以自定义 HandlerMapping
4、请求处理-常用参数注解使用
注解:
http:8080:localhost/car/{id}/owner/{username}?age=18&inters=basketball&inters=game
- @PathVariable 路径变量 : id、username
- @RequestHeader 获取请求头
- @RequestParam 获取请求参数(指问号后的参数,url?a=1&b=2): age、inters
- @CookieValue 获取Cookie值
- @RequestAttribute 获取request域属性
- @RequestBody 获取请求体[POST]
- @MatrixVariable 矩阵变量
- @ModelAttribute
4.1、@PathVariable原理:
//If the method parameter is {@link java.util.Map Map<String, String> //then the map is populated with all path variable names and values. @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface PathVariable {
}
通过@PathVariable可以将请求中的所有参数放到map中。
使用用例
@RequestMapping("/car/{id}/owner/{username}") public Map getCar(@PathVariable("id") Integer id, @PathVariable("username") String username, @PathVariable Map<String,String> map){ Map<String,Object> maps = new HashMap<>(); maps.put("id",id); maps.put("username",username); maps.put("kv",map); return maps; }
4.2、@RequestHeader原理
通过@RequestHeader可以将请求头中的所有参数放到map中。
//If the method parameter is {@link java.util.Map Map<String, String>, //{@link org.springframework.util.MultiValueMap MultiValueMap<String, String>}, or {@link org.springframework.http}HttpHeaders HttpHeaders}@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestHeader {}
@RequestMapping("/car/{id}/owner/{username}")
public Map getCar(@PathVariable("id") Integer id,
PathVariable("username") String username,
@PathVariable Map<String,String> map,
@RequestHeader("User-Agent") String agent,
@RequestHeader Map<String,String> headers) {
Map<String,Object> maps = new HashMap<>();
maps.put("id",id);
maps.put("username",username);
maps.put("kv",map);
maps.put("header",agent);
maps.put("headers",headers);
return maps;
4.3、@RequestParam
通过@RequestParam可以将请求参数中的所有参数放到map中。
//If the method parameter is {@link java.util.Map Map<String, String>, //{@link org.springframework.util.MultiValueMap MultiValueMap<String, String>}, or {@link org.springframework.http}HttpHeaders HttpHeaders}@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestParam {}
@RequestMapping("/car/{id}/owner/{username}")
public Map getCar(@PathVariable("id") Integer id,
@PathVariable("username") String username,
@PathVariable Map<String,String> map,
@RequestHeader("User-Agent") String agent,
@RequestHeader Map<String,String> headers,
@RequestParam("age")String age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String,String> params){
Map<String,Object> maps = new HashMap<>();
maps.put("id",id);
maps.put("username",username);
maps.put("kv",map);
maps.put("header",agent);
maps.put("headers",headers);
maps.put("age",age);
maps.put("inters",inters);
maps.put("prams",params);
return maps;
}
4.4、@CookieValue
通过@CookieValue可以将cookie 放到Cookie中
//The method parameter may be declared as type {@link javax.servlet.http.Cookie} or as cookie value type (String, int, etc.). @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CookieValue {}
@RequestMapping("/car/{id}/owner/{username}")
public Map getCar(@PathVariable("id") Integer id,
@PathVariable("username") String username,
@PathVariable Map<String,String> map,
@RequestHeader("User-Agent") String agent,
@RequestHeader Map<String,String> headers,
@RequestParam("age")String age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String,String> params,
@CookieValue("_ga")String _ga,
@CookieValue("_ga") Cookie cookie){
Map<String,Object> maps = new HashMap<>();
maps.put("id",id);
maps.put("username",username);
maps.put("kv",map);
maps.put("header",agent);
maps.put("headers",headers);
maps.put("age",age);
maps.put("inters",inters);
maps.put("prams",params);
map.put("_ga",_ga);
System.out.println(cookie.getName() + ":" + cookie.getValue());
return maps;
}
_ga:GA1.1.1466056184.1630465944
4.5、@RequestBody
获取表单数据,因为表单有请求体。
@PostMapping("/save")
public Map postMethod(@RequestBody String content){
Map<String,Object> map = new HashMap<>();
map.put("content",content);
return map;
}
4.6、@RequestAttribute
@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") String msg,
@RequestAttribute(value = "code")Integer code,
HttpServletRequest request){
Object msg1 = request.getAttribute("msg");map.put("reqMethod_msg",msg1);
map.put("annotation_msg",msg);return map;
}
}
4.7、@MatrixVariable与UrlPathHelper
- 语法:
- 请求路径:/cars/sell;low=34;brand=byd,audi,yd
- SpringBoot默认是禁用了矩阵变量的功能
- 手动开启:原理是对于路径的处理,都是使用UrlPathHelper的removeSemicolonContent(移除分号内容)设置为false,让其支持矩阵变量的。
- 矩阵变量必须有url路径变量才能被解析
获取请求参数的两种方式:查询字符串和矩阵变量。
正常:服务端生成session存放内容,返回给客户端封装到cookie中生成jsessionid标识,再次请求时客户端携带cookie到服务端。
当cookie被禁用时,可以使用矩阵变量的方式将jsessionid传给后端。
@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,
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;}
}
手动开启矩阵变量:
- 实现
WebMvcConfigurer
接口:
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
- 创建返回
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);
}
}
}
}
ctrl + F12 : 继承树