在SpringBoot中,处理请求的控制器方法可以接受多种类型的参数,如Model和Map<String, Object>。你是否好奇它们是如何被解析和处理的?本篇文章将带你深入探究SpringBoot源码,揭示Model和Map参数背后的工作原理。通过详细分析ModelAndViewContainer、MapMethodProcessor、ModelMethodProcessor等核心类,以及视图渲染过程中数据如何从BindingAwareModelMap转移到请求域,你将全面理解SpringBoot如何确保Model和Map的无缝互换及数据一致性。阅读本文,你将掌握SpringBoot参数解析的内在机制,提升对Spring MVC框架的深入理解。准备好了吗?让我们一同揭开这神秘面纱
SpringBoot源码解析之Model和Map参数解析
在Spring MVC中,处理请求的方法可以接受多种类型的参数,比如HttpServletRequest
、Model
、Map<String, Object>
等。这篇文章将详细解析Model
和Map
参数在SpringBoot中的处理机制,帮助大家更深入地理解其工作原理。
测试代码
首先,我们通过一个简单的示例代码来展示Model
和Map
在控制器方法中的使用。
@Controller
public class HelloController {
@RequestMapping("/helloModelAndMap")
public String helloModelAndMap(HttpServletRequest request, Model model, Map<String, Object> map) {
System.out.println(model.getClass().getSimpleName());
System.out.println(map.getClass().getSimpleName());
System.out.println(model == map);
request.setAttribute("message", "Hello World!");
model.addAttribute("name", "张三");
map.put("age", 18);
// 这里拿不到Model和Map中的值,转发后能拿到 在视图渲染阶段才会放到请求域中
System.out.println("helloModelAndMap:" + request.getAttribute("name"));
System.out.println("helloModelAndMap:" + request.getAttribute("age"));
return "forward:/success";
}
@RequestMapping("/success")
@ResponseBody
public Object success(HttpServletRequest request) {
Map<String, Object> result = new HashMap<>();
result.put("message", request.getAttribute("message"));
result.put("name", request.getAttribute("name"));
result.put("age", request.getAttribute("age"));
return result;
}
}
控制台输出
运行上述代码并访问/helloModelAndMap
时,控制台会输出以下信息:
BindingAwareModelMap
BindingAwareModelMap
true
helloModelAndMap:null
helloModelAndMap:null
请求响应
访问/helloModelAndMap
后,浏览器会收到以下响应:
{"name":"张三","message":"Hello World!","age":18}
结果分析
通过控制台输出我们可以看到:
model
和map
的类型都是BindingAwareModelMap
。model
和map
指向同一个对象。
这表明Spring在处理Model
和Map
参数时,将它们都映射到同一个BindingAwareModelMap
对象。
深入源码解析
ModelAndViewContainer
ModelAndViewContainer
是Spring MVC在处理请求过程中用于存储模型数据的容器。在其内部,包含了一个ModelMap
对象:
public class ModelAndViewContainer {
private final ModelMap defaultModel = new BindingAwareModelMap();
public ModelMap getModel() {
if (useDefaultModel()) {
return this.defaultModel;
} else {
if (this.redirectModel == null) {
this.redirectModel = new ModelMap();
}
return this.redirectModel;
}
}
// 其他代码省略...
}
MapMethodProcessor
MapMethodProcessor
用于处理Map
类型的参数。它实现了HandlerMethodArgumentResolver
接口,用于解析方法参数。
public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Map.class.isAssignableFrom(parameter.getParameterType()) &&
parameter.getParameterAnnotations().length == 0;
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
return mavContainer.getModel();
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return Map.class.isAssignableFrom(returnType.getParameterType());
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue instanceof Map){
mavContainer.addAllAttributes((Map) returnValue);
} else if (returnValue != null) {
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
}
在resolveArgument
方法中,MapMethodProcessor
从ModelAndViewContainer
中获取Model
,即BindingAwareModelMap
对象。
ModelMethodProcessor
ModelMethodProcessor
用于处理Model
类型的参数。它同样实现了HandlerMethodArgumentResolver
接口。
public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Model.class.isAssignableFrom(parameter.getParameterType());
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
return mavContainer.getModel();
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return Model.class.isAssignableFrom(returnType.getParameterType());
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
return;
} else if (returnValue instanceof Model) {
mavContainer.addAllAttributes(((Model) returnValue).asMap());
} else {
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
}
在resolveArgument
方法中,ModelMethodProcessor
也从ModelAndViewContainer
中获取Model
,即BindingAwareModelMap
对象。
AbstractView
在视图渲染过程中,Spring会将BindingAwareModelMap
中的数据设置到请求域中。关键代码在AbstractView
类的exposeModelAsRequestAttributes
方法:
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
model.forEach((name, value) -> {
if (value != null) {
request.setAttribute(name, value);
} else {
request.removeAttribute(name);
}
});
}
渲染视图的调用链
在处理完控制器方法后,Spring MVC会调用视图解析和渲染逻辑。在渲染视图的过程中,BindingAwareModelMap
中的数据被转移到请求域中,这样前端页面便可以通过请求域获取这些数据。具体调用链如下:
- DispatcherServlet:处理请求,调用处理器适配器。
- HandlerAdapter:调用具体的控制器方法。
- ModelAndView:控制器方法返回
ModelAndView
对象。 - ViewResolver:解析视图名称,获取视图对象。
- AbstractView:在视图对象的
render
方法中调用exposeModelAsRequestAttributes
,将模型数据暴露到请求域。
总结
通过以上分析,我们了解了SpringBoot中Model
和Map
参数解析的工作机制。总结如下:
Model
和Map
参数都被解析为BindingAwareModelMap
对象。ModelAndViewContainer
内部使用BindingAwareModelMap
来存储模型数据。MapMethodProcessor
和ModelMethodProcessor
从ModelAndViewContainer
中获取相同的BindingAwareModelMap
对象。- 在视图渲染过程中,
BindingAwareModelMap
中的数据被转移到请求域中。
这种设计使得Model
和Map
可以互换使用,并且保证了数据的一致性和简便性。希望这篇文章能帮助大家更深入地理解SpringBoot的参数解析机制。如果有任何疑问或建议,欢迎在评论区讨论。