3、请求映射

3.1、表单Delete提交问题

我们可以使用_method方式来提交delete和put等请求方式

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div>表单</div>
  9. <div>
  10. <form action="/user" method="get">
  11. <input value="提交 get" type="submit">
  12. </form>
  13. <form action="/user" method="post">
  14. <input value="提交 post" type="submit">
  15. </form>
  16. <form action="/user" method="post">
  17. <input name="_method" value="DELETE" type="hidden">
  18. <input value="提交 delete" type="submit">
  19. </form>
  20. <form action="/user" method="post">
  21. <input name="_method" value="put" type="hidden">
  22. <input value="提交 put" type="submit">
  23. </form>
  24. </div>
  25. </body>
  26. </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、普通参数与基本注解

      image.png
      @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、矩阵变量

image.png

  • 矩阵变量请求默认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循环调用一遍,都执行以下

![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1627550638858-7f96ccbb-c874-49ce-8512-efce1443b66c.png#align=left&display=inline&height=299&margin=%5Bobject%20Object%5D&name=image.png&originHeight=598&originWidth=1932&size=174479&status=done&style=none&width=966)
<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

image.png

  • 执行目标方法

    // 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个参数处理器
    image.png
    处理器是个接口
    image.png

  • 判断哪个解析器支持当前参数解析在类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;
    }

image.png
转换器设计 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);
              }
          });
      }