文章目录
使用实体类接收请求参数,在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。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<form action="/save" method="post">
姓名:<input type="text" name="userName" value="张三"/><br>
年龄:<input type="text" name="age" value="22"/><br>
生日:<input type="text" name="birth" value="2000/10/20 14:00:00"><br>
宠物名字:<input type="text" name="pet.name" value="小苹果"><br>
宠物重量:<input type="text" name="pet.weight" value="15.0"><br>
<input type="submit" value="提交"/>
</form>
</body>
</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();
}
Map<String, Object> bindingResultModel = 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<Class<?>> sourceCandidates = this.getClassHierarchy(sourceType.getType());
List<Class<?>> 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<String, Number> {
StringToNumberConverterFactory() {
}
public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToNumberConverterFactory.StringToNumber(targetType);
}
private static final class StringToNumber<T extends Number> implements Converter<String, T> {
private final Class<T> targetType;
public StringToNumber(Class<T> targetType) {
this.targetType = targetType;
}
@Nullable
public T convert(String source) {
return source.isEmpty() ? null : NumberUtils.parseNumber(source, this.targetType);
}
}
}
自定义converter
修改静态页面index.html,修改后的结果如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<form action="/save" method="post">
姓名:<input type="text" name="userName" value="张三"/><br>
年龄:<input type="text" name="age" value="22"/><br>
生日:<input type="text" name="birth" value="2000/10/20 14:00:00"><br>
<!-- 宠物名字:<input type="text" name="pet.name" value="小苹果"><br>-->
<!-- 宠物重量:<input type="text" name="pet.weight" value="15.0"><br>-->
宠物:<input type="text" name="pet" value="小苹果,15.0"/><br>
<input type="submit" value="提交"/>
</form>
</body>
</html>
改成<input type="text" name="pet" value="小苹果,15.0"/>
后,点击提交,显示如下错误页面,且提示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<String, Pet>() {
@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();
}
Map<String, Object> bindingResultModel = 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)
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
//...
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
} catch (ConversionFailedException var14) {
//...
}
//...
}