SpringMVC

来源:互联网 发布:中国国家顶级域名 编辑:程序博客网 时间:2024/06/03 12:13

该篇讲述数据绑定流程

请看绑定流程示意图:

这里写图片描述


【1】首先分析一下数据绑定流程

【该篇描述的背景为填写表单对象,传递到后台(参数为Employee employee)】

① SpringMVC框架将ServletRequest对象以及目标方法的入参实例(objectName,attr)传递给WebDataBinderFactory实例,以创建DataBinder实例对象。

    • objectName为@ModelAttribute注解的value值或者根据参数类型自动创建
    • 其中attr会根据目标方法参数类型从mav获取或者创建

② DataBinder调用装配在SpringMVC上下文中的ConversionService组件进行数据类型转换、数据格式化工作。将Servlet中的请求信息填充到创建的入参对象中。

③ 调用Validator 组件对已经绑定了请求消息的入参对象(创建的)进行数据合法性校验,并最终生成数据结果。

④ SpringMVC 抽取 BindingResult 中的入参对象(getTarget)和校验错误对象(BindingResult),将他们赋给处理方法的响应入参

详细分析流程,请点击查看SpringMVC运行流程示例


【解析方法参数】

  • 源码如下:

    • 此时类为ModelAttributeMethodProcessor
@Override    public final Object resolveArgument(            MethodParameter parameter, ModelAndViewContainer mavContainer,            NativeWebRequest request, WebDataBinderFactory binderFactory)            throws Exception {//Derives the model attribute name for a method parameter based on: //1.The parameter @ModelAttribute annotation value //2.The parameter type         String name = ModelFactory.getNameForParameter(parameter);        //获取attribute,如果mav有则直接获取;如果没有则创建        Object attribute = (mavContainer.containsAttribute(name)) ?                mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);        //根据request、name、attribute创建binder!!        WebDataBinder binder = binderFactory.createBinder(request, attribute, name);        if (binder.getTarget() != null) {        //Extension point to bind the request to the target object.---见级联方法1            bindRequestParameters(binder, request);            //Validate the model attribute if applicable. //The default implementation checks for //@javax.validation.Valid.--见级联方法7            validateIfApplicable(binder, parameter);            if (binder.getBindingResult().hasErrors()) {                if (isBindExceptionRequired(binder, parameter)) {                    throw new BindException(binder.getBindingResult());                }            }        }//....      
  • 注释如下:
Resolve the argument from the model or if not found instantiate it with its default if it is available. The model attribute is then populated with request values via data binding and optionally validated if @java.validation.Valid is present on the argument.

注意,这里是差异!!!

前一篇的流程讲解中 resolver为:

Object org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument

方法如下

@Override    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) {                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);            arg = binder.convertIfNecessary(arg, paramType, parameter);        }        handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);        return arg;    }

级联方法1void bindRequestParameters()

  • 将请求绑定到目标对象的扩展点。
@Override    protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {        ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);        ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;        servletBinder.bind(servletRequest);        //见级联方法2    }

级联方法2void bind

public void bind(ServletRequest request) {        MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);        MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);        if (multipartRequest != null) {            bindMultipart(multipartRequest.getMultiFileMap(), mpvs);        }        //见级联方法3        addBindValues(mpvs, request);        //见级联方法4        doBind(mpvs);    }

注释如下:

Bind the parameters of the given request to this binder's target, also binding multipart files in case of a multipart request. --把请求中的参数绑定到binder的target上(对一个multipart request同样有效)This call can create field errors, representing basic binding errors like a required field (code "required"), or type mismatch between value and bean property (code "typeMismatch"). Multipart files are bound via their parameter name, just like normal HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property, invoking a "setUploadedFile" setter method. The type of the target property for a multipart file can be MultipartFile, byte[], or String. The latter two receive the contents of the uploaded file; all metadata like original file name, content type, etc are lost in those cases.

此时请求中的参数为:

这里写图片描述


级联方法3void addBindValues

注释:Merge URI variables into the property values to use for data binding.

protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {        String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;        Map<String, String> uriVars = (Map<String, String>) request.getAttribute(attr);        if (uriVars != null) {            for (Entry<String, String> entry : uriVars.entrySet()) {                if (mpvs.contains(entry.getKey())) {                    logger.warn("Skipping URI variable '" + entry.getKey()                            + "' since the request contains a bind value with the same name.");                }                else {                    mpvs.addPropertyValue(entry.getKey(), entry.getValue());                }            }        }    }

级联方法4void doBind

此时class为 :.WebDataBinder

@Override    protected void doBind(MutablePropertyValues mpvs) {        checkFieldDefaults(mpvs);        checkFieldMarkers(mpvs);        //调用父类的该方法--见级联方法5        super.doBind(mpvs);    }

级联方法5

此时class为:DataBinder

protected void doBind(MutablePropertyValues mpvs) {        checkAllowedFields(mpvs);        checkRequiredFields(mpvs);/*Apply given property values to the target object. Default implementation applies all of the supplied property values as bean property values. By default, unknown fields will be ignored.*/      //见级联方法6        applyPropertyValues(mpvs);    }

级联方法6

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());            }        }    }

下面执行顺序为:

① getPropertyAccessor().setPropertyValues –>

②getInternalBindingResult().getPropertyAccessor(); –>

protected AbstractPropertyBindingResult getInternalBindingResult() {        if (this.bindingResult == null) {            initBeanPropertyAccess();        }        return this.bindingResult;    }

