最近看了下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 做web开发时 经常会不知道如何合适绑定页面数据 用惯struts2的朋友更认为spring mvc 绑定数据不如struts2方便 本人最开始也是这么认为 经过一段...
Spring+MVC数据绑定大全+
1.创建第一个 Spring MVC 程序案例 ...11.Spring MVC 数据绑定案例 12.Spring MVC 实现 JSON 数据返回案例 13.Spring MVC 文件的上传与下载案例 14.Spring MVC 拦截器案例 15.Spring MVC 异常处理案例
NULL 博文链接:https://yfm049.iteye.com/blog/860494
1、有如下一个订单信息页面order.jsp(置于/WEB-INF/jsp目录下),按以下步骤实现一个使用POJO类型完成表单数据传输的SpringMVC数据绑定项目。 (1) 创建一个Order类来封装上述订单信息,其中各个属性的名称和数据类型...
全书共计12章,分别从Spring框架、模型2和MVC模式、Spring MVC介绍、控制器、数据绑定和表单标签库、传唤器和格式化、验证器、表达式语言、JSTL、国际化、上传文件、下载文件多个角度介绍了Spring MVC。除此之外,...
Spring MVC数据绑定大全.rar
全书共计12章,分别从Spring框架、模型2和MVC模式、Spring MVC介绍、控制器、数据绑定和表单标签库、传唤器和格式化、验证器、表达式语言、JSTL、国际化、上传文件、下载文件多个角度介绍了Spring MVC。除此之外,...
主要介绍了Spring MVC数据绑定概述及原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
第3篇是Spring MVC技术入门,包括Spring MVC的背景介绍、架构整体剖析、环境搭建、处理器与映射器的讲解、前端控制器的源码分析、多种视图解析器的介绍、请求映射与参数绑定的介绍、Validation校验与异常处理和拦截...
全书共计12章,分别从Spring框架、模型2和MVC模式、Spring MVC介绍、控制器、数据绑定和表单标签库、传唤器和格式化、验证器、表达式语言、JSTL、国际化、上传文件、下载文件多个角度介绍了Spring MVC。除此之外,...
随之在 Spring MVC 3.2 中出现了 @MatrixVariable 注解,该注解的出现使得开发人员能够将请求中的矩阵变量(MatrixVariable)绑定到处理器的方法参数中。而 Spring 4.0 更全面地支持这个规范,这也是 Spring 4.0 ...
第12章Spring MVC参数绑定的验证 简单数据类型绑定 默认数据类型绑定 POJO类型 复杂数据类型绑定(数组、集合)
13.1 数据绑定介绍 在数据绑定过程中,Spring MVC框架会通过数据绑定组件(DataBinder)将请求参数串的内容进行类型转换,然后将转换后的值赋给控制器类中方法的形参,这样后台方法就可以正确绑定并获取客户端请求...
全书共计12章,分别从Spring框架、模型2和MVC模式、Spring MVC介绍、控制器、数据绑定和表单标签库、传唤器和格式化、验证器、表达式语言、JSTL、国际化、上传文件、下载文件多个角度介绍了Spring MVC。除此之外,...
《Spring MVC 3.0实战指南》,参考《Spring 3.x企业应用开发实战》。 内容简介: 1、Spring MVC框架简介 2、HTTP请求地址映射 3、HTTP请求数据的绑定 4、数据转换、格式化、校验 5、数据模型控制 6、视图及解析器 7...
奉上学习Spring MVC 3.0的技术资料PPT资料,我花了两天写成,该资料根据最新拙作的《Spring 3.x 企业应用开发实战 》写成,仅需要1个小时左右的时间,就可以让你学习到Spring MVC 3.0的所有知识点和新功能,强烈推荐...
本篇文章主要介绍了Spring MVC---数据绑定和表单标签详解,具有一定的参考价值,有兴趣的可以了解一下。