• HttpServlet中,可以通过request.getParameter()获取请求传入的参数、通过request.getHeader()获取请求头内容、通过request.getRequestURL()获取请求的URL。
  • 而在Spring MVC中,可以直接通过注解的方式获取请求相关的各类信息。
  • Spring MVC 框架会将 HTTP 请求的信息绑定到相应的方法入参中,并根据方法的返回值类型做出相应的后续处理。

@RequestParam注解

  • 在处理方法入参处使用 @RequestParam 可以把请求参数传递给请求方法
  • value:参数名
  • required:是否必须。默认为 true, 表示请求参数中必须包含对应的参数,若不存在,将抛出异常
  • defaultValue: 默认值,当没有传递参数时使用该值

代码

@RequestMapping("/requestParamTest")
String requestParamTest(@RequestParam(value = "id", defaultValue = "0") Integer id,
                        @RequestParam(value = "name", required = true) String name) {
  System.out.println("id : " + id);
  System.out.println("name : " + name);
  System.out.println("Person : " + new Person(name, id));
  return "hello";
}
<form action="requestParamTest" method="get">
    <label>id
        <input type="text" name="id"/>
    </label><br/>
    <label>name
        <input type="text" name="name"/>
    </label><br/>
    <input type="submit" value="submit">
</form><br/>

通过@RequestParamvalue值和input标签的name值对应。

@RequestMapping("/requestParamTest3")
  String requestParamTest3(Integer id, String name) {
    System.out.println("id : " + id);
    System.out.println("name : " + name);
    System.out.println("Person : " + new Person(name, id));
    return "hello";
  }
<form action="requestParamTest3" method="get">
        <label>id
            <input type="text" name="id"/>
        </label><br/>
        <label>name
            <input type="text" name="name"/>
        </label><br/>
        <input type="submit" value="submit">
    </form><br/>

当@RequestParamvalue值和input标签的name值相同时,注解可以省略

@RequestHeader 注解

  • 通过 @RequestHeader 即可将请求头中的属性值绑定到处理方法的入参中

代码

@RequestMapping("/requestHeaderTest")
String requestHeaderTest(@RequestHeader("Accept-Language") String acceptLanguage,
                         @RequestHeader("User-Agent")String userAgent) {
  System.out.println("Accept-Language : " + acceptLanguage);
  System.out.println("User-Agent : " + userAgent);
  return "hello";
}

@CookieValue 注解

  • @CookieValue 可让处理方法入参绑定某个 Cookie 值

代码

@RequestMapping("/cookieValueTest")
String cookieValueTest(@CookieValue(value="JSESSIONID", required=false) String JSESSIONID) {
  System.out.println("JSESSIONID : " + JSESSIONID);
  return "hello";
}

@PathVariable 注解

  • 常用于Rest风格的参数传入。在URL上添加参数,然后在处理的时候,直接将参数提取,作为参数输入到方法上。

代码

@GetMapping("/testPathVariable/{variable}")
public String testPathVariable(@PathVariable Integer variable) {
  log.info(variable.toString());
  return variable.toString();
}

http://localhost:8080/testPathVariable/10输入时,variable 为 10

@RequestBody 注解

  • 当注解方法入参的时候,如果是对应的Java Bean,就会对Java Bean的属性进行注入;如果参数是String类型,就会将整个请求体交给该参数。
  • @RequestBody 标注,可以处理json格式的请求体

代码

@RequestMapping("/getJsonTest")
  public String getJsonTest(@RequestBody Employee employee) {
    System.out.println(employee);
    return "success";
  }

  @RequestMapping("/requestBodyTest")
  public String requestBodyTest(@RequestBody String requestBody) {
    System.out.println(requestBody); // {"id":0,"name":"john","gender":"male"}
    return "success";
  }

@MatrixVariable 注解

  • 可以实现通过另一种方式,传递参数。在路径变量上,通过; 分割,添加多个参数。如http://localhost:8080/testMatrixVariable/a;mv1=b;mv2=c
@GetMapping("/testMatrixVariable/{pathVariable}")
  public Map<String, Object> testMatrixVariable(@PathVariable String pathVariable,
                                                @MatrixVariable("mv1") String mv1,
                                                @MatrixVariable("mv2") String mv2) {
    log.info(pathVariable);  // a
    log.info(mv1);  // b
    log.info(mv2);  // c
    Map<String, Object> map = new HashMap<>();
    map.put("pathVariable", pathVariable);
    map.put("mv1", mv1);
    map.put("mv2", mv2);
    return map;
  }
  • 但是,该方法默认不能实现。因为底层有一个UrlPathHelper 用于处理请求过来的Url,默认将private boolean removeSemicolonContent = true;,将分号后面的内容去除。因此,还需要自己配置,将该值设为false
