`
aine_pan
  • 浏览: 43883 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

Spring MVC 之神奇的数据绑定

阅读更多
最近看了下Spring MVC的架构,的确是继承了Spring的一贯风格和优秀的编程思想,具体不说了,今天分享下自己学习的一点心得。
直接看我们的controller 类:
@Controller
public class LoginAction{
	@RequestMapping(value = "login.do", method = RequestMethod.POST)
	protected ModelAndView onSubmit(LoginInfo cmd)
			throws Exception {
		if (login(cmd) == 0) {
			HashMap result_map = new HashMap();
			result_map.put("logininfo", loginInfo);
			List msgList = new LinkedList();
			msgList.add("msg1");
			msgList.add("msg2");
			msgList.add("msg3");
			result_map.put("messages", msgList);
			return new ModelAndView("main", result_map);
		} else {
			return new ModelAndView("loginfail");
		}
	}

        private int login(LoginInfo loginInfo) {
		if ("aine".equalsIgnoreCase(loginInfo.getUsername())
				&& "aine".equals(loginInfo.getPassword())) {
			return 0;
		}
		return 1;
	}
}


看到这里不知道有没有人跟我一样,奇怪,Spring怎么组织LoginInfo这个实体类给controller的?
大概的流程应该是:前端HttpRequest到Web容器,而Web容器转给核心中转类(servlet)DispatcherServlet,而这个类里获取当前需要的实体类,并且实例化,填上request中的值,调AP端的controller类。最后在我们的controller类中就可以直接使用已经封装请求值实体类了。

现在我们就关注DispatcherServlet是如何获取并且动态封装交易数据的。

1 我们知道在启动Spring的时候,我们会注册所有的Bean,封装在HandlerMapping中。
2 当请求收到后,系统会根据请求中的action到Map中去取HandlerExecutionChain。在HandlerExecutionChain中封装了所有的Interceptors和我们的真正的controller handler。
3 处理完controller handler之后,DispatcherServlet会获得一个View,框架会根据这个View组织页面到Client端页面。

用上面的case为例,真实的Handler是LoginAction。
1 框架在调用onSubmit(方法名不重要)之前会分析,当前的请求映射的方法需要哪些参数。因为我是通过注解的方式配置的,所以在AnnotationMethodHandlerAdapter中,我们可以看到:
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
		Method handlerMethod = methodResolver.resolveHandlerMethod(request);
		ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		ExtendedModelMap implicitModel = new BindingAwareModelMap();

		Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
		ModelAndView mav =
				methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
		methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
		return mav;
	}

首先框架启动时会存储所有的类与Method的Mapping:
	private final Map<Class<?>, ServletHandlerMethodResolver> methodResolverCache =
			new HashMap<Class<?>, ServletHandlerMethodResolver>();

此时我们可以根据Handler Class找到Mapping的Method,通过ServletHandlerMethodInvoker类来调用invokeHandlerMethod方法(清楚起见,忽略了部分代码):
	public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
			NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
		Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
		try {
			...
			Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
			...
			ReflectionUtils.makeAccessible(handlerMethodToInvoke);
			return handlerMethodToInvoke.invoke(handler, args);
		}
		catch (IllegalStateException ex) {
			throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex);
		}
		catch (InvocationTargetException ex) {
			ReflectionUtils.rethrowException(ex.getTargetException());
			return null;
		}
	}

在handlerMethod中,我们已经可以获得当前Mapping的方法所有属性,包括其输入参数,返回值。最终我们必然会调用执行这个方法,但是在调用之前,我们来看看我们要做哪些事情。
1 处理BridgedMethod属性和方法(忽略之)。
2 resolveHandlerArguments,处理输入参数(重点)。
3 打开方法的访问性限制
4 正式的访问方法的逻辑

其中对输入参数的处理是我这次讲解的重点,我们继续看resolveHandlerArguments的处理逻辑(清楚起见,忽略部分代码)。
	private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,
			NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {

		Class[] paramTypes = handlerMethod.getParameterTypes();//获取所有参数
		Object[] args = new Object[paramTypes.length];

		for (int i = 0; i < args.length; i++) {//遍历处理所有参数
			MethodParameter methodParam = new MethodParameter(handlerMethod, i);
			methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);
			GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
					
			WebDataBinder binder = resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);//创建参数实例:LoginInfo
			//其底层实现就是一句话:BeanUtils.instantiateClass(paramType);
			
			boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
			if (binder.getTarget() != null) {
				doBind(binder, webRequest, validate, !assignBindingResult);//开始操作参数实例的值了
			}
			args[i] = binder.getTarget();
			if (assignBindingResult) {
				args[i + 1] = binder.getBindingResult();
				i++;
			}
			implicitModel.putAll(binder.getBindingResult().getModel());
		}
	} 

其中最关键的是doBind方法了,我们继续往下看,框架会调用WebRequestDataBinder中的bind方法:
	public void bind(WebRequest request) {
		MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());//遍历request,获取提交的所有请求参数和值
		if (request instanceof NativeWebRequest) {
			MultipartRequest multipartRequest = ((NativeWebRequest) request).getNativeRequest(MultipartRequest.class);
			if (multipartRequest != null) {
				bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
			}
		}
		doBind(mpvs);
	}

这里已经获取到所有的请求参数值了,离我们的目地更近一步了,下面继续看doBind(mpvs)。
这里必须要清楚我们的下一个主角了:BeanPropertyBindingResult,他拥有一个
private transient BeanWrapper beanWrapper;

相信有经验的人看到他,就知道离真相不远了。
在DataBinder中:
protected void applyPropertyValues(MutablePropertyValues mpvs) {
		try {
			// Bind request parameters onto target object.
			BeanWrapperImpl.setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
		}
		catch (PropertyBatchUpdateException ex) {
			// Use bind error processor to create FieldErrors.
			for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
				getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
			}
		}
	}

大家都知道BeanWrapperImpl是继承AbstractPropertyAccessor的,而setPropertyValues方法是基类实现的,我们看看他的实现逻辑:
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
			throws BeansException {

		List<PropertyAccessException> propertyAccessExceptions = null;
		List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
				((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
		for (PropertyValue pv : propertyValues) {
			try {
				// This method may throw any BeansException, which won't be caught
				// here, if there is a critical failure such as no matching field.
				// We can attempt to deal only with less serious exceptions.
				setPropertyValue(pv);
			}
			catch (NotWritablePropertyException ex) {
				if (!ignoreUnknown) {
					throw ex;
				}
				// Otherwise, just ignore it and continue...
			}
			catch (NullValueInNestedPathException ex) {
				if (!ignoreInvalid) {
					throw ex;
				}
				// Otherwise, just ignore it and continue...
			}
			catch (PropertyAccessException ex) {
				if (propertyAccessExceptions == null) {
					propertyAccessExceptions = new LinkedList<PropertyAccessException>();
				}
				propertyAccessExceptions.add(ex);
			}
		}

		// If we encountered individual exceptions, throw the composite exception.
		if (propertyAccessExceptions != null) {
			PropertyAccessException[] paeArray =
					propertyAccessExceptions.toArray(new PropertyAccessException[propertyAccessExceptions.size()]);
			throw new PropertyBatchUpdateException(paeArray);
		}
	}

setPropertyValue方法是BeanWrapperImpl子类实现的具体逻辑了,对于这个牛逼的类,我不太想在这里说太多了,了解Spring的人都知道他的牛逼之处,贴一下他的实现代码:
private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
		String propertyName = tokens.canonicalName;
		String actualName = tokens.actualName;
		try {
		
				final Method writeMethod = (pd instanceof GenericTypeAwarePropertyDescriptor ?
						((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodForActualAccess() :
						pd.getWriteMethod());
		
				final Object value = valueToApply;

				writeMethod.invoke(this.object, value);//调用POJO中的setXX();

			}
			catch (TypeMismatchException ex) {
				throw ex;
			}
			catch (InvocationTargetException ex) {
				throw ex;
			}
			catch (Exception ex) {
				throw ex;
			}
		}
	}

这样返回的实体类就是已经封装好所有属性值的对象了。
至于为什么系统会根据属性名直接调用set属性名方法,可以看实现类GenericTypeAwarePropertyDescriptor:
public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName,
			Method readMethod, Method writeMethod, Class propertyEditorClass)
			throws IntrospectionException {

		super(propertyName, null, null);
		this.beanClass = beanClass;
		this.propertyEditorClass = propertyEditorClass;

		Method readMethodToUse = BridgeMethodResolver.findBridgedMethod(readMethod);
		Method writeMethodToUse = BridgeMethodResolver.findBridgedMethod(writeMethod);
		if (writeMethodToUse == null && readMethodToUse != null) {
			// Fallback: Original JavaBeans introspection might not have found matching setter
			// method due to lack of bridge method resolution, in case of the getter using a
			// covariant return type whereas the setter is defined for the concrete property type.
			writeMethodToUse = ClassUtils.getMethodIfAvailable(this.beanClass,
					"set" + StringUtils.capitalize(getName()), readMethodToUse.getReturnType());//这下彻底清除了吧?
		}
		this.readMethod = readMethodToUse;
		this.writeMethod = writeMethodToUse;//这个是就最终的方法
	}


总结一下:不管是使用什么方法配置Spring,实例化对象的底层实现是一致的,容器注册controller的时候已经登记了被Mapping的方法的所有属性,在请求访问的时候,通过BeanWrapperImpl动态实例化POJO并且赋值,在真正调用controller的时候,传入的参数已经是处理过的了,所以在controller中可以直接使用。

不知道这样写大家能不能看出个所以然,要写一篇逻辑完整严谨的文章真的不容易,以后在多努力吧。
分享到:
评论

相关推荐

    spring MVC数据绑定大全

    spring MVC数据绑定 含例子 转载自疯芒毕露的专栏 刚开始用spring mvc 做web开发时 经常会不知道如何合适绑定页面数据 用惯struts2的朋友更认为spring mvc 绑定数据不如struts2方便 本人最开始也是这么认为 经过一段...

    Spring+MVC数据绑定大全+

    Spring+MVC数据绑定大全+

    SpringMVCDemo:Spring MVC 框架知识案例

    1.创建第一个 Spring MVC 程序案例 ...11.Spring MVC 数据绑定案例 12.Spring MVC 实现 JSON 数据返回案例 13.Spring MVC 文件的上传与下载案例 14.Spring MVC 拦截器案例 15.Spring MVC 异常处理案例

    spring mvc 参数绑定漏洞

    NULL 博文链接:https://yfm049.iteye.com/blog/860494

    Spring MVC注解与数据绑定(含源码及结果图)

    1、有如下一个订单信息页面order.jsp(置于/WEB-INF/jsp目录下),按以下步骤实现一个使用POJO类型完成表单数据传输的SpringMVC数据绑定项目。 (1) 创建一个Order类来封装上述订单信息,其中各个属性的名称和数据类型...

    [免费]Spring MVC学习指南(高清)

    全书共计12章,分别从Spring框架、模型2和MVC模式、Spring MVC介绍、控制器、数据绑定和表单标签库、传唤器和格式化、验证器、表达式语言、JSTL、国际化、上传文件、下载文件多个角度介绍了Spring MVC。除此之外,...

    Spring MVC数据绑定大全.rar

    Spring MVC数据绑定大全.rar

    大优惠 Spring MVC学习指南(第2版)2017.pdf

    全书共计12章,分别从Spring框架、模型2和MVC模式、Spring MVC介绍、控制器、数据绑定和表单标签库、传唤器和格式化、验证器、表达式语言、JSTL、国际化、上传文件、下载文件多个角度介绍了Spring MVC。除此之外,...

    Spring MVC数据绑定概述及原理详解

    主要介绍了Spring MVC数据绑定概述及原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    Spring MVC+MyBatis开发从入门到项目实战

    第3篇是Spring MVC技术入门,包括Spring MVC的背景介绍、架构整体剖析、环境搭建、处理器与映射器的讲解、前端控制器的源码分析、多种视图解析器的介绍、请求映射与参数绑定的介绍、Validation校验与异常处理和拦截...

    Spring MVC学习指南

    全书共计12章,分别从Spring框架、模型2和MVC模式、Spring MVC介绍、控制器、数据绑定和表单标签库、传唤器和格式化、验证器、表达式语言、JSTL、国际化、上传文件、下载文件多个角度介绍了Spring MVC。除此之外,...

    Spring MVC 使用矩阵变量绑定参数.docx

    随之在 Spring MVC 3.2 中出现了 @MatrixVariable 注解,该注解的出现使得开发人员能够将请求中的矩阵变量(MatrixVariable)绑定到处理器的方法参数中。而 Spring 4.0 更全面地支持这个规范,这也是 Spring 4.0 ...

    第12章Spring MVC参数绑定的验证

    第12章Spring MVC参数绑定的验证 简单数据类型绑定 默认数据类型绑定 POJO类型 复杂数据类型绑定(数组、集合)

    基于java的企业级应用开发:数据绑定介绍.ppt

    13.1 数据绑定介绍 在数据绑定过程中,Spring MVC框架会通过数据绑定组件(DataBinder)将请求参数串的内容进行类型转换,然后将转换后的值赋给控制器类中方法的形参,这样后台方法就可以正确绑定并获取客户端请求...

    Spring.MVC-A.Tutorial-Spring.MVC学习指南

    全书共计12章,分别从Spring框架、模型2和MVC模式、Spring MVC介绍、控制器、数据绑定和表单标签库、传唤器和格式化、验证器、表达式语言、JSTL、国际化、上传文件、下载文件多个角度介绍了Spring MVC。除此之外,...

    Spring MVC 3.0实战指南.ppt

    《Spring MVC 3.0实战指南》,参考《Spring 3.x企业应用开发实战》。 内容简介: 1、Spring MVC框架简介 2、HTTP请求地址映射 3、HTTP请求数据的绑定 4、数据转换、格式化、校验 5、数据模型控制 6、视图及解析器 7...

    Spring MVC的教程项目代码

    奉上学习Spring MVC 3.0的技术资料PPT资料,我花了两天写成,该资料根据最新拙作的《Spring 3.x 企业应用开发实战 》写成,仅需要1个小时左右的时间,就可以让你学习到Spring MVC 3.0的所有知识点和新功能,强烈推荐...

    Spring MVC---数据绑定和表单标签详解

    本篇文章主要介绍了Spring MVC---数据绑定和表单标签详解,具有一定的参考价值,有兴趣的可以了解一下。

Global site tag (gtag.js) - Google Analytics