zoukankan      html  css  js  c++  java
  • SpringBoot系列教程web篇之如何自定义参数解析器


    title: 190831-SpringBoot系列教程web篇之如何自定义参数解析器
    banner: /spring-blog/imgs/190831/logo.jpg
    tags:

    • 请求参数
      categories:
    • SpringBoot
    • 高级篇
    • Web
      date: 2019-08-31 16:45:48
      keywords: Spring SpringBoot 参数解析 HandlerMethodArgumentResolver

    SpringMVC提供了各种姿势的http参数解析支持,从前面的GET/POST参数解析篇也可以看到,加一个@RequsetParam注解就可以将方法参数与http参数绑定,看到这时自然就会好奇这是怎么做到的,我们能不能自己定义一种参数解析规则呢?

    本文将介绍如何实现自定义的参数解析,并让其生效

    I. 环境搭建

    首先得搭建一个web应用才有可能继续后续的测试,借助SpringBoot搭建一个web应用属于比较简单的活;

    创建一个maven项目,pom文件如下

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7</version>
        <relativePath/> <!-- lookup parent from update -->
    </parent>
    
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
        <java.version>1.8</java.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    

    II. 自定义参数解析器

    对于如何自定义参数解析器,一个较推荐的方法是,先搞清楚springmvc接收到一个请求之后完整的处理链路,然后再来看在什么地方,什么时机,来插入自定义参数解析器,无论是从理解还是实现都会简单很多。遗憾的是,本篇主要目标放在的是使用角度,所以这里只会简单的提一下参数解析的链路,具体的深入留待后续的源码解析

    1. 参数解析链路

    http请求流程图,来自 SpringBoot是如何解析HTTP参数的

    既然是参数解析,所以肯定是在方法调用之前就会被触发,在Spring中,负责将http参数与目标方法参数进行关联的,主要是借助org.springframework.web.method.support.HandlerMethodArgumentResolver类来实现

    /**
     * Iterate over registered {@link HandlerMethodArgumentResolver}s and invoke the one that supports it.
     * @throws IllegalStateException if no suitable {@link HandlerMethodArgumentResolver} is found.
     */
    @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("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
    	}
    	return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }
    

    上面这段核心代码来自org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#resolveArgument,主要作用就是获取一个合适的HandlerMethodArgumentResolver,实现将http参数(webRequest)映射到目标方法的参数上(parameter)

    所以说,实现自定义参数解析器的核心就是实现一个自己的HandlerMethodArgumentResolver

    2. HandlerMethodArgumentResolver

    实现一个自定义的参数解析器,首先得有个目标,我们在get参数解析篇里面,当时遇到了一个问题,当传参为数组时,定义的方法参数需要为数组,而不能是List,否则无法正常解析;现在我们则希望能实现这样一个参数解析,以支持上面的场景

    为了实现上面这个小目标,我们可以如下操作

    a. 自定义注解ListParam

    定义这个注解,主要就是用于表明,带有这个注解的参数,希望可以使用我们自定义的参数解析器来解析;

    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ListParam {
        /**
         * Alias for {@link #name}.
         */
        @AliasFor("name") String value() default "";
    
        /**
         * The name of the request parameter to bind to.
         *
         * @since 4.2
         */
        @AliasFor("value") String name() default "";
    }
    

    b. 参数解析器ListHandlerMethodArgumentResolver

    接下来就是自定义的参数解析器了,需要实现接口HandlerMethodArgumentResolver

    public class ListHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.hasParameterAnnotation(ListParam.class);
        }
    
        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
            ListParam param = parameter.getParameterAnnotation(ListParam.class);
            if (param == null) {
                throw new IllegalArgumentException(
                        "Unknown parameter type [" + parameter.getParameterType().getName() + "]");
            }
    
            String name = "".equalsIgnoreCase(param.name()) ? param.value() : param.name();
            if ("".equalsIgnoreCase(name)) {
                name = parameter.getParameter().getName();
            }
            String ans = webRequest.getParameter(name);
            if (ans == null) {
                return null;
            }
    
            String[] cells = StringUtils.split(ans, ",");
            return Arrays.asList(cells);
        }
    }
    

    上面有两个方法:

    • supportsParameter就是用来表明这个参数解析器适不适用
      • 实现也比较简单,就是看参数上有没有前面定义的ListParam注解
    • resolveArgument 这个方法就是实现将http参数粗转换为目标方法参数的具体逻辑
      • 上面主要是为了演示自定义参数解析器的过程,实现比较简单,默认只支持List<String>

    3. 注册

    上面虽然实现了自定义的参数解析器,但是我们需要把它注册到HandlerMethodArgumentResolver才能生效,一个简单的方法如下

    @SpringBootApplication
    public class Application extends WebMvcConfigurationSupport {
    
        @Override
        protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
            argumentResolvers.add(new ListHandlerMethodArgumentResolver());
        }
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class);
        }
    }
    

    4. 测试

    为了验证我们的自定义参数解析器ok,我们开两个对比的rest服务

    @RestController
    @RequestMapping(path = "get")
    public class ParamGetRest {
        /**
         * 自定义参数解析器
         *
         * @param names
         * @param age
         * @return
         */
        @GetMapping(path = "self")
        public String selfParam(@ListParam(name = "names") List<String> names, Integer age) {
            return names + " | age=" + age;
        }
    
        @GetMapping(path = "self2")
        public String selfParam2(List<String> names, Integer age) {
            return names + " | age=" + age;
        }
    }
    

    演示demo如下,添加了ListParam注解的可以正常解析,没有添加注解的会抛异常

    II. 其他

    0. 项目&相关博文

    1. 一灰灰Blog

    尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

    下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

    一灰灰blog

  • 相关阅读:
    Docker底层技术了解(3)
    Docker整体结构了解(2)
    Docker简介(1)
    linux常用命令
    python反射
    flask线程隔离技术
    flask核心机制
    并发编程
    socket通信
    网络编程
  • 原文地址:https://www.cnblogs.com/yihuihui/p/11439768.html
Copyright © 2011-2022 走看看