Spring MVC 中的引數繫結
引數繫結入口
@RequestMapping(value = "/saveUser", method = {RequestMethod.POST })
public ResponseEntity<ResultData> postData(@RequestBody body1, User user, String username, String passwd){
return new ResponseEntity<>(new ResultData(ResultData.ResultState.SUCCESS, true), HttpStatus.OK);
}
在上面的方法中,spring MVC框架通過獲取到的http請求分別為不同的引數型別進行賦值,即引數繫結。首先確定引數繫結的入口,DispatcherServlet是處理請求的入口,在該類中獲取HandlerMapping例項,其中AbstractHandlerMethodMapping載入了所有的Controller的方法,通過反射獲取方法上的註解和HandlerMethod,建立url與HandlerMethod直接的關聯。這樣當DispatcherServlet處理請求時,就會通過url交給對應的HandlerMethod處理。
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder("Invoking [");
sb.append(getBeanType().getSimpleName()).append(".");
sb.append(getMethod().getName()).append("] method with arguments ");
sb.append(Arrays.asList(args));
logger.trace (sb.toString());
}
Object returnValue = doInvoke(args);
if (logger.isTraceEnabled()) {
logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
}
return returnValue;
}
以上是InvocableHandlerMethod對請求的處理,HandlerMethod例項中包括Controller類中方法的必要資訊,如Method, MethodParameter[],對應的bean等,InvocableHandlerMethod持有下面這三個例項,spring就是通過這三個例項進行的引數繫結。
private WebDataBinderFactory dataBinderFactory;
private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite();
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
HandlerMethodArgumentResolver引數解析器
按spring一貫方式,spring通過策略模式給一種功能針對不同情況提供了不同的實現。spring引數解析器對不同的引數型別給出了不同的實現,如@RequestParam、@PathVariable、@RequestBody註解的引數、Bean型別的引數、普通型別的引數等。
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
HandlerMethodArgumentResolver是引數解析器的頂層介面,supportsParameter用於判斷該解析器能夠解析的引數型別,resolveArgument用於具體的解析並返回解析結果。
HandlerMethodArgumentResolverComposite儲存了spring預設的引數解析器實現,一種改進的組合模式,其實現的supportsParameter方法實際是遍歷持有的解析器是否支援相應的引數解析,resolveArgument是呼叫持有的解析器的resolveArgument方法。
1. 簡單引數解析器RequestParamMethodArgumentResolver
RequestParamMethodArgumentResolver能夠解析的引數型別是有RequestParam註解的引數或者簡單型別的引數,對應get 方式中queryString的值和post方式中 body data的值。在過載的resolveArgument方法中,4-7行確定引數的名稱,並根據引數的名稱從NativeWebRequest獲取引數對應的值,然後將引數值轉換為MethodParameter對應的型別。這個轉換操作是由DataBinder完成的,DataBinder由WebDataBinderFactory負責建立,WebDataBinderFactory是在InvocableHandlerMethod建立的。
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
Object arg = resolveName(namedValueInfo.name, parameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveDefaultValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !parameter.getParameterType().getName().equals("java.util.Optional")) {
handleMissingValue(namedValueInfo.name, parameter);
}
arg = handleNullValue(namedValueInfo.name, arg, paramType);
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveDefaultValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, paramType, parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
2. @RequestBody和@ResponseBody註解的引數解析器RequestResponseBodyMethodProcessor
這個類還實現了HandlerMethodReturnValueHandler兩個介面,用於對處理方法返回值進行處理的策略介面。resolveArgument的引數解析主要是在readWithMessageConverters方法中實現,在這個方法中利用HttpMessageConverter機制將java物件寫入到HttpOutputMessage或者從HttpInputMessage讀入流到java物件。最後,進行引數驗證並返回。
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
return arg;
}
HttpMessageConverter機制
HttpMessageConverter是訊息轉換器的頂層介面,從介面方法名可以看到主要是成對出現的判斷是否可寫可讀(通過接收的媒體型別判斷)以及讀寫的方法。在servlet標準中,可以用javax.servlet.ServletRequest獲取ServletInputStream和ServletOutputStream,spring分別將其轉換為HttpInputMessage和HttpOutputMessage介面,可以通過getBody方法獲得對應的輸入流和輸出流。
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, MediaType mediaType);
boolean canWrite(Class<?> clazz, MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
3. 自定義bean型別的引數解析器ModelAttributeMethodProcessor
ModelAttributeMethodProcessor能夠解析ModelAttribute註解的引數,或者非簡單型別的引數。通過databinder對引數進行賦值。applyPropertyValues是databinder中的方法,獲取屬性訪問器通過java內省的方式對引數物件中的屬性進行賦值。
哈哈
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// Bind request parameters onto target object.
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
}
catch (PropertyBatchUpdateException ex) {
// Use bind error processor to create FieldErrors.
for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
}
}
}
屬性訪問器(PropertyAccessor)
PropertyAccessor是一個頂層介面,子類實現的setPropertyValue對屬性進行賦值,TypeConverter是Spring型別轉換體系中最頂層的介面,ConfigurablePropertyAccessor繼承了這兩個介面,還提供了設定ConversionService的方法,BeanWrapperImpl是這個介面的具體實現類,支援巢狀屬性、索引屬性(陣列|集合|Map)。
BeanWrapperImpl是在applyPropertyValues的getPropertyAccessor中建立的,BeanWrapperImpl構造方法中,設定屬性,建立TypeConverterDelegate,並進行目標物件的內省分析,將分析結果儲存到cachedIntrospectionResults。cachedIntrospectionResults快取了由spring封裝的ExtendedBeanInfo和GenericTypeAwarePropertyDescriptor。
setPropertyValues迭代所有封裝的PropertyValue,呼叫setPropertyValue(PropertyValue pv),這是一個遞迴方法,getPropertyAccessorForPropertyPath通過屬性名propertyName獲取當前屬性的子屬性,若為空則返回當前屬性,接著呼叫getPropertyNameTokens獲取封裝的PropertyTokenHolder,propertyName實際是一個屬性表示式,PropertyTokenHolder儲存了表示式相關的屬性:
(1)actualName儲存當前級別屬性的實際名稱,為[前的字串,
(2)canonicalName為actualName再加上[key1][key2][key3]..這種形式儲存當前級別屬性的實際名稱,為下一個.前的字串
(3)keys代表當前級別屬性中所有位於[與]間的key或索引所組成的陣列
最後都呼叫setPropertyValue(AbstractNestablePropertyAccessor.PropertyTokenHolder tokens, PropertyValue pv)方法賦值,此方法中支援array、map、list屬性,如果屬性值為空且autoGrowNestedPaths為真則建立對應的例項,如果PropertyTokenHolder的keys為空呼叫型別的預設建構函式。
public void setPropertyValue(PropertyValue pv) throws BeansException {
AbstractNestablePropertyAccessor.PropertyTokenHolder tokens = (AbstractNestablePropertyAccessor.PropertyTokenHolder)pv.resolvedTokens;
if(tokens == null) {
String propertyName = pv.getName();
AbstractNestablePropertyAccessor nestedPa;
try {
nestedPa = this.getPropertyAccessorForPropertyPath(propertyName);
} catch (NotReadablePropertyException var6) {
throw new NotWritablePropertyException(this.getRootClass(), this.nestedPath + propertyName, "Nested property in path '" + propertyName + "' does not exist", var6);
}
tokens = this.getPropertyNameTokens(this.getFinalPath(nestedPa, propertyName));
if(nestedPa == this) {
pv.getOriginalPropertyValue().resolvedTokens = tokens;
}
nestedPa.setPropertyValue(tokens, pv);
} else {
this.setPropertyValue(tokens, pv);
}
}
型別轉換 Converter
spring在引數賦值前,需要將傳入的字串轉換為目標物件的實際型別,databinder通過ConversionService例項進行型別轉換(spring3之前使用PropertyEditor來轉換)。具體的轉換類可實現Converter 介面,它支援從一個 Object 轉為另一個 Object 。ConversionService是一個頂層介面,ConverterRegistry介面用於管理具體的Converter轉換類,GenericConversionService是這兩個介面的實現。
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
<T> T convert(Object source, Class<T> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
ConverterFactory 介面是一種獲取Converter的方式,限制Converter轉換的目標類都繼承相同的父類,如StringToEnumConverterFactory,從 String 到 Enum 的轉換。GenericConverter 介面支援在多個不同的原型別和目標型別之間進行轉換。
參考