【第二十一讲】参数解析器
- 常见参数解析器
- 组合模式在 Spring 中的体现
- ${} #{} 小技巧
1-常见参数解析器
构建参数类型
static class Controller {
public void test(
@RequestParam("name1") String name1, // name1=张三
String name2, // name2=李四
@RequestParam("age") int age, // age=18
@RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据
@RequestParam("file") multipartfile file, // 上传文件
@PathVariable("id") int id, // /test/124 /test/{id}
@RequestHeader("Content-Type") String header,
@CookieValue("token") String token,
@Value("${JAVA_HOME}") String home2, // spring 获取数据 ${} #{}
HttpServletRequest request, // request, response, session ...
@modelattribute("abc") User user1, // name=zhang&age=18
User user2, // name=zhang&age=18
@RequestBody User user3 // json
) {
}
}
static class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
模拟输入
private static HttpServletRequest mockRequest() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("name1", "zhangsan");
request.setParameter("name2", "lisi");
request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));
Map<String, String> map = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");
System.out.println(map);
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);
request.setContentType("application/json");
request.setCookies(new Cookie("token", "123456"));
request.setParameter("name", "张三");
request.setParameter("age", "18");
String json =
"{\n" +
"\"name\": \"john\",\n" +
"\"age\": 42\n" +
"}" ;
request.setContent( json.getBytes(StandardCharsets.UTF_8));
return new StandardServletMultipartResolver().resolveMultipart(request);
}
测试
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
DefaultListablebeanfactory beanfactory = context.getDefaultListablebeanfactory();
// 准备测试 Request
HttpServletRequest request = mockRequest();
// 要点1. 控制器方法被封装为 HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getmethod("test", String.class, String.class, int.class, String.class, multipartfile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));
// 要点2. 准备对象绑定与类型转换
DefaultDataBinderFactory factory = new DefaultDataBinderFactory(null);
// 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
ModelAndViewContainer container = new ModelAndViewContainer();
// 要点4. 解析每个参数值
for (MethodParameter parameter : handlerMethod.getmethodParameters()) {
RequestParamMethodArgumentResolver resolver = new RequestParamMethodArgumentResolver(beanfactory, false);
String annotations = Arrays.stream(parameter.getParameterannotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
String str = annotations.length() > 0 ? " @" + annotations + " " : " ";
parameter.initParameterNamediscovery(new DefaultParameterNamediscoverer());
if(resolver.supportsParameter(parameter)){
// 支持此参数
Object v = resolver.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
System.out.println(v.getClass());
System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + "->" + v);
}else {
System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
}
}
}
-
// 要点2. 准备对象绑定与类型转换 DefaultDataBinderFactory factory = new DefaultDataBinderFactory(null);
2-组合模式在 Spring 中的体现
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
DefaultListablebeanfactory beanfactory = context.getDefaultListablebeanfactory();
// 准备测试 Request
HttpServletRequest request = mockRequest();
// 要点1. 控制器方法被封装为 HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getmethod("test", String.class, String.class, int.class, String.class, multipartfile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));
// 要点2. 准备对象绑定与类型转换
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null,null);
// 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
ModelAndViewContainer container = new ModelAndViewContainer();
// 要点4. 解析每个参数值
for (MethodParameter parameter : handlerMethod.getmethodParameters()) {
// 多个解析器组合
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(
// false 表示必须有 @RequestParam
new RequestParamMethodArgumentResolver(beanfactory, false),
new PathVariableMethodArgumentResolver(),
// 请求头解析器
new RequestHeaderMethodArgumentResolver(beanfactory),
// 解析Cookie 的值
new ServletCookieValueMethodArgumentResolver(beanfactory),
// ${} #{}
new ExpressionValueMethodArgumentResolver(beanfactory),
new ServletRequestMethodArgumentResolver(),
new ServletmodelattributeMethodProcessor(false), // 必须有 @modelattribute
new RequestResponseBodyMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter())),
new ServletmodelattributeMethodProcessor(true), // 省略了 @modelattribute
new RequestParamMethodArgumentResolver(beanfactory, true) // 省略 @RequestParam
);
String annotations = Arrays.stream(parameter.getParameterannotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
String str = annotations.length() > 0 ? " @" + annotations + " " : " ";
parameter.initParameterNamediscovery(new DefaultParameterNamediscoverer());
if(composite.supportsParameter(parameter)){
// 支持此参数
Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
//System.out.println(v.getClass());
System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + "->" + v);
log.info("模型数据为:" + container.getModel());
}else {
System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
}
}
}
总结
RequestMappingHandlerAdapter 调用过程为
- 控制器方法被封装为 HandlerMethod
- 准备对象绑定与类型转换
- 准备 ModelAndViewContainer 用来存储中间 Model 结果
- 解析每个参数值
解析参数依赖的就是各种参数解析器,它们都有两个重要方法
常见参数的解析
* @RequestParam
* 省略 @RequestParam
* @RequestParam(defaultValue)
* multipartfile
* @PathVariable
* @RequestHeader
* @CookieValue
* @Value
* HttpServletRequest 等
* @modelattribute
* 省略 @modelattribute
* @RequestBody
@RequestParam, @CookieValue 等注解中的参数名、默认值, 都可以写成活的, 即从 ${ } #{ }中获取