- 实例
- 源码解析
- 自定义converter
- 调试
使用实体类接收请求参数,在SpringMVC获取请求参数这篇博文中介绍过,本篇主要解析原理。
实例看实例先。
新建一个spring项目:demo4。
java目录下新建controller类com.example.boot.controller.Demo4Controller。
package com.example.boot.controller; import com.example.boot.bean.Person; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class Demo4Controller { @PostMapping("/save") @ResponseBody private Person save(Person person){ return person; } }
resources.static下新建静态页面index.html。
首页
启动应用,提交表单,结果如下。
主要看下org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument这个方法。
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { //... Object attribute = null; BindingResult bindingResult = null; if (mavContainer.containsAttribute(name)) { //... } else { try { attribute = this.createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException var10) { //... } } if (bindingResult == null) { WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { this.bindRequestParameters(binder, webRequest); } this.validateIfApplicable(binder, parameter); //... } //... bindingResult = binder.getBindingResult(); } MapbindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; }
- attribute = this.createAttribute(name, parameter, binderFactory, webRequest),得到一个空的Person类实例,如下。
Person(userName=null, age=null, birth=null, pet=null)
- WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name),WebDataBinder,即 Web数据绑定器,将请求参数的值绑定到指定的JavaBean里。
得到的WebDataBinder binder如下。
- this.bindRequestParameters(binder, webRequest),binder利用它的converters将请求参数转换为指定数据类型,再次封装到JavaBean中。binder.target,就是封装得到JavaBean。
转换前,首先通过调用 org.springframework.core.convert.support.GenericConversionService#getConverter找converter,用遍历方式找converter。
public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) { List> sourceCandidates = this.getClassHierarchy(sourceType.getType()); List > targetCandidates = this.getClassHierarchy(targetType.getType()); Iterator var5 = sourceCandidates.iterator(); while(var5.hasNext()) { Class> sourceCandidate = (Class)var5.next(); Iterator var7 = targetCandidates.iterator(); while(var7.hasNext()) { Class> targetCandidate = (Class)var7.next(); ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate); GenericConverter converter = this.getRegisteredConverter(sourceType, targetType, convertiblePair); if (converter != null) { return converter; } } } return null; }
比如,java.lang.String -> java.lang.Number 时,找到的converter是org.springframework.core.convert.support.StringToNumberConverterFactory。
最后,调用StringToNumberConverterFactory#convert方法,将String转换为Integer。
final class StringToNumberConverterFactory implements ConverterFactory自定义converter{ StringToNumberConverterFactory() { } public Converter getConverter(Class targetType) { return new StringToNumberConverterFactory.StringToNumber(targetType); } private static final class StringToNumber implements Converter { private final Class targetType; public StringToNumber(Class targetType) { this.targetType = targetType; } @Nullable public T convert(String source) { return source.isEmpty() ? null : NumberUtils.parseNumber(source, this.targetType); } } }
修改静态页面index.html,修改后的结果如下:
首页
改成后,点击提交,显示如下错误页面,且提示Cannot convert value of type ‘java.lang.String’ to required type ‘com.example.boot.bean.Pet’ for property ‘pet’: no matching editors or conversion strategy found。所以,我们需要自定义转换器,可以将String转换为Pet类的转换器。
在com.example.boot下新建配置类config.MyConfig,如下,
package com.example.boot.config; import com.example.boot.bean.Pet; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.format.FormatterRegistry; import org.springframework.util.StringUtils; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class MyConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new Converter() { @Override public Pet convert(String source) { if(!StringUtils.isEmpty(source)){ String[] split = source.split(","); Pet pet = new Pet(); pet.setName(split[0]); pet.setWeight(Float.parseFloat(split[1])); return pet; } return null; } }); } }
实现了WebMvcConfigurer#addFormatters方法,其中定义了converter,该converter可将String类转换成Pet类。
渐渐地,接触WebMvcConfigurer越来越多。
- 前后端交互,实现WebMvcConfigurer#addCorsMappings方法,实现跨域。
- 基于SpringMVC的web工程的配置实现,使用WebMvcConfigurer实现视图控制器、拦截器、文件上传解析器、静态资源访问等等。
- 注解@MatrixVariable,使用WebMvcConfigurer#configurePathMatch方法,实现矩阵变量。
下图演示部分调试过程。
调试过程经过以下部分源码。
- org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { //... HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); //... mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); //... this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); //... }
- org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return this.handleInternal(request, response, (HandlerMethod)handler); }
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { //... mav = this.invokeHandlerMethod(request, response, handlerMethod); //... return mav; }
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { //... invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]); //... var15 = this.getModelAndView(mavContainer, modelFactory, webRequest); //... return var15; }
- org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } return this.doInvoke(args); }
- org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //... Object[] args = new Object[parameters.length]; for(int i = 0; i < parameters.length; ++i) { //... if (args[i] == null) { if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception var10) { //... } } } return args; }
- org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { //... Object attribute = null; BindingResult bindingResult = null; if (mavContainer.containsAttribute(name)) { //... } else { try { attribute = this.createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException var10) { //... } } if (bindingResult == null) { WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { this.bindRequestParameters(binder, webRequest); } //... } //... bindingResult = binder.getBindingResult(); } MapbindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; }
- org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor#bindRequestParameters
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) { ServletRequest servletRequest = (ServletRequest)request.getNativeRequest(ServletRequest.class); Assert.state(servletRequest != null, "No ServletRequest"); ServletRequestDataBinder servletBinder = (ServletRequestDataBinder)binder; servletBinder.bind(servletRequest); }
- org.springframework.web.bind.ServletRequestDataBinder#bind
public void bind(ServletRequest request) { MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request); MultipartRequest multipartRequest = (MultipartRequest)WebUtils.getNativeRequest(request, MultipartRequest.class); if (multipartRequest != null) { this.bindMultipart(multipartRequest.getMultiFileMap(), mpvs); } else if (StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/form-data")) { HttpServletRequest httpServletRequest = (HttpServletRequest)WebUtils.getNativeRequest(request, HttpServletRequest.class); if (httpServletRequest != null && HttpMethod.POST.matches(httpServletRequest.getMethod())) { StandardServletPartUtils.bindParts(httpServletRequest, mpvs, this.isBindEmptyMultipartFiles()); } } this.addBindValues(mpvs, request); this.doBind(mpvs); }
- org.springframework.web.bind.WebDataBinder#doBind
protected void doBind(MutablePropertyValues mpvs) { this.checkFieldDefaults(mpvs); this.checkFieldMarkers(mpvs); this.adaptEmptyArrayIndices(mpvs); super.doBind(mpvs); }
- org.springframework.validation.DataBinder#doBind
protected void doBind(MutablePropertyValues mpvs) { this.checkAllowedFields(mpvs); this.checkRequiredFields(mpvs); this.applyPropertyValues(mpvs); }
- org.springframework.validation.DataBinder#applyPropertyValues
protected void applyPropertyValues(MutablePropertyValues mpvs) { try { this.getPropertyAccessor().setPropertyValues(mpvs, this.isIgnoreUnknownFields(), this.isIgnoreInvalidFields()); } catch (PropertyBatchUpdateException var7) { //... } }
- org.springframework.beans.AbstractPropertyAccessor#setPropertyValues(org.springframework.beans.PropertyValues, boolean, boolean)
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException { //... try { Iterator var6 = propertyValues.iterator(); while(var6.hasNext()) { PropertyValue pv = (PropertyValue)var6.next(); try { this.setPropertyValue(pv); } catch (NotWritablePropertyException var14) { //... } catch (NullValueInNestedPathException var15) { //... } catch (PropertyAccessException var16) { //... } } } finally { //... } //... }
- org.springframework.beans.AbstractNestablePropertyAccessor#setPropertyValue(org.springframework.beans.PropertyValue)
public void setPropertyValue(PropertyValue pv) throws BeansException { //... nestedPa.setPropertyValue(tokens, pv); //... }
- org.springframework.beans.AbstractNestablePropertyAccessor#setPropertyValue(org.springframework.beans.AbstractNestablePropertyAccessor.PropertyTokenHolder, org.springframework.beans.PropertyValue)
protected void setPropertyValue(AbstractNestablePropertyAccessor.PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { //... this.processLocalProperty(tokens, pv); }
- org.springframework.beans.AbstractNestablePropertyAccessor#processLocalProperty
private void processLocalProperty(AbstractNestablePropertyAccessor.PropertyTokenHolder tokens, PropertyValue pv) { //... valueToApply = this.convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor()); //... }
- org.springframework.beans.AbstractNestablePropertyAccessor#convertForProperty
protected Object convertForProperty(String propertyName, @Nullable Object oldValue, @Nullable Object newValue, TypeDescriptor td) throws TypeMismatchException { return this.convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td); }
- org.springframework.beans.AbstractNestablePropertyAccessor#convertIfNecessary
private Object convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class> requiredType, @Nullable TypeDescriptor td) throws TypeMismatchException { //... try { return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td); } catch (IllegalStateException | ConverterNotFoundException var8) { //... } catch (IllegalArgumentException | ConversionException var9) { //... } }
- org.springframework.beans.TypeConverterDelegate#convertIfNecessary(java.lang.String, java.lang.Object, java.lang.Object, java.lang.Class, org.springframework.core.convert.TypeDescriptor)
publicT convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException { //... if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) { try { return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor); } catch (ConversionFailedException var14) { //... } //... }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)