1. 程式人生 > >Spring MVC 中的引數繫結

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 介面支援在多個不同的原型別和目標型別之間進行轉換。

參考