`
aine_pan
  • 浏览: 44840 次
  • 性别: 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中的数据绑定,并提供实例帮助初学者理解。 1. **模型...

    spring mvc 参数绑定漏洞

    3. **默认配置不当**:Spring MVC的默认配置可能允许过于宽松的数据绑定,例如允许空值绑定到非null字段,或者允许任意类型的参数绑定。 针对这些风险,开发者可以采取以下措施来增强Spring MVC应用的安全性: 1. *...

    Spring MVC数据绑定大全.rar

    "Spring MVC数据绑定大全.rar"这个压缩包很可能包含了全面讲解Spring MVC数据绑定技术的资料,如"Spring MVC数据绑定大全.pdf"。下面将详细阐述Spring MVC数据绑定的关键知识点。 1. **注解驱动的数据绑定**:...

    Spring MVC jar包

    - **基本概念**:Spring MVC提供了一个灵活的MVC实现,包括请求映射、视图解析、模型绑定等功能。它通过DispatcherServlet作为前端控制器,负责接收请求并分发到相应的处理器。 - **组件**:主要包括Model、View、...

    最全最经典spring-mvc教程

    Spring MVC还支持数据绑定,自动将请求参数绑定到Java对象,这极大地简化了表单提交的处理。对于验证,Spring MVC提供了BindingResult和Validator接口,用于校验模型数据的正确性。 另外,Spring MVC与Spring框架的...

    Spring.MVC-A.Tutorial-Spring.MVC学习指南 高清可复制版PDF

    除此之外,Spring MVC还支持数据绑定、验证、本地化、主题、异常处理等功能。例如,使用@ModelAttribute注解可以将请求参数绑定到Controller方法的参数上,@Valid用于进行数据验证,Validator接口可以自定义验证逻辑...

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

    以下是对Spring MVC数据绑定的深入解释: **数据绑定概述** 在Spring MVC中,数据绑定是通过`DataBinder`组件实现的。当一个HTTP请求到达控制器方法时,Spring MVC会自动创建一个`DataBinder`实例,并使用`...

    spring mvc 4.0

    6. **数据绑定与验证**:Spring MVC提供了数据绑定和验证功能,允许将表单数据自动绑定到Java对象,并进行校验,简化了业务逻辑处理。 7. **视图解析**:Spring MVC 4.0支持多种视图技术,如JSP、FreeMarker、...

    Spring MVC 4.2.3

    5. **数据绑定和验证**:Spring MVC提供强大的数据绑定功能,将HTTP请求参数自动映射到处理方法的参数,并支持JSR-303/JSR-349 Bean Validation标准进行数据验证。 6. **异常处理**:通过定义全局异常处理器,可以...

    Mastering Spring MVC 4(2015.09)源码

    总的来说,"Mastering Spring MVC 4(2015.09)源码"提供了深入学习Spring MVC的机会,你可以通过阅读和分析源码来了解如何配置DispatcherServlet、怎样编写控制器、如何进行数据绑定与验证,以及如何利用拦截器等特性...

    spring MVC_快速入门

    Spring MVC支持自动的数据绑定,可以将请求参数直接绑定到Controller方法的参数上,也可以将表单数据绑定到Java对象。此外,@Valid用于验证模型数据,配合Validator接口实现自定义校验规则。 7. **异常处理** ...

    SpringMVCDemo:Spring MVC 框架知识案例

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

    Spring MVC + Mybatis+Spring实现的个人博客系统

    这是一个基于Spring MVC、Mybatis和Spring框架实现的个人博客系统,涵盖了Web开发中的后端架构设计、数据库管理和前端展示等多个方面。以下将详细介绍这个系统的关键知识点: **1. Spring MVC** Spring MVC是Spring...

    spring mvc框架依赖全面jar

    `org.springframework.web.servlet-3.1.1.RELEASE.jar` 是Spring MVC的主要组件,它负责处理HTTP请求,提供模型-视图-控制器的实现,包括控制器、视图解析和模型数据绑定等功能。 `org.springframework.web-3.1.1....

    Spring MVC使用Demo

    首先,Spring MVC的设计模式基于Model-View-Controller(MVC),它将应用程序的业务逻辑、数据和用户界面进行了分离,使得代码更加清晰、易于维护。在Spring MVC中,Controller处理用户的请求,Model存储数据,而...

    Spring MVC学习框架

    8. **数据绑定与验证**:Spring MVC 提供了数据绑定功能,可以自动将请求参数映射到 Controller 方法的参数。同时,通过 @Valid 和 Validation API 可以实现数据校验。 9. **拦截器(Interceptor)**:拦截器可以在...

    spring mvc jar包

    以上这些库构成了 Spring MVC 开发的基础环境,开发者可以利用它们来创建控制器、定义模型、配置视图解析器,以及实现事务管理、数据访问等复杂功能。通过 Spring MVC,开发者能够以声明式的方式组织应用程序,提高...

    精通Spring MVC4

    Spring MVC支持自动的数据绑定,将请求参数自动映射到Controller方法的参数上。同时,使用JSR-303/JSR-349提供的注解可以进行数据验证,如@NotNull、@Size等。 9. **上传下载** 使用MultipartFile处理文件上传,...

Global site tag (gtag.js) - Google Analytics