SpringMVC之自定義參數(shù)解析

前面一篇SpringMVC工作原理之參數(shù)解析分析了參數(shù)解析及轉(zhuǎn)換的過程,先是通過參數(shù)解析器解析參數(shù),然后再是轉(zhuǎn)換器轉(zhuǎn)換參數(shù),最終綁定到對應 RequestMapping 方法參數(shù)上。但是在有些時候 SpringMVC 提供的參數(shù)解析器或參數(shù)轉(zhuǎn)換器滿足不了我們的需求,這個時候就需要我們自己按照 SpringMVC 提供的接口進行自定義。

一 自定義參數(shù)解析器

1 場景分析

在有些開發(fā)場景中,SpringMVC 提供的參數(shù)解析器滿足不了咱們的需求。例如在數(shù)據(jù)量大的提交環(huán)境中,提交數(shù)據(jù)用到了表單和JSON融合的方式,就是表單某個字段的 value 是JSON字符串。
如果整個提交的數(shù)據(jù)體是JSON數(shù)據(jù)還好,導入Jackson架包,用 @RequestBody 修飾參數(shù),最終 SpringMVC 會通過自帶的 RequestResponseBodyMethodProcessor 解析器進行解析,使用 Jackson 提供的 MappingJackson2HttpMessageConverter 轉(zhuǎn)換器將JSON數(shù)據(jù)轉(zhuǎn)換成我們想要的格式。
如果提交的是正常表單數(shù)據(jù)也好,用 @RequestParam 修飾參數(shù),最終 SpringMVC 會通過自帶的 RequestParamMethodArgumentResolver 解析器解析出表單里面的 value,然后找到合適的轉(zhuǎn)換器將數(shù)據(jù)裝換成我們想要的格式。
但是現(xiàn)在是表單里面摻雜了JSON字符串的 value,為了優(yōu)雅的解決這個問題,就需要我們自定義一個參數(shù)解析器。

2 開始代碼編寫

假設(shè)現(xiàn)在是一個發(fā)送消息的請求,表單提交的數(shù)據(jù)前面的 keyvalue 指明了發(fā)送人的信息,后面有一個 key 對一個發(fā)送消息體,是一個JSON字符串,里面指明了消息的具體情況。

注意:開始下面代碼前需要先導入 Jackson 支持 JSON 數(shù)據(jù)轉(zhuǎn)換的架包。

① 自定義名叫 JSONRequestParam 參數(shù)注解
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JSONRequestParam {

    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;
}
② 自定義名叫 JSONArgumentResolver 參數(shù)解析器,需要實現(xiàn) HandlerMethodArgumentResolver 接口
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletRequest;

public class JSONArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(JSONRequestParam.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        //得到 JSONRequestParam 注解信息并將其轉(zhuǎn)換成用來記錄注解信息的 JSONRequestParamNamedValueInfo 對象
        JSONRequestParam jsonRequestParam = parameter.getParameterAnnotation(JSONRequestParam.class);
        JSONRequestParamNamedValueInfo namedValueInfo = new JSONRequestParamNamedValueInfo(jsonRequestParam.name(), jsonRequestParam.required());
        if (namedValueInfo.name.isEmpty()) {
            namedValueInfo.name = parameter.getParameterName();
            if (namedValueInfo.name == null) {
                throw new IllegalArgumentException(
                        "Name for argument type [" + parameter.getNestedParameterType().getName() +
                                "] not available, and parameter name information not found in class file either.");
            }
        }

        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);

        //獲得對應的 value 的 JSON 字符串
        String jsonText = servletRequest.getParameter(namedValueInfo.name);

        //得到參數(shù)的 Class
        Class clazz = parameter.getParameterType();

        //使用 Jackson 將 JSON 字符串轉(zhuǎn)換成我們想要的對象類
        ObjectMapper mapper = new ObjectMapper();
        Object value = mapper.readValue(jsonText, clazz);

        return value;
    }

    private static class JSONRequestParamNamedValueInfo {

        private String name;

        private boolean required;

        public JSONRequestParamNamedValueInfo(String name, boolean required) {
            this.name = name;
            this.required = required;
        }
    }
}
③ 自定義參數(shù)解析器的注入