@Configuration(proxyBeanMethods = false)
public class Config {
  @Bean
  public WebMvcConfigurer webMvcConfigurer() {
    return new WebMvcConfigurer() {
      @Override
      public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
      }
    };
  }
}

使用POJO作为参数

  • Spring MVC 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值。支持级联属性

代码

// java bean
public class Father extends Person {
  private Person son;
  /*忽略get、set、toString方法*/
// java bean
public class Person {
	private Integer id;
	private String name;
	/*忽略get、set、toString方法*/
}
@RequestMapping("requestParamTest2")
String requestParamTest2(Person person) {
	System.out.println("Person : " + person);
	return "hello";
}
<form action="requestParamTest2" method="get">
        <label>id
            <input type="text" name="id"/>
        </label><br/>
        <label>name
            <input type="text" name="name"/>
        </label><br/>
        <input type="submit" value="submit">
    </form><br/>

Spring MVC会根据 input 标签的 name 匹配 pojo 的属性。

@RequestMapping("/requestParamTest4")
  String requestParamTest4(Father father) {
    System.out.println("father : " + father);
    return "hello";
  }
<form action="requestParamTest4" method="post">
    <label>id
        <input type="text" name="id"/>
    </label><br/>
    <label>name
        <input type="text" name="name"/>
    </label><br/>
    <label>son.id
        <input type="text" name="son.id"/>
    </label><br/>
    <label>son.name
        <input type="text" name="son.name"/>
    </label><br/>
    <input type="submit" value="submit">
</form><br/>

级联的pojo,通过属性名.属性名的方式定位。

使用Servlet原生API作为参数

  • MVC 的 Handler 方法(Controller中的方法)可以接受的ServletAPI 类型的参数
  • HttpServletRequest:就是Servlet中的那个request
  • HttpServletResponse:就是Servlet中的那个response
  • HttpSession:就是通过request.getSession()获取的那个session
  • java.security.Principal
  • Locale
  • InputStream:就是通过request.getInputStream()获取的那个InputStream
  • OutputStream:就是通过response.getOutputStream()获取的那个OutputStream
  • Reader:就是通过request.getReader()获取的那个Reader
  • Writer:就是通过response.getWriter()获取的那个Writer
@RequestMapping("/rawApiTest")
 String rawApiTest(Locale locale) {
   System.out.println(locale);
   return "hello";
 }

源代码解析

参数的传入过程

  • 重点是一个叫做HandlerMethodArgumentResolver 的参数解析器。通过该解析器,将传入的参数与方法参数进行一一绑定。
  • 执行步骤如下:
  • DispatcherServlet中的doDispatch()方法,真正执行处理请求。而到了真正方法执行的一步,会先查找参数,再通过反射执行方法
  • doDispatch(),执行方法
  • 适配器中执行方法
  • 依旧在适配器内,将所有的参数解析器放入要被一个叫invocableMethod 的对象中(该对象存储了要执行的方法和参数之类的信息)。此时有27个不同的参数解析器。
  • 再次进入一个要执行方法的对象
  • 该方法里面,又有一个执行方法的方法
  • 然后就先解析参数,再执行方法。这个应该是最重要的过程了。
  • 首先,在getArgumentResolver 方法中找到指定的解析器,然后调用解析器的resolveArgument 方法,解析参数。
  • 而找到解析器的步骤也很简单,就是遍历所有解析器,看哪个支持,就直接返回该解析器。
@Nullable
  public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
    if (resolver == null) {
      throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    } else {
      return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }
  }
  
  @Nullable
  private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
    if (result == null) {
      Iterator var3 = this.argumentResolvers.iterator();

      while(var3.hasNext()) {
        HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
        if (resolver.supportsParameter(parameter)) {
          result = resolver;
          this.argumentResolverCache.put(parameter, resolver);
          break;
        }
      }
    }

    return result;
  }
  • 解析参数,如今就进入 了一个抽象的参数解析器内部了
  • 进入到具体的参数解析器内部,并在request中找到了想要的参数。
  • 至此,参数解析就完成了。