当请求提交的参数足够多时,可以使用JavaBean作为接收参数。而具体的是怎样将请求报文中的参数值和控制器中的Bean参数的属性一一对应赋值的。这就是需要探究的
首先来到DispatcherServlet中寻找处理请求的适配器,然后再使用适配器处理请求和参数。
在JavaBean中
@Data
public class TestBean {
private String username;
private String password;
private Food food;
}
在控制器中
@RequestMapping("/testBean")
public TestBean testBean(TestBean testBean){
return testBean;
}
页面中
springboot会自动转换类型和格式,会联级封装,在上面的bean中没写toString,但是控制器使用了@RestController注解,返回的类型在页面就会变成json。想知道为什么就看源码
首先重启服务器,来到DispatcherServlet中,找到那一行确定请求适配器的,还有执行控制器方法的那一行。首先确定是用哪一个参数解析器解析实体参数的
确定是由 ha = {RequestMappingHandlerAdapter@7233} 这个适配器处理当前请求映射,然后来到真正处理请求的那一行 mv = ha.handle.... 。进入
首先来到AbstractHandlerMethodAdapter-->>handle,然后继续进入来到RequestMappingHandlerAdapter-->>invokeHandlerMethod里面,略过前面的初始化内容,找到这一行
invocableMethod.invokeAndHandle(webRequest, mavContainer);
进入后,找到熟悉的这两行
第一行是处理请求和参数的,执行完了就会执行控制器方法,再执行第二句
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
首先进入第一行,来到InvocableHandlerMethod-->>invokeForRequest里面,从第一行进入来到getMethodArgumentValues方法里面,确定参数的解析器和为控制器参数赋值
找到这两行,第一行确定的是参数的解析器resolver,第二行是获取请求中的值为参数赋值
if (!this.resolvers.supportsParameter(parameter)) {
...
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
首先进入第一行,经过HandlerMethodArgumentResolverComposite-->>supportsParameter方法来到getArgumentResolver方法
在这里确定由哪个参数解析器解析参数
在底层可以发现很多这样的增强for循环,实现解决一些参数赋值解析问题
在循环里面有27个解析器,经过循环最后是由result = {ServletModelAttributeMethodProcessor@7402} 作为参数解析器
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
在拿到参数解析器后返回,来到InvocableHandlerMethod-->>getMethodArgumentValues,对参数进行解析赋值
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
进入后再来到resolveArgument里面,显示获取刚才被放进缓存的参数解析器,然后判断有没有参数解析器,然后进入最后一行来到ModelAttributeMethodProcessor-->>resolveArgumentresolveArgument里面
在这里面,首先对这两mavContainer、binderFactory做非空判断,这两个一个负责参数传值显示数据、一个负责接下来的事
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//做非空判断
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
//获取控制器参数
String name = ModelFactory.getNameForParameter(parameter);
//判断参数用没用ModelAttribute注解,所以它是null
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute注解,所以它是null.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance-----------创建属性实例
try {
//执行过这一行后就会创建一个参数实例
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
else {
attribute = ex.getTarget();
}
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
//在这将表单提交的参数和自定义参数属性值进行绑定赋值
//首先创建一个操作对象
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
//对创建好的绑定操作对进行绑定赋值,进去看看怎么操作的
bindRequestParameters(binder, webRequest);
}
//此时自定义参数每个属性已经有了值,验证是否类型匹配
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
创建的参数实例
上面已经知道哪一行事进行参数属性赋值操作,进入后来到
里面有两参数,一个是创建的哪个空的参数对象,一个是发送进来的携带着数据的请求
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
//执行过这一行后就拿到了请求里面的表单提交的数据,确定了请求的类型s
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
Assert.state(servletRequest != null, "No ServletRequest");
//对传递进来的空的参数对象做类型转换
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
//对空参对象进行属性参数赋值---进入看看是怎么赋值的
servletBinder.bind(servletRequest);
}
再bind里面
//这里的这个参数是上面那个控制器参数创建的没有值的对象强转过来的,就是那个binder
public void bind(ServletRequest request) {
//这一行就是把请求参数传给之前创建的没有值的参数对象,进入step into
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
................
}
来到ServletRequestParameterPropertyValues【servlet请求参数属性值】里面
public static Map getParametersStartingWith(ServletRequest request, @Nullable String prefix) {
//非空判断
Assert.notNull(request, "Request must not be null");
//创建一个数组接收请求中的所有参数
Enumeration paramNames = request.getParameterNames();
//创建一个空集合
Map params = new TreeMap<>();
//什么都没做,也没有传进来一个String类型的参数,所以是null,那么前缀就是空串
if (prefix == null) {
prefix = "";
}
//循环赋值
while (paramNames != null【请求里面有参数的,不为空】 &&
paramNames.hasMoreElements()【返回true】) {
//规定第一个循环的参数
String paramName = paramNames.nextElement();
//判断前缀是否为空或者当前循环的参数名的前缀是不是之前传进来的String参数前缀---符合
if (prefix.isEmpty() || paramName.startsWith(prefix)) {
//设置参数名,前缀现在是0拼接字符串加上参数名
String unprefixed = paramName.substring(prefix.length()【这里是空串】);
//从传过来的参数中取出当前参数的值
String[] values = request.getParameterValues(paramName);
//判断非空
if (values == null || values.length == 0) {
// Do nothing, no values found at all.
}
else if (values.length > 1) {
//将刚才设置好的前缀和参数值
params.put(unprefixed, values);
}
else {
params.put(unprefixed, values[0]);
}
}
}
//返回已经赋好值的集合
return params;
}
在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);
}
//有没有那个MULTIPART_FORM_DATA_VALUE = "multipart/form-data";----不符合
else if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.MULTIPART_FORM_DATA_VALUE)) {
HttpServletRequest httpServletRequest = WebUtils.getNativeRequest(request, HttpServletRequest.class);
if (httpServletRequest != null && HttpMethod.POST.matches(httpServletRequest.getMethod())) {
StandardServletPartUtils.bindParts(httpServletRequest, mpvs, isBindEmptyMultipartFiles());
}
}
addBindValues(mpvs, request);
//将处理结果返回
doBind(mpvs);
}
在赋值完后,最终回到了resolveArgument方法里面,然后得到了赋值结果
最后通过这三行将数据装在模型里作为响应页面的model数据
// Add resolved attribute and BindingResult at the end of the model
//将处理好的属性赋值结果添加在model末尾
Map bindingResultModel = bindingResult.getModel();
//对处理后的参数对象bindingResultModel进行处理----------确保将返回的model中没有处理后的属性
mavContainer.removeAttributes(bindingResultModel);
//再将处理后的参数对象bindingResultModel放进去model
mavContainer.addAllAttributes(bindingResultModel);
处理后的结果
最后返回装好值的参数对象,至此就完成了对控制器自定义参数的赋值。最后一路直到
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
继续往下执行,这一行完成对返回页面的数据的封装
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
在这完成对返回页面数据的具体封装
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
//设置请求处理状态为true
mavContainer.setRequestHandled(true);
//开始处理请求
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
//这一行具体把返回值和返回的报文内容封装
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
可以进入最后一行里面,会来到writeWithMessageConverters方法里面
在里面
通过这个循环确定输出形式
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
....
//通过这一行输出内容,确定输出类型和输出的具体内容
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
最后结束 invokeAndHandle 方法,在invokeHandlerMethod方法中返回已经设置好的响应内容
return getModelAndView(mavContainer, modelFactory, webRequest);
这样,发送请求由自定义类型参数接收数据的过程就结束了,就是说这一句话执行完了
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
剩下的就是接收之前处理好的响应内容然后在渲染页面的时候放进去,作为响应内容显示显示在页面上