public void initBeanPropertyAccess() {        Assert.state(this.bindingResult == null,                "DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");        this.bindingResult = new BeanPropertyBindingResult(                getTarget(), getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit());        if (this.conversionService != null) {            this.bindingResult.initConversion(this.conversionService);        }    }

这里写图片描述

从④依次返回!


【进行数据格式化】

    @Override    public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {        return new StringToNumber<T>(targetType);    }

【将请求参数写入到目标对象】:

  • 循环遍历赋值;
  • 将会用到数据格式化;
@Override    public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)            throws BeansException {        List<PropertyAccessException> propertyAccessExceptions = null;        List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?                ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));        for (PropertyValue pv : propertyValues) {            try {                // This method may throw any BeansException, which won't be caught                // here, if there is a critical failure such as no matching field.                // We can attempt to deal only with less serious exceptions.                setPropertyValue(pv);            }            catch (NotWritablePropertyException ex) {                if (!ignoreUnknown) {                    throw ex;                }                // Otherwise, just ignore it and continue...            }            catch (NullValueInNestedPathException ex) {                if (!ignoreInvalid) {                    throw ex;                }                // Otherwise, just ignore it and continue...            }            catch (PropertyAccessException ex) {                if (propertyAccessExceptions == null) {                    propertyAccessExceptions = new LinkedList<PropertyAccessException>();                }                propertyAccessExceptions.add(ex);            }        }        // If we encountered individual exceptions, throw the composite exception.        if (propertyAccessExceptions != null) {            PropertyAccessException[] paeArray =                    propertyAccessExceptions.toArray(new PropertyAccessException[propertyAccessExceptions.size()]);            throw new PropertyBatchUpdateException(paeArray);        }    }
  • 具体写入方法
writeMethod.invoke(this.object, value);

这里写图片描述


【级联方法7】void validateIfApplicable

注释如下:

Validate the model attribute if applicable. The default implementation checks for @javax.validation.Valid.
    • 就是验证
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {        Annotation[] annotations = parameter.getParameterAnnotations();        for (Annotation annot : annotations) {            if (annot.annotationType().getSimpleName().startsWith("Valid")) {                Object hints = AnnotationUtils.getValue(annot);                //见级联方法8                binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});                break;            }        }    }

级联方法8

调用binder的validators,进行校验!!!

public void validate(Object... validationHints) {        for (Validator validator : getValidators()) {            if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {                ((SmartValidator) validator).validate(getTarget(), getBindingResult(), validationHints);            }            else if (validator != null) {                validator.validate(getTarget(), getBindingResult());            }        }    }

【回到最开始的方法】:

@Override    public final Object resolveArgument(            MethodParameter parameter, ModelAndViewContainer mavContainer,            NativeWebRequest request, WebDataBinderFactory binderFactory)            throws Exception {        String name = ModelFactory.getNameForParameter(parameter);        Object attribute = (mavContainer.containsAttribute(name)) ?                mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);        WebDataBinder binder = binderFactory.createBinder(request, attribute, name);        if (binder.getTarget() != null) {            bindRequestParameters(binder, request);            validateIfApplicable(binder, parameter);            if (binder.getBindingResult().hasErrors()) {                if (isBindExceptionRequired(binder, parameter)) {                    throw new BindException(binder.getBindingResult());                }            }        }        //Add resolved attribute and BindingResult at the end of the model        //生成最终的数据绑定结果!!        Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();        mavContainer.removeAttributes(bindingResultModel);        mavContainer.addAllAttributes(bindingResultModel);        //返回target!        return binder.getTarget();    }

返回到HandlerMethodArgumentResolverComposite.resolveArgument( )

@Override    public Object resolveArgument(            MethodParameter parameter, ModelAndViewContainer mavContainer,            NativeWebRequest webRequest, WebDataBinderFactory binderFactory)            throws Exception {        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);        Assert.notNull(resolver, "Unknown parameter type [" + parameter.getParameterType().getName() + "]");        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);    }

依次返回。。。

返回到Object invokeForRequest()

这个方法前面说过,就是从请求里面为目标方法参数赋值!!!

public final 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(this.getBeanType().getSimpleName()).append(".");            sb.append(getMethod().getName()).append("] method with arguments ");            sb.append(Arrays.asList(args));            logger.trace(sb.toString());        }        Object returnValue = invoke(args);        if (logger.isTraceEnabled()) {            logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");        }        return returnValue;    }

这里写图片描述


调用目标方法

这里写图片描述


拿到目标方法的返回值

这里写图片描述

回到void invokeAndHandle()

  • 该方法就是为了处理返回值!!!
public final void invokeAndHandle(ServletWebRequest webRequest,            ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);        setResponseStatus(webRequest);        if (returnValue == null) {            if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {                mavContainer.setRequestHandled(true);                return;            }        }        else if (StringUtils.hasText(this.responseReason)) {            mavContainer.setRequestHandled(true);            return;        }        mavContainer.setRequestHandled(false);        try {            this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);        }        catch (Exception ex) {            if (logger.isTraceEnabled()) {                logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);            }            throw ex;        }    }
private ModelAndView invokeHandleMethod(){    //...        //处理完目标方法        requestMappingMethod.invokeAndHandle(webRequest, mavContainer);        if (asyncManager.isConcurrentHandlingStarted()) {            return null;        }        //返回MV        return getModelAndView(mavContainer, modelFactory, webRequest);}

。。。。以下过程与前一篇一致SpringMVC运行流程分析

0 0
原创粉丝点击