3、请求映射
3.1、表单Delete提交问题
我们可以使用_method方式来提交delete和put等请求方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>表单</div>
<div>
<form action="/user" method="get">
<input value="提交 get" type="submit">
</form>
<form action="/user" method="post">
<input value="提交 post" type="submit">
</form>
<form action="/user" method="post">
<input name="_method" value="DELETE" type="hidden">
<input value="提交 delete" type="submit">
</form>
<form action="/user" method="post">
<input name="_method" value="put" type="hidden">
<input value="提交 put" type="submit">
</form>
</div>
</body>
</html>
package com.daijunyi.springbootweb.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@RequestMapping(path = "/user",method = RequestMethod.GET)
public Object getUser(){
return "张三";
}
@RequestMapping(path = "/user",method = RequestMethod.POST)
public Object saveUser(){
return "保存张三";
}
@RequestMapping(path = "/user",method = RequestMethod.DELETE)
public Object deleteUser(){
return "删除 张三";
}
@RequestMapping(path = "/user",method = RequestMethod.PUT)
public Object putUser(){
return "put 张三";
}
}
SpringBoot帮我们配置了hiddenmethod,但是默认是不开启的
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
开启功能
spring:
mvc:
hiddenmethod:
filter:
enabled: true
源码:
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);
}
- 表单提交带上_method=put
请求过来被HiddenHttpMethodFilter拦截
- 请求正常,并且是POST方法
_method请求方式只要是
static { ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name())); }
3.2、请求分配
3.2.1、普通参数与基本注解
@RequestAttribute@Controller public class RequestAttributeTestController { @GetMapping("/goto") public Object goToSuccess(HttpServletRequest request){ request.setAttribute("msg","我来了"); request.setAttribute("code",200); return "forward:/success"; } @ResponseBody @GetMapping("/success") public Object success(@RequestAttribute("msg") String msg, @RequestAttribute("code") String code, HttpServletRequest request){ HashMap<Object, Object> map = new HashMap<>(); map.put("msg_1",msg); map.put("msg_2",request.getAttribute("msg")); return map; } }
package com.daijunyi.springbootweb.controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.Cookie;
import java.util.HashMap;
import java.util.Map;
@RestController
public class ParameterTestController {
@GetMapping("/car/{id}/owner/{username}")
public Map getCar(@PathVariable("id") Integer id,
@PathVariable("username") String username,
@PathVariable Map<String,Object> value,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String,Object> headers,
@RequestParam("name") String name,
@RequestParam Map params,
@CookieValue("_ga") String ag,
@CookieValue("ga") Cookie cookie
){
HashMap<Object, Object> map = new HashMap<>();
map.put("id",id);
map.put("username",username);
map.put("values",value);
map.put("userAgent",userAgent);
map.put("headers",headers);
map.put("name",name);
map.put("params",params);
return map;
}
@PostMapping("/car")
public Object postCar(@RequestBody String content){
return content;
}
}
3.2.2、矩阵变量
- 矩阵变量请求默认SpringBoot是不开启的
开启配置需要自行配置UrlPathHelper相关设置
- 配置有两种方法@Configuration+WebMvcConfigurer
还有一种就是@Bean+创建WebMvcConfigurer实现类注入容器中
//矩阵变量请求 /cars/sell;low=34;brand=byd,audi,bmw //SpringBoot默认禁用了矩阵变量 // 手动开启:原理,对于路径的处理,UrlPathHelper进行解析。removeSemicolonContent支持矩阵变量的 @GetMapping("/cars/{path}") public Object carsSell(@MatrixVariable("low") String low, @MatrixVariable("brand") List<String> brand, @PathVariable("path") String path){ HashMap<Object, 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 Object boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossId, @MatrixVariable(value = "age",pathVar = "empId") Integer empId){ HashMap<Object, Object> map = new HashMap<>(); map.put("bossId",bossId); map.put("empId",empId); return map; }
```java package com.daijunyi.springbootweb.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.util.UrlPathHelper;
@Configuration(proxyBeanMethods = false) public class MyConfig implements WebMvcConfigurer {
// @Bean // public WebMvcConfigurer webMvcConfigurer(){ // WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer() { // @Override // public void configurePathMatch(PathMatchConfigurer configurer) { // UrlPathHelper urlPathHelper = new UrlPathHelper(); // urlPathHelper.setRemoveSemicolonContent(false); // configurer.setUrlPathHelper(urlPathHelper); // } // }; // return webMvcConfigurer; // }
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
- 当项目启动的时候,返回容器中会有2个WebMvcConfigurer相关的类,然后for循环调用一遍,都执行以下

<a name="qk7lv"></a>
### 3.3、参数处理原理
<a name="Mr3Bq"></a>
#### 3.3.1、请求分配路径
在类DispatcherServlet中doGet->processRequest->doService->doDispatch
<a name="IOWNO"></a>
#### 3.3.2、doDispatch详解
- 获取到合适的请求处理器HandlerAdapter 找到 RequestMappingHandlerAdapter
```java
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
- 4种支持的HandlerAdapter
执行目标方法
// Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
调用了RequestMappingHandlerAdapter中的handleInternal方法
@Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ... // Execute invokeHandlerMethod in synchronized block if required. if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No synchronization on session demanded at all... mav = invokeHandlerMethod(request, response, handlerMethod); } .... return mav; }
再次调用handleInternal中invokeHandlerMethod方法
@Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { .... invocableMethod.invokeAndHandle(webRequest, mavContainer); .... }
调用invokeHandlerMethod中的invokeAndHandle
@Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { .... invocableMethod.invokeAndHandle(webRequest, mavContainer); .... }
调用 invokeAndHandle中的invokeForRequest
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { .... Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); ....
调用invokeForRequest中的doInvoke方法
@Nullable public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } return doInvoke(args); }
doInvoke中的method.invoke(getBean(), args) 反射调用方法
@Nullable protected Object doInvoke(Object... args) throws Exception { Method method = getBridgedMethod(); ReflectionUtils.makeAccessible(method); try { if (KotlinDetector.isSuspendingFunction(method)) { return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args); } return method.invoke(getBean(), args); } ..... }
3.3.3、参数处理器
27个参数处理器
处理器是个接口判断哪个解析器支持当前参数解析在类HandlerMethodArgumentResolverComposite中
//HandlerMethodArgumentResolverComposite @Nullable private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) { if (resolver.supportsParameter(parameter)) { result = resolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; }
当获取到合适的解析器之后,再调用解析器的解析方法进行解析
HandlerMethodArgumentResolverComposite @Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); if (resolver == null) { throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first."); } return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); }
解析器接口有两个方法
- supportsParameter 是否支持解析该参数
resolveArgument 解析参数 ```java public interface HandlerMethodArgumentResolver {
/**
- Whether the given {@linkplain MethodParameter method parameter} is
- supported by this resolver.
- @param parameter the method parameter to check
- @return {@code true} if this resolver supports the supplied parameter;
- {@code false} otherwise */ boolean supportsParameter(MethodParameter parameter);
/**
- Resolves a method parameter into an argument value from a given request.
- A {@link ModelAndViewContainer} provides access to the model for the
- request. A {@link WebDataBinderFactory} provides a way to create
- a {@link WebDataBinder} instance when needed for data binding and
- type conversion purposes.
- @param parameter the method parameter to resolve. This parameter must
- have previously been passed to {@link #supportsParameter} which must
- have returned {@code true}.
- @param mavContainer the ModelAndViewContainer for the current request
- @param webRequest the current request
- @param binderFactory a factory for creating {@link WebDataBinder} instances
- @return the resolved argument value, or {@code null} if not resolvable
- @throws Exception in case of errors with the preparation of argument values
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
- Servlet请求方法参数处理器 ServletRequestMethodArgumentResolver
- 可以处理,WebRequest、ServletRequest、MultipartRequest、HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
```java
@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) && !parameter.hasParameterAnnotations()) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
3.3.1、ServletModelAttributeMethodProcessor 用来处理 自定义数据
ServletModelAttributeMethodProcessor 父类ModelAttributeMethodProcessor 中的方法
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
GenericConversionService 类中有124种类型转换器
@Nullable//GenericConversionService
protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
GenericConverter converter = this.converterCache.get(key);
if (converter != null) {
return (converter != NO_MATCH ? converter : null);
}
converter = this.converters.find(sourceType, targetType);
if (converter == null) {
converter = getDefaultConverter(sourceType, targetType);
}
if (converter != null) {
this.converterCache.put(key, converter);
return converter;
}
this.converterCache.put(key, NO_MATCH);
return null;
}
转换器设计 Sring->Number
private static final class StringToNumber<T extends Number> implements Converter<String, T> {
private final Class<T> targetType;
public StringToNumber(Class<T> targetType) {
this.targetType = targetType;
}
@Override
@Nullable
public T convert(String source) {
if (source.isEmpty()) {
return null;
}
return NumberUtils.parseNumber(source, this.targetType);
}
}
3.3.2、请求参数中的 Map和Model添加的参数是会在转发的时候添加到request的请求域中的
@Controller
public class RequestAttributeTestController {
@GetMapping("/goto")
public Object goToSuccess(HttpServletRequest request, Map map, Model model){
request.setAttribute("msg","我来了");
request.setAttribute("code",200);
map.put("msg1","map");
model.addAttribute("msg2","mode");
return "forward:/success";
}
@ResponseBody
@GetMapping("/success")
public Object success(@RequestAttribute("msg") String msg,
@RequestAttribute("code") String code,
@RequestAttribute("msg1") String msg1,
@RequestAttribute("msg2") String msg2,
HttpServletRequest request){
HashMap<Object, Object> map = new HashMap<>();
map.put("msg_1",msg);
map.put("msg_2",request.getAttribute("msg"));
map.put("msg1",msg1);
map.put("msg2",msg2);
return map;
}
}
Map和Model参数在最终分别通过 MapMethodProcessor和ModelMethodProcessor 参数解析器解析完成之后都返回mavContainer.getModel()获取到的ModelMap
//ModelMethodProcessor @Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure"); return mavContainer.getModel(); }
//MapMethodProcessor @Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure"); return mavContainer.getModel(); }
最终指向的数据是这个defaultModel
public class ModelAndViewContainer { private final ModelMap defaultModel = new BindingAwareModelMap();
然之后在请求执行完成之后会对参数进行一些更新绑定等操作
//ModelFactory public void updateModel(NativeWebRequest request, ModelAndViewContainer container) throws Exception { //默认的 ModelMap再被获取出来 ModelMap defaultModel = container.getDefaultModel(); if (container.getSessionStatus().isComplete()){ this.sessionAttributesHandler.cleanupAttributes(request); } else { this.sessionAttributesHandler.storeAttributes(request, defaultModel); } if (!container.isRequestHandled() && container.getModel() == defaultModel) { //重新把ModelMap的参数绑定回request中 updateBindingResult(request, defaultModel); } }
在AbstractView中会ModelMap中的数据进行进一步封装成一个Map再通过renderMergedOutputModel放入请求Request中
//AbstractView @Override public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isDebugEnabled()) { logger.debug("View " + formatViewName() + ", model " + (model != null ? model : Collections.emptyMap()) + (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes)); } Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); }
InternalResourceView类中renderMergedOutputModel方法进一步调用exposeModelAsRequestAttributes方法完成参数设置
//InternalResourceView @Override protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose the model object as request attributes. exposeModelAsRequestAttributes(model, request); ..... }
在AbstractView类中exposeModelAsRequestAttributes方法通过setAttribute的原生方法完成参数的赋值。
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); } }); }