SpringMVC源码解析- HandlerAdapter - ModelFactory
ModelFactory主要是两个职责:
1. 初始化model
2. 处理器执行后将modle中相应参数设置到SessionAttributes中
我们来看看具体的处理逻辑(直接充当分析目录):
1. 初始化model
1.1 解析类上使用的sessionAttributres,将获取参数合并到mavContainer中
1.2 执行注解了@ModelAttribute的方法,并将结果同步到Model
参数名的生成规则:@ModelAttribute中定义的value > 方法的返回类型决定(直接往model.addAttribute的除外)
1.3 将注解@ModelAttribute方法参数(在@SessionAttributes定义范围内)同步到model中
将方法中使用@ModelAttribute的参数跟@SessionAttribute核对,如果都定义了,需要将其参数值同步至mavContainer
2. 处理器执行后将modle中相应参数设置到SessionAttributes中
2.1 如果SessionStatus被调用了setComplete则清除sesssionAttributesHandler中缓存的数据
2.2 如果没清除,将model中的数据同步至sessionAttributesHandler中
2.3 如果handler还没处理完(是否需要渲染页面),绑定BindingResult到model(如果需要的话)
上面的代码说明在日常开发时,SessionStatus.setComplete写在方法哪个位置都行,因为他是在方法执行后才在这边调用,跟方法中的顺序无关.
1. 初始化model
做了三个事情,详细见源码中的注释吧:
- package org.springframework.web.method.annotation;
- public final class ModelFactory {
- public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod)
- throws Exception {
- // 获取使用@SessionAttributes注解并已经解析的参数,合并到mavContainer
- Map<String, ?> attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request);
- mavContainer.mergeAttributes(attributesInSession);
- // 执行使用@ModelAttribute注解的方法,并将结果设置到mavContainer
- invokeModelAttributeMethods(request, mavContainer);
- // 将同时使用@ModelAttribute和@SessionAttributes注解的参数设置到mavContainer
- for (String name : findSessionAttributeArguments(handlerMethod)) {
- if (!mavContainer.containsAttribute(name)) {
- Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
- if (value == null) {
- throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
- }
- mavContainer.addAttribute(name, value);
- }
- }
- }
- // ...
- }
1.1 解析类上使用的sessionAttributres,将获取参数合并到mavContainer中
这部分,之前的<SpringMVC源码解析 - HandlerAdapter - @SessionAttributes注解处理>已经讲述得很细,这边就不展开.
1.2 执行注解了@ModelAttribute的方法,并将结果同步到Model
迭代所有使用@ModelAttribute注解的方法
获取@ModelAttribute中的value属性值作为 model attribute,如果mavContainer中已经存在则退出
委托InvocableHandlerMethod的invokeForRequest生成属性值.
a,获取当前方法的调用参数
b,直接执行invoke,并返回结果
如果方法不是void的,则需要将值同步到mavContainer
a,如果方法是void,则说明用户直接将参数通过model.addAttribute设置好值了
b,参数名的生成规则:@ModelAttribute中定义的value > 方法的返回类型决定
根据方法的返回类型决定参数名时,大致的规则如下:
String -> string(这边就解释我之前没搞明白使用@ModelAttribute注解实例的最后一个情况)
List<Double> -> doubleList
c,如果mavContainer中还没有这个参数值,则同步进去
- package org.springframework.web.method.annotation;
- public final class ModelFactory {
- private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer mavContainer)
- throws Exception {
- // 迭代使用@ModelAttribute注解的方法
- for (InvocableHandlerMethod attrMethod : this.attributeMethods) {
- // 使用@ModelAttribute的value值作为 attribute name
- String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();
- if (mavContainer.containsAttribute(modelName)) {
- continue;
- }
- // 委托InvocableHandlerMethod调用方法,生成值
- Object returnValue = attrMethod.invokeForRequest(request, mavContainer);
- // 如果方法返回值,需要将这个值同步到mavContainer中
- if (!attrMethod.isVoid()){
- // 生成参数名:注解的value或者返回值类型
- String returnValueName = getNameForReturnValue(returnValue, attrMethod.getReturnType());
- if (!mavContainer.containsAttribute(returnValueName)) {
- mavContainer.addAttribute(returnValueName, returnValue);
- }
- }
- }
- }
- // ...
- }
看看InvocableHandlerMethod的invokeForRequest(NativeWebRequest request,ModelAndViewContainer mavContainer,Object... providedArgs)
这边涉及到两个封装类:InvocableHandlerMethod和MethodParameter.
InvocableHandlerMethod封装一个可执行的方法,在HandlerMethod基础上添加方法参数解析的职责.
MethodParameter封装方法定义相关的概念
具体的处理逻辑还是看代码中的注释吧.
- package org.springframework.web.method.support;
- public class InvocableHandlerMethod extends HandlerMethod {
- public final Object invokeForRequest(NativeWebRequest request,
- ModelAndViewContainer mavContainer,
- Object... providedArgs) throws Exception {
- // 生成方法调用时的参数
- Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
- // 霸气的调用
- Object returnValue = invoke(args);
- return returnValue;
- }
- private Object[] getMethodArgumentValues(
- NativeWebRequest request, ModelAndViewContainer mavContainer,
- Object... providedArgs) throws Exception {
- // 获取参数,这边没有值
- MethodParameter[] parameters = getMethodParameters();
- Object[] args = new Object[parameters.length];
- for (int i = 0; i < parameters.length; i++) {
- MethodParameter parameter = parameters[i];
- // 参数名称查找器,反射中拿不到参数名,所以使用spring的parameterNameDiscover
- parameter.initParameterNameDiscovery(parameterNameDiscoverer);
- // 获取参数的目标类型,methodParam.setParameterType(result);设置.这边具体的逻辑后面再细化
- GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
- // 尝试通过类型判断,获取参数的值
- args[i] = resolveProvidedArgument(parameter, providedArgs);
- if (args[i] != null) {
- continue;
- }
- // 使用HandlerMethodArgumentResolver,判断是否支持处理
- if (argumentResolvers.supportsParameter(parameter)) {
- try {
- // 这边直接处理,实际执行时,是通过责任链设计模式处理
- args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory);
- continue;
- } catch (Exception ex) {
- throw ex;
- }
- }
- if (args[i] == null) {
- String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
- throw new IllegalStateException(msg);
- }
- }
- return args;
- }
- private Object invoke(Object... args) throws Exception {
- // 解决权限的问题
- ReflectionUtils.makeAccessible(this.getBridgedMethod());
- try {
- return getBridgedMethod().invoke(getBean(), args);
- }
- catch (IllegalArgumentException | InvocationTargetExceptione) {
- // 省略异常处理机制
- }
- }
- // ...
- }
我们再来看看参数名称的生成规则吧:
如果@ModelAttribute中定义了value,就以value命名
如果注解中没有定义value,则根据返回值类型定义名称
如:String会被定义为string,List<Double>会被定义为doubleList(集合都是这样定义的,包括array数组)
- package org.springframework.web.method.annotation;
- public final class ModelFactory {
- public static String getNameForReturnValue(Object returnValue, MethodParameter returnType) {
- ModelAttribute annot = returnType.getMethodAnnotation(ModelAttribute.class);
- if (annot != null && StringUtils.hasText(annot.value())) { // 注解中定义了value
- return annot.value();
- }
- else { // 根据类型生成
- Method method = returnType.getMethod();
- Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, returnType.getDeclaringClass());
- return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue);
- }
- }
- // ...
- }
接下来是如何根据返回值类型生成参数名称的逻辑,挺有意思,重点展开:
这边又根据方法的signature中定义的参数类型是否细化再衍生一个分支:
如果方法签名中只定义Object类型,则需要根据value生成;否则根据签名生成
- package org.springframework.core;
- public abstract class Conventions {
- public static String getVariableNameForReturnType(Method method, Class resolvedType, Object value) {
- // 如果signature定义为object,则根据value来判断
- if (Object.class.equals(resolvedType)) {
- if (value == null) {
- throw new IllegalArgumentException("Cannot generate variable name for an Object return type with null value");
- }
- // 这边的处理逻辑跟下面的很类似,不展开.差别是一个根据value,一个根据resolvedType判断
- return getVariableName(value);
- }
- Class valueClass;
- // 是否是数组或集合
- boolean pluralize = false;
- if (resolvedType.isArray()) { // 数组,读取内部元素的类型
- valueClass = resolvedType.getComponentType();
- pluralize = true;
- }
- else if (Collection.class.isAssignableFrom(resolvedType)) { // 集合
- // 集合内的元素类型
- valueClass = GenericCollectionTypeResolver.getCollectionReturnType(method);
- if (valueClass == null) {
- if (!(value instanceof Collection)) {// 跟value再校验一遍类型
- throw new IllegalArgumentException(
- "Cannot generate variable name for non-typed Collection return type and a non-Collection value");
- }
- Collection collection = (Collection) value;
- if (collection.isEmpty()) {
- throw new IllegalArgumentException(
- "Cannot generate variable name for non-typed Collection return type and an empty Collection value");
- }
- // 获取集合中的第一个value
- Object valueToCheck = peekAhead(collection);
- // 获取value的类系
- valueClass = getClassForValue(valueToCheck);
- }
- pluralize = true;
- }
- else {
- valueClass = resolvedType;
- }
- String name = ClassUtils.getShortNameAsProperty(valueClass);
- return (pluralize ? pluralize(name) : name);
- }
- // 获取集合中的第一个value
- private static Object peekAhead(Collection collection) {
- Iterator it = collection.iterator();
- if (!it.hasNext()) {
- throw new IllegalStateException(
- "Unable to peek ahead in non-empty collection - no element found");
- }
- Object value = it.next();
- if (value == null) {
- throw new IllegalStateException(
- "Unable to peek ahead in non-empty collection - only null element found");
- }
- return value;
- }
- private static Class getClassForValue(Object value) {
- Class valueClass = value.getClass();
- // 代理时根据接口获取,遍历时以第一个符合条件的为准
- if (Proxy.isProxyClass(valueClass)) {
- Class[] ifcs = valueClass.getInterfaces();
- for (Class ifc : ifcs) {
- if (!ignoredInterfaces.contains(ifc)) {
- return ifc;
- }
- }
- }
- else if (valueClass.getName().lastIndexOf('$') != -1 && valueClass.getDeclaringClass() == null) {
- // '$' in the class name but no inner class -
- // assuming it's a special subclass (e.g. by OpenJPA)
- valueClass = valueClass.getSuperclass();
- }
- return valueClass;
- }
- // 数组或结合统一添加后缀List
- private static String pluralize(String name) {
- //private static final String PLURAL_SUFFIX = "List";
- return name + PLURAL_SUFFIX;
- }
- }
1.3 将注解@ModelAttribute方法参数(在@SessionAttributes定义范围内)同步到model中
遍历HandlerMethod的所有参数,找出使用了@ModelAttribute注解的参数
获取参数的名称:注解value值 > 参数类型
核对这个参数名称是否在@SessionAttributes注解内
如果mavContainer中还没有该参数,继续处理
获取缓存在sessionAttributesHandler中的参数值
如果值为空,抛HttpSessionRequiredException
否则同步到mavContainer中
- package org.springframework.web.method.annotation;
- public final class ModelFactory {
- // ...
- public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod)
- throws Exception {
- // ...
- for (String name : findSessionAttributeArguments(handlerMethod)) {
- if (!mavContainer.containsAttribute(name)) {
- Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
- if (value == null) {
- throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
- }
- mavContainer.addAttribute(name, value);
- }
- }
- }
- private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) {
- List<String> result = new ArrayList<String>();
- // 这边找的是HandlerMethod的参数
- for (MethodParameter param : handlerMethod.getMethodParameters()) {
- if (param.hasParameterAnnotation(ModelAttribute.class)) {
- String name = getNameForParameter(param);
- if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, param.getParameterType())) {
- result.add(name);
- }
- }
- }
- return result;
- }
- public static String getNameForParameter(MethodParameter parameter) {
- ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class);
- String attrName = (annot != null) ? annot.value() : null;
- // 如果value为空,获取参数类型解析属性名称
- return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter);
- }
- }
2. 处理器执行后将modle中相应参数设置到SessionAttributes中
2.1 如果SessionStatus被调用了setComplete则清除sesssionAttributesHandler中缓存的数据
2.2 如果没清除,将model中的数据同步至sessionAttributesHandler中
2.3 如果handler还没处理完(是否需要渲染页面),绑定BindingResult到model(如果需要的话)
还需要补充说明的是:
判断绑定BindingResult到model时的条件(满足任意):
a,不是其他参数绑定结果的Bindingresult
b,@SessionAttributes注解定义范围内
c, 不是null,数组,集合,map,简单数据类型
剩下的看代码注释就行了
- package org.springframework.web.method.annotation;
- public final class ModelFactory {
- // ...
- public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception {
- if (mavContainer.getSessionStatus().isComplete()){ // 清除
- this.sessionAttributesHandler.cleanupAttributes(request);
- }
- else { // 不清除,那么就需要同步
- this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel());
- }
- if (!mavContainer.isRequestHandled()) {
- updateBindingResult(request, mavContainer.getModel());
- }
- }
- private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception {
- List<String> keyNames = new ArrayList<String>(model.keySet());
- for (String name : keyNames) {
- Object value = model.get(name);
- // 核对是否需要绑定BindingResult到model
- if (isBindingCandidate(name, value)) {
- String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name;
- if (!model.containsAttribute(bindingResultKey)) { // 不是其他参数绑定的结果
- WebDataBinder dataBinder = binderFactory.createBinder(request, value, name);
- model.put(bindingResultKey, dataBinder.getBindingResult());
- }
- }
- }
- }
- private boolean isBindingCandidate(String attributeName, Object value) {
- // 不是其他参数绑定的结果
- if (attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
- return false;
- }
- // 是否在@SessionAttributes注解定义中
- Class<?> attrType = (value != null) ? value.getClass() : null;
- if (this.sessionAttributesHandler.isHandlerSessionAttribute(attributeName, attrType)) {
- return true;
- }
- // 不是null,数组,集合,map,简单数据类型,则调用
- return (value != null && !value.getClass().isArray() && !(value instanceof Collection) &&
- !(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass()));
- }
- }
SpringMVC源码解析- HandlerAdapter - ModelFactory的更多相关文章
- SpringMVC源码解析- HandlerAdapter - ModelFactory(转)
ModelFactory主要是两个职责: 1. 初始化model 2. 处理器执行后将modle中相应参数设置到SessionAttributes中 我们来看看具体的处理逻辑(直接充当分析目录): 1 ...
- SpringMVC源码解析- HandlerAdapter初始化
HandlerAdapter初始化时,主要是进行注解解析器初始化注册;返回值处理类初始化;全局注解@ControllerAdvice内容读取并缓存. 目录: 注解解析器初始化注册:@ModelAttr ...
- SpringMVC源码解析 - HandlerAdapter - @SessionAttributes注解处理
使用SpringMVC开发时,可以使用@SessionAttributes注解缓存信息.这样业务开发时,就不需要一次次手动操作session保存,读数据. @Controller @RequestMa ...
- SpringMVC源码解析 - HandlerAdapter - HandlerMethodArgumentResolver
HandlerMethodArgumentResolver主要负责执行handler前参数准备工作. 看个例子,红色部分的id初始化,填充值就是它干的活: @RequestMapping(value ...
- springMVC源码解析--ViewResolver视图解析器执行(三)
之前两篇博客springMVC源码分析--ViewResolver视图解析器(一)和springMVC源码解析--ViewResolverComposite视图解析器集合(二)中我们已经简单介绍了一些 ...
- SpringMVC源码解析
一:springmvc运行过程: 1. dispatcherServlet 通过 HandlerMapping 找到controller2. controller经过后台逻辑处理得到结果集modela ...
- 深入了解SpringMVC源码解析
Spring MVC源码解析 Spring MVC的使用原理其实是通过配置一个Servlet来接管所有的请求,所有的请求由这个Servlet来进行分发处理. 我们可以从web.xml里面看出这一点 & ...
- springMVC源码解析--ViewResolverComposite视图解析器集合(二)
上一篇博客springMVC源码分析--ViewResolver视图解析器(一)中我们介绍了一些springMVC提供的很多视图解析器ViewResolver,在开发的一套springMVC系统中是可 ...
- springMVC源码解析--HandlerMethodArgumentResolverComposite参数解析器集合(二)
上一篇博客springMVC源码分析--HandlerMethodArgumentResolver参数解析器(一)中我们已经介绍了参数解析相关的东西,并且也提到了HandlerMethodArgume ...
随机推荐
- NET基础篇——反射的奥妙
反射是一个程序集发现及运行的过程,通过反射可以得到*.exe或*.dll等程序集内部的信息.使用反射可以看到一个程序集内部的接口.类.方法.字段.属性.特性等等信息.在System.Reflectio ...
- cowboy添加验证码
参考的http://beebole.com/blog/erlang/how-to-implement-captcha-in-erlang-web-application/,移到cowboy,修改了下: ...
- 黄聪:win7 64位系统PS、AI、PSD缩略图预览补丁
MysticThumbs支持Windows 7 / Vista / XP,32位和64位.除了预览PSD以外,还支持DDS.SGI缩略图显示. Mystic Thumbs是一款用来支持win7 64位 ...
- css用法大全
direction:控制文本方向 ltr:默认.文本方向从左到右. rtl:文本方向从右到左. inherit:规定应该从父元素继承 direction 属性的值. <select name=& ...
- Gson的几种使用方式
一.Gson是一个Java类库,用于将Java对象转换为它们所代表的JSON数据,也可以用于将一个JSON字符串转换为对应的Java对象.这个是谷歌开发的一套针对json处理的一个类库,功能很强大. ...
- OpenGL 画高斯随机函数
高斯函数代码 const float CFFTOceanShader::_getGaussianRandomFloat() const { float u1 = rand() / (float)RAN ...
- springcloud(五) Hystrix 降级,超时
分布式系统中一定会遇到的一个问题:服务雪崩效应或者叫级联效应什么是服务雪崩效应呢? 在一个高度服务化的系统中,我们实现的一个业务逻辑通常会依赖多个服务,比如:商品详情展示服务会依赖商品服务, 价格服务 ...
- OpenMP 旅行商问题,静态调度
▶ <并行程序设计导论>第六章中讨论了旅行商,分别使用了 MPI,Pthreads,OpenMP 来进行实现,这里是 OpenMP 的代码,分为静态调度(每个线程分分配等量的搜索人物)和动 ...
- Rhythmk 学习 Hibernate 09 - Hibernate HQL
1.初始数据 @Test public void test01() { Session session = null; try { session = HibernateUtil.getSession ...
- 【321】python进程监控:psutil
参考:Python进程监控-MyProcMonitor 参考:Python3.6 安装psutil 模块和功能简介 参考:psutil module (Download files) 参考:廖雪峰 - ...