• SpringMVC原理学习(二)参数解析器


     如下是所有的参数解析器,下面会通过代码演示常见的参数解析器

    1、初步了解 RequestMappingHandlerAdapter 的调用过程 

    先了解一下 HandlerMethod,后面代码会用到。

    HandlerMethod封装了很多属性,在访问请求方法的时候可以方便的访问到方法、方法参数、方法上的注解、所属类等并且对方法参数封装处理,也可以方便的访问到方法参数的注解等信息。

    1. public HandlerMethod(Object bean, Method method) {
    2. this((Object)bean, (Method)method, (MessageSource)null);
    3. }
    4. protected HandlerMethod(Object bean, Method method, @Nullable MessageSource messageSource) {
    5. Assert.notNull(bean, "Bean is required");
    6. Assert.notNull(method, "Method is required");
    7. this.bean = bean;
    8. this.beanFactory = null;
    9. this.messageSource = messageSource;
    10. this.beanType = ClassUtils.getUserClass(bean);
    11. this.method = method;
    12. this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
    13. ReflectionUtils.makeAccessible(this.bridgedMethod);
    14. this.parameters = this.initMethodParameters();
    15. this.evaluateResponseStatus();
    16. this.description = initDescription(this.beanType, this.method);
    17. }
    18. public MethodParameter[] getMethodParameters() {
    19. return this.parameters;
    20. }

    其中的 initMethodParameters() 方法会初始化方法的参数信息:

    User类:

    1. public class User {
    2. private String name;
    3. private int age;
    4. // 省略了基本方法
    5. }

    Controller类

    1. public class Controller {
    2. public void test(
    3. @RequestParam("name1") String name1, // name1=张三
    4. String name2, // name2=李四
    5. @RequestParam("age") int age, // age=18 格式转换
    6. @RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据
    7. @RequestParam("file") MultipartFile file, // 上传文件
    8. @PathVariable("id") int id, // /test/124 /test/{id}
    9. @RequestHeader("Content-Type") String header,
    10. @CookieValue("token") String token,
    11. @Value("${JAVA_HOME}") String home2, // spring 获取数据 ${} #{}
    12. HttpServletRequest request, // request, response, session ...
    13. @ModelAttribute("abc") User user1, // name=zhang&age=18
    14. User user2, // name=zhang&age=18
    15. @RequestBody User user3 // json
    16. ) {
    17. }
    18. }

    配置类

    1. @Configuration
    2. public class WebConfig {
    3. }

     主启动类

    1. public class A21 {
    2. public static void main(String[] args) throws Exception {
    3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
    4. DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
    5. // 准备测试 Request
    6. HttpServletRequest request = mockRequest();
    7. // 要点1. 控制器方法被封装为 HandlerMethod
    8. HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test",
    9. String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class,
    10. String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));
    11. //多个解析器的组合 组合器的设计模式
    12. HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
    13. composite.addResolvers(
    14. //beanFactory:需要在Spring容器里获取值,如 ${JAVA_HOME}
    15. // false:表示必须有 @RequestParam true:表示不带 @RequestParam 的参数都会交给该解析器解析
    16. new RequestParamMethodArgumentResolver(beanFactory, false)
    17. );
    18. // 要点4. 解析每个参数值
    19. for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
    20. //获取参数上的注解,并连接起来
    21. String annotions = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
    22. String str = annotions.length() > 0 ? "@" + annotions : " ";
    23. //是否支持此参数
    24. if (composite.supportsParameter(parameter)) {
    25. //解析参数,获取参数的值
    26. //参数一:方法参数 参数二:ModelAndViewContainer,处理保存Model和View
    27. //参数三:请求 参数四:数据绑定工厂
    28. Object v = composite.resolveArgument(parameter, null, new ServletWebRequest(request), null);
    29. System.out.println("[" + parameter.getParameterIndex() + "] " + str + " " + parameter.getParameterType().getSimpleName()
    30. + " " + parameter.getParameterName() + " -> " + v + " [" + v.getClass() + "]");
    31. } else {
    32. System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName()
    33. + " " + parameter.getParameterName());
    34. }
    35. }
    36. }
    37. // 模拟请求
    38. private static HttpServletRequest mockRequest() {
    39. MockHttpServletRequest request = new MockHttpServletRequest();
    40. request.setParameter("name1", "zhangsan");
    41. request.setParameter("name2", "lisi");
    42. // 参数一:参数名 参数二:文件名 参数三:内容
    43. request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));
    44. // 找出/test/{id} 与 /test/123 的映射关系
    45. // map:id=123
    46. Map map = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");
    47. //放到 request 作用域
    48. request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);
    49. request.setContentType("application/json");
    50. request.setCookies(new Cookie("token", "123456"));
    51. request.setParameter("name", "张三");
    52. request.setParameter("age", "18");
    53. Gson gson = new Gson();
    54. String json = gson.toJson(new User("王五", 4));
    55. request.setContent(json.getBytes(StandardCharsets.UTF_8));
    56. return new StandardServletMultipartResolver().resolveMultipart(request);
    57. }
    58. }

    结果:所有带箭头的都是被解析的,发现有两个问题:

    • 参数类型都为 String,方法参数的类型没有被解析
    • 方法参数的名字为 null

    添加数据绑定工厂:ServletRequestDataBinderFactory

    •  方法参数的类型被成功解析

    设置参数名的解析器

    • parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
    • 得到方法参数的名字
    1. public class A21 {
    2. public static void main(String[] args) throws Exception {
    3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    4. DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory(WebConfig.class);
    5. // 准备测试 Request
    6. HttpServletRequest request = mockRequest();
    7. // 要点1. 控制器方法被封装为 HandlerMethod
    8. HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test",
    9. String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class,
    10. String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));
    11. // 要点2. 准备对象绑定与类型转换
    12. ServletRequestDataBinderFactory bindFactory = new ServletRequestDataBinderFactory(null, null);
    13. //多个解析器的组合 组合器的设计模式
    14. HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
    15. composite.addResolvers(
    16. //beanFactory:需要在Spring容器里获取值,如 ${JAVA_HOME}
    17. // false:表示必须有 @RequestParam true:表示不带 @RequestParam 的参数都会交给该解析器解析
    18. new RequestParamMethodArgumentResolver(beanFactory, false)
    19. );
    20. // 要点4. 解析每个参数值
    21. for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
    22. //获取参数上的注解,并连接起来
    23. String annotions = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
    24. String str = annotions.length() > 0 ? "@" + annotions : " ";
    25. // 设置参数名的解析器
    26. parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
    27. //是否支持此参数
    28. if (composite.supportsParameter(parameter)) {
    29. //解析参数,获取参数的值
    30. //参数一:方法参数 参数二:ModelAndViewContainer,处理保存Model和View
    31. //参数三:请求 参数四:数据绑定工厂
    32. Object v = composite.resolveArgument(parameter, null, new ServletWebRequest(request), bindFactory);
    33. System.out.println("[" + parameter.getParameterIndex() + "] " + str + " " + parameter.getParameterType().getSimpleName()
    34. + " " + parameter.getParameterName() + " -> " + v + " [" + v.getClass() + "]");
    35. } else {
    36. System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName()
    37. + " " + parameter.getParameterName());
    38. }
    39. }
    40. }
    41. }

    添加 @RequestMapping 解析器时参数设置为 true,表示不带 @RequestParam 的参数都会交给该解析器解析

    1. composite.addResolvers(
    2. //beanFactory:需要在Spring容器里获取值,如 ${JAVA_HOME}
    3. // false:表示必须有 @RequestParam true:表示不带 @RequestParam 的参数都会交给该解析器解析
    4. new RequestParamMethodArgumentResolver(beanFactory, true)
    5. );

     结果:

     

    原因:解析 @PathVariable 时报错,@PathVariable 并不属于该解析器解析,解决办法后面会说。

     2、常见参数的解析

    准备 ModelAndViewContainer 用来存储中间 Model 结果,解析 @ModelAttribute 会把结果存储在里面。

    Controller类有这样的写法,是因为存储的时候如果不指定名字,会按类型的小写存储,由于两个参数类型一样,第二个结果会覆盖掉第一个结果。

    1. @ModelAttribute("abc") User user1, // name=zhang&age=18
    2. User user2, // name=zhang&age=18
    1. public class A21 {
    2. public static void main(String[] args) throws Exception {
    3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
    4. DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
    5. // 准备测试 Request
    6. HttpServletRequest request = mockRequest();
    7. // 要点1. 控制器方法被封装为 HandlerMethod
    8. HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test",
    9. String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class,
    10. String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));
    11. // 要点2. 准备对象绑定与类型转换
    12. ServletRequestDataBinderFactory bindFactory = new ServletRequestDataBinderFactory(null, null);
    13. // 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
    14. ModelAndViewContainer container = new ModelAndViewContainer();
    15. //多个解析器的组合 组合器的设计模式
    16. HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
    17. composite.addResolvers(
    18. //beanFactory:需要在Spring容器里获取值,如 ${JAVA_HOME}
    19. // false:表示必须有 @RequestParam true:表示不带 @RequestParam 的参数都会交给该解析器解析(报错)
    20. new RequestParamMethodArgumentResolver(beanFactory, false),
    21. new PathVariableMethodArgumentResolver(),
    22. new RequestHeaderMethodArgumentResolver(beanFactory),
    23. new ServletCookieValueMethodArgumentResolver(beanFactory),
    24. new ExpressionValueMethodArgumentResolver(beanFactory),
    25. new ServletRequestMethodArgumentResolver(),
    26. new ServletModelAttributeMethodProcessor(false), //必须有 @ModelAttribute
    27. //位置,放在下面会被 ServletModelAttributeMethodProcessor 解析
    28. new RequestResponseBodyMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter())),
    29. new ServletModelAttributeMethodProcessor(true), //省略了 @ModelAttribute
    30. new RequestParamMethodArgumentResolver(beanFactory, true)//省略了 @RequestParam
    31. );
    32. // 要点4. 解析每个参数值
    33. for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
    34. //获取参数上的注解,并连接起来
    35. String annotions = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
    36. String str = annotions.length() > 0 ? "@" + annotions : " ";
    37. // 设置参数名的解析器
    38. parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
    39. //是否支持此参数
    40. if (composite.supportsParameter(parameter)) {
    41. //解析参数,获取参数的值
    42. //参数一:方法参数 参数二:ModelAndViewContainer,处理保存Model和View
    43. //参数三:请求 参数四:数据绑定工厂
    44. Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), bindFactory);
    45. System.out.println("[" + parameter.getParameterIndex() + "] " + str + " " + parameter.getParameterType().getSimpleName()
    46. + " " + parameter.getParameterName() + " -> " + v + " [" + v.getClass() + "]");
    47. System.out.println("模型数据为:" + container.getModel());
    48. } else {
    49. System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName()
    50. + " " + parameter.getParameterName());
    51. }
    52. }
    53. }
    54. }

    结果:

    这样添加的原因:防止 @RequestBody 被 ServletModelAttributeMethodProcessor解析,并且保证省略 @ModelAttribute 的参数也可以被 ServletModelAttributeMethodProcessor 解析。

    1. new ServletModelAttributeMethodProcessor(false)
    2. new RequestResponseBodyMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter())),
    3. new ServletModelAttributeMethodProcessor(true), //省略了 @ModelAttribute

    如果我这样写 

    1. new ServletModelAttributeMethodProcessor(true)
    2. new RequestResponseBodyMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter())),

    可以看到解析的结果发生变化,ServletModelAttributeMethodProcessor 解析了 @RequestBody 注解,发生了错误的解析。

    3、总结

    1. 初步了解 RequestMappingHandlerAdapter 的调用过程

      1. 控制器方法被封装为 HandlerMethod

      2. 准备对象绑定与类型转换

      3. 准备 ModelAndViewContainer 用来存储中间 Model 结果

      4. 解析每个参数值

    2. 解析参数依赖的就是各种参数解析器,它们都有两个重要方法

      • supportsParameter 判断是否支持方法参数

      • resolveArgument 解析方法参数

    3. 常见参数的解析

      • @RequestParam

      • 省略 @RequestParam

      • @RequestParam(defaultValue)

      • MultipartFile

      • @PathVariable

      • @RequestHeader

      • @CookieValue

      • @Value

      • HttpServletRequest 等

      • @ModelAttribute

      • 省略 @ModelAttribute

      • @RequestBody

    4. 组合模式在 Spring 中的体现

    5. @RequestParam, @CookieValue 等注解中的参数名、默认值, 都可以写成活的, 即从 ${ } #{ }中获取

  • 相关阅读:
    【信号处理】基于优化算法的 SAR 信号处理(Matlab代码实现)
    Python Web开发记录 Day16:Django part10 文件上传(完结篇)
    JAVA刷题之字符串的一些个人思路
    使用vi、vim、sed、echo、cat操作文件
    Comparator::compare设定排序的升序 降序
    Git: tag管理
    计算机毕业设计springboot交互式大学英语学习平台g9223源码+系统+程序+lw文档+部署
    HTML模板 宽屏大气的企业官网网站模板
    哪款蓝牙耳机音质好?2022年学生党蓝牙耳机推荐
    C语言练习---【求素数】(一篇带你掌握素数求解)
  • 原文地址:https://blog.csdn.net/qq_51409098/article/details/127766052