美文网首页@IT·互联网
SpringMVC参数解析器

SpringMVC参数解析器

作者: 我可能是个假开发 | 来源:发表于2023-12-21 17:08 被阅读0次

    一、常见参数解析器的作用

    package com.hcx.springdemo.mvc;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.beans.factory.support.DefaultListableBeanFactory;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.core.DefaultParameterNameDiscoverer;
    import org.springframework.core.MethodParameter;
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
    import org.springframework.mock.web.MockHttpServletRequest;
    import org.springframework.mock.web.MockPart;
    import org.springframework.util.AntPathMatcher;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.context.request.ServletWebRequest;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
    import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
    import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
    import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
    import org.springframework.web.method.support.ModelAndViewContainer;
    import org.springframework.web.multipart.MultipartFile;
    import org.springframework.web.multipart.support.StandardServletMultipartResolver;
    import org.springframework.web.servlet.HandlerMapping;
    import org.springframework.web.servlet.mvc.method.annotation.*;
    
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import java.nio.charset.StandardCharsets;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    /*
        目标: 解析控制器方法的参数值
    
        常见的参数处理器如下:
            org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@abbc908
            org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@44afefd5
            org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@9a7a808
            org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@72209d93
            org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@2687f956
            org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@1ded7b14
            org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@29be7749
            org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@5f84abe8
            org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@4650a407
            org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@30135202
            org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@6a4d7f76
            org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@10ec523c
            org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@53dfacba
            org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@79767781
            org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@78411116
            org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@aced190
            org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@245a060f
            org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@6edaa77a
            org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@1e63d216
            org.springframework.web.method.annotation.ModelMethodProcessor@62ddd21b
            org.springframework.web.method.annotation.MapMethodProcessor@16c3ca31
            org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@2d195ee4
            org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@2d6aca33
            org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@21ab988f
            org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@29314cc9
            org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@4e38d975
            org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@35f8a9d3
     */
    public class ParamResolver {
    
        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),
                        new ServletCookieValueMethodArgumentResolver(beanFactory),
                        new ExpressionValueMethodArgumentResolver(beanFactory),
                        new ServletRequestMethodArgumentResolver(),
                        new ServletModelAttributeMethodProcessor(false), // 必须有 @ModelAttribute
                        new RequestResponseBodyMethodProcessor(List.of(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);
                    System.out.println("模型数据为:" + container.getModel());
                } else {
                    System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
                }
            }
        }
    
        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");
            request.setContent("{\"name\":\"李四\",\"age\":20}".getBytes(StandardCharsets.UTF_8));
            return new StandardServletMultipartResolver().resolveMultipart(request);
        }
    
        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 +
                       '}';
            }
        }
    }
    

    RequestMappingHandlerAdapter 的调用过程

    • 1.控制器方法被封装为 HandlerMethod
    • 2.准备对象绑定与类型转换
    • 3.准备 ModelAndViewContainer 用来存储中间 Model 结果
    • 4.解析每个参数值

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

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

    常见参数的解析:

    • @RequestParam
    • 省略 @RequestParam
    • @RequestParam(defaultValue)
    • MultipartFile
    • @PathVariable
    • @RequestHeader
    • @CookieValue
    • @Value
    • HttpServletRequest 等
    • @ModelAttribute
    • 省略 @ModelAttribute
    • @RequestBody

    二、获取参数名

    使用javac xxx.java编译,编译后,没有参数名;
    使用javac -parameters xxx.java编译,编译后,会保留参数名(Method Parameters),可以通过反射api获取到;
    使用javac -g xxx.java编译,编译后,会保留参数名(Local VariableTable),反射获取不到,通过ASM可以获取到;

    1.通过反射获取参数名

    public class Test1 {
        public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {
            // 1. 反射获取参数名
            Method foo = Bean2.class.getMethod("foo", String.class, int.class);
            for (Parameter parameter : foo.getParameters()) {
                System.out.println(parameter.getName());
            }
    

    2.通过ASM获取参数名

    public class Test2 {
        public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {
            // 1. 反射获取参数名
            Method foo = Bean2.class.getMethod("foo", String.class, int.class);
    
            // 2. 基于 LocalVariableTable 本地变量表
            LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
            String[] parameterNames = discoverer.getParameterNames(foo);
            System.out.println(Arrays.toString(parameterNames));
        }
    }
    

    3.总结

    如果编译时添加了 -parameters 可以生成参数表, 反射时就可以拿到参数名
    如果编译时添加了 -g 可以生成调试信息, 但分为两种情况

    • 普通类, 会包含局部变量表, 用 asm 可以拿到参数名
    • 接口, 不会包含局部变量表, 无法获得参数名 (这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名)

    parameters针对类和接口都生效,本地变量表针对接口不生效。

    Spring把两种方法统一了,两种编译选项都可以获取到参数名:

    package org.springframework.core;
    
    public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
        public DefaultParameterNameDiscoverer() {
            if (KotlinDetector.isKotlinReflectPresent() && !NativeDetector.inNativeImage()) {
                this.addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
            }
    
            this.addDiscoverer(new StandardReflectionParameterNameDiscoverer());
            this.addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
        }
    }
    

    相关文章

      网友评论

        本文标题:SpringMVC参数解析器

        本文链接:https://www.haomeiwen.com/subject/knsogdtx.html