最終我們需要將自定義的參數(shù)解析器注入到 RequestMappingHandlerAdapter 適配器的 customArgumentResolvers 的集合屬性中。但是該適配器一般是由 <mvc:annotation-driven /> 標簽幫我們注入的,所以在寫了該標簽的情況下就不要自己手動注入,所以正確的注入姿勢如下:

<mvc:annotation-driven>
    <mvc:argument-resolvers>
        <bean class="com.ogemray.springmvc.controller.JSONArgumentResolver"></bean>
    </mvc:argument-resolvers>
</mvc:annotation-driven>

④ 編寫參數(shù)需要轉(zhuǎn)換的 POJO 類

public class Message {

    //1. 文字  2. 圖片 3. 語音 4. 其他
    private int type;

    //文字類容
    private String content;

    //圖片信息
    private float picWidth;
    private float picHeight;
    private float picAddress;

    //語音信息
    private float voiceTime;
    private float voiceAddress;
}

⑤ 編寫Controller里面對應的方法代碼

@ResponseBody
@RequestMapping(value = "hello", method = RequestMethod.POST)
public String testHello(@RequestParam String touid, @JSONRequestParam(value = "msg") Message message) {

    System.out.println("發(fā)送人UID : " + touid);
    System.out.println(message);
    return "success";
}

這里為了更直觀很多東西做了簡化,參數(shù)包括兩部分,前面是正常鍵值對標記接收人的uid,后面用自定義 @JSONRequestParam 注解修飾的消息實體。返回值也只是一個字符串,并將該字符串直接寫入到 Response 的 body 里面,所以請求頭里面的 Accept 字段應該標記接收內(nèi)容格式為文本格式。

⑥ AJAX 發(fā)送請求

<a id="tag" href="${pageContext.request.contextPath}/hello">點擊發(fā)送消息</a>
<script type="text/javascript">
    $("#tag").click(function () {

        var msgBody = {type : 1, content : "Hello Word"};
        var args = {touid : "893081892", msg : JSON.stringify(msgBody)};

        $.ajax({
            url: this.href,
            type: "POST",
            data: args,
            contentType: "application/x-www-form-urlencoded; charset=utf-8",
            headers: { Accept: "text/html; charset=utf-8" },
            success: function (data, textStatus) {
                console.log(textStatus);
                console.log(data);
            },
            error: function (data, textStatus, errorThrown) {
                console.log(textStatus);
                console.log(data);
                console.log(errorThrown);
            }
        });
        return false;
    });
</script>

注意:這里需要指明發(fā)送的數(shù)據(jù)為表單格式(即 Content-Type = application/x-www-form-urlencoded; charset=utf-8)

二 自定義參數(shù)轉(zhuǎn)換器

1 使用場景

在某些時候,表單提交大量數(shù)據(jù)時就顯得很復雜,例如需要提交關(guān)于某個人的信息,需要包含名字、年齡、身高......,這時候請求體里面就需要大量的鍵值對,如果我們將一個人的信息用特殊符號分割寫進一個字符串里面,這樣一個鍵值對就可以將整個人的信息都傳遞出去,服務器按照指定格式解析字符串,這樣一來便節(jié)省了不必要的流量消耗。這種情況下SpringMVC提供的轉(zhuǎn)換器就不夠用了,需要我們自定義轉(zhuǎn)換器。

2 開始代碼編寫

假設(shè)提交的個人信息字符串里面包括人的名字、年齡、身高、和體重,然后用 "&" 字符分割。

① 首先寫一個關(guān)于記錄人信息的 POJO 類
public class Person {
    private String name;
    private Integer age;
    private Float height;
    private Float weight;
}
② 自定義類名為 PersonConverter 轉(zhuǎn)換器,需要實現(xiàn) Converter 接口
import org.springframework.core.convert.converter.Converter;

public class PersonConverter implements Converter<String, Person> {

