1. 程式人生 > 程式設計 >SpringBoot系列教程web篇之如何自定義引數解析器

SpringBoot系列教程web篇之如何自定義引數解析器

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,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