前面一篇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ù)前面的 key 和 value 指明了發(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)建的 conversionService 是 FormattingConversionServiceFactoryBean 工廠類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/>標簽