直接看我们的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, ServletHandlerMethodResolver> methodResolverCache = new HashMap , 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 { ListpropertyAccessExceptions = null; List 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 (); } 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中可以直接使用。
不知道这样写大家能不能看出个所以然,要写一篇逻辑完整严谨的文章真的不容易,以后在多努力吧。