    @Override
    public Person convert(String source) {

        if (source != null) {
            String[] array = source.split("&");
            if (array.length >= 4) {
                Person person = new Person();
                person.setName(array[0]);
                person.setAge(Integer.parseInt(array[1]));
                person.setHeight(Float.parseFloat(array[2]));
                person.setWeight(Float.parseFloat(array[3]));
                return person;
            } else  {
                System.out.println("參數(shù)不合法 : " + source);
            }
        }
        return null;
    }
}
③ 將自定義的轉(zhuǎn)換器注入到容器

<mvc:annotation-driven /> 標簽會自動幫我們創(chuàng)建并注入 ConfigurableWebBindingInitializer 對象,該對象上面記錄了驗證器(validator 屬性)、轉(zhuǎn)換器相關(guān)(conversionService 屬性)等等,最終將該對象綁定到 RequestMappingHandlerAdapter 適配器上,用作后來的參數(shù)驗證及轉(zhuǎn)換。
在源碼里 <mvc:annotation-driven /> 標簽幫我們創(chuàng)建的 conversionServiceFormattingConversionServiceFactoryBean 工廠類bean,所以我們下面自定義也用它。使用FormattingConversionServiceFactoryBean 可以讓SpringMVC支持 @NumberFormat@DateTimeFormat 等Spring內(nèi)部自定義的轉(zhuǎn)換器。

<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="com.ogemray.converter.PersonConverter"></bean>
        </set>
    </property>
</bean>
<mvc:annotation-driven conversion-service="conversionService" />
④ 編寫Controller里面對應的方法代碼
@ResponseBody
@RequestMapping(value = "hello", method = RequestMethod.POST)
public String testHello(Person person) {
    System.out.println(person);
    return "success";
}

這里也是為了代碼直觀做了簡化,直接返回字符串簡單了解提交狀態(tài)。

⑤ AJAX 發(fā)送請求
<a id="tag" href="${pageContext.request.contextPath}/hello">點擊發(fā)送請求</a>
<script type="text/javascript">
    $("#tag").click(function () {
        $.ajax({
            url: this.href,
            type: "POST",
            data: {person : "Tom&18&175.1&56.8"},
            contentType: "application/x-www-form-urlencoded; charset=utf-8",
            heanders: { Accept: "text/html; charset=utf-8" },
            success: function (data) {
                console.log(data);
            },
            error: function () { }
        });
        return false;
    });
</script>

總結(jié)

優(yōu)秀的框架支持開閉原則,對外擴展開放,內(nèi)部修改關(guān)閉。通過這兩個自定義可以學到很多優(yōu)秀的編程思想。

其他相關(guān)文章

SpringMVC入門筆記
SpringMVC工作原理之處理映射[HandlerMapping]
SpringMVC工作原理之適配器[HandlerAdapter]
SpringMVC工作原理之參數(shù)解析
SpringMVC之自定義參數(shù)解析
SpringMVC工作原理之視圖解析及自定義
SpingMVC之<mvc:annotation-driven/>標簽

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 對于java中的思考的方向,1必須要看前端的頁面,對于前端的頁面基本的邏輯,如果能理解最好,不理解也要知道幾點。 ...
    神尤魯?shù)婪?/span>閱讀 901評論 0 0
  • 前提 在日常使用SpringMVC進行開發(fā)的時候,有可能遇到前端各種類型的請求參數(shù),這里做一次相對全面的總結(jié)。Sp...
    zhrowable閱讀 2,164評論 0 15
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,304評論 4 61
  • 門徒 影評 關(guān)于這部由爾冬升,陳可辛聯(lián)手導演并監(jiān)制的電影,我真的有些不知該如何定位。這部電影說是警匪片...
    行走世間的鎖匠閱讀 1,714評論 3 2
  • 在城市里穿梭的地鐵上,摩肩擦踵的人們神情麻木地低著頭,或看著手機,或看著手中的報紙,或打著未完的瞌睡,沒有人知道一...
    膽小鬼zxhsdbd閱讀 801評論 0 7

友情鏈接更多精彩內(nèi)容