Controller方法的参数类型可以是基本类型,也可以是封装后的普通Java类型。若这个普通Java类型没有声明任何注解,则意味着它的每一个属性都需要到Request中去查找对应的请求参数。众所周知,无论客户端传入的是什么类型的请求参数,最终都要以字节的形式传给服务端。而服务端通过Request的getParameter方法取到的参数也都是字符串形式的结果。所以,需要有一个把字符串形式的参数转换成服务端真正需要的类型的转换工具,在spring中这个转换工具为WebDataBinder。
 
   WebDataBinder不需要我们自己去创建,我们只需要向它注册参数类型对应的属性编辑器PropertyEditor。PropertyEditor可以将字符串转换成其真正的数据类型,它的void setAsText(String text)方法实现数据转换的过程。
 
  具体的做法是,在Controller中声明一个InitBinder方法,方法中利用WebDataBinder将自己实现的或者spring自带的PropertyEditor进行注册。像下面这样:

  1. @InitBinder
  2. public void initBinder(WebDataBinder binder) throws Exception {
  3. binder.registerCustomEditor(Long.class, new CustomNumberEditor(Long.class, true));
  4. binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
  5. }
  
  处理没有任何注解的普通Java类型的参数解析器是ModelAttributeMethodProcessor,下面是参加解析方法的代码:
  1. public final Object resolveArgument(
  2. MethodParameter parameter, ModelAndViewContainer mavContainer,
  3. NativeWebRequest request, WebDataBinderFactory binderFactory)
  4. throws Exception {
  5. String name = ModelFactory.getNameForParameter(parameter);
  6. Object target = (mavContainer.containsAttribute(name)) ?
  7. mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);
  8. WebDataBinder binder = binderFactory.createBinder(request, target, name);
  9. if (binder.getTarget() != null) {
  10. bindRequestParameters(binder, request);
  11. validateIfApplicable(binder, parameter);
  12. if (binder.getBindingResult().hasErrors()) {
  13. if (isBindExceptionRequired(binder, parameter)) {
  14. throw new BindException(binder.getBindingResult());
  15. }
  16. }
  17. }
  18. mavContainer.addAllAttributes(binder.getBindingResult().getModel());
  19. return binder.getTarget();
  20. }

每次请求到来后的参数解析都会利用WebDataBinderFactory创建一个binder对象,然后从这个binder中取得最终解析好的参数对象。WebDataBinderFactory是在InvocableHandlerMethod中定义的,即不同的Controller方法有着不同的WebDataBinderFactory。其实创建binder的同时还对binder进行了初始化,这个初始化过程就会执行Controller中的InitBinder方法。InitBinderDataBinderFactory实现了初始化binder的方法:

  1. public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception {
  2. for (InvocableHandlerMethod binderMebinderMethod thod : this.binderMethods) {
  3. if (isBinderMethodApplicable(binderMethod, binder)) {
  4. Object returnValue = binderMethod.invokeForRequest(request, null, binder);
  5. if (returnValue != null) {
  6. throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod);
  7. }
  8. }
  9. }
  10. }
      上面方法中的binderMethods就是在Controller中定义的InitBinder方法,并且binderMethod 同Controller中的其他方法一样也是InvocableHandlerMethod。从上面的代码可以看出,InitBinder方法可以声明多个,WebDataBinderFactory初始化binder的时候会分别调用每个InitBinder方法。而我们在初始化的过程中使用了binder.registerCustomEditor,间接地向BeanWrapperImpl中注册了传入的PropertyEditor,以便在参数类型转换的时候使用。
 
      还记得刚才的ModelAttributeMethodProcessor解析参数时,创建binder之后调用了bindRequestParameters实现了请求参数的绑定,它的子类ServletModelAttributeMethodProcessor重写了这个方法:
  1. protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
  2. ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
  3. ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
  4. servletBinder.bind(servletRequest);
  5. }
    
     不论是父类还是子类,其实都是调用了binder的bind方法。下面是ServletRequestDataBinder的bind方法
  1. public void bind(ServletRequest request) {
  2. MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
  3. MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
  4. if (multipartRequest != null) {
  5. bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
  6. }
  7. addBindValues(mpvs, request);
  8. doBind(mpvs);
  9. }
 

这个方法跟依赖注入的过程非常相似,依赖注入是根据属性在容器中找到满足条件的对象,然后设置到当前的bean中。而上面的方法不是在容器中查找,而是从Request中获取,即把Request中的请求参数注入到binder的target中去。此时进行类型转换的就是刚刚注册的PropertyEditor,因为InitBinder方法每次都会执行,所以使用者可以在每个Controller中对相同类型的参数定义不同的参数转换方式。

经过了bindRequestParameters方法的处理,现在binder中target(即HandlerMethod的参数)已经包含了Request中的请求参数。

 
      那么,现在还有一个问题,InvocableHandlerMethod中的WebDataBinderFactory是如何来的呢?它的创建过程在RequestMappingHandlerAdapter(本文所有逻辑过程均假定使用RequestMappingHandlerAdapter):
 
  1. private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
  2. Class<?> handlerType = handlerMethod.getBeanType();
  3. Set<Method> methods = this.dataBinderFactoryCache.get(handlerType);
  4. if (methods == null) {
  5. methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);
  6. this.dataBinderFactoryCache.put(handlerType, methods);
  7. }
  8. List<InvocableHandlerMethod> binderMethods = new ArrayList<InvocableHandlerMethod>();
  9. for (Method method : methods) {
  10. InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method);
  11. binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);
  12. binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));
  13. binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
  14. binderMethods.add(binderMethod);
  15. }
  16. return createDataBinderFactory(binderMethods);
  17. }

SpringMVC中WebDataBinder的应用及原理的更多相关文章

  1. 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    目录 前言 现象 源码分析 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口介绍 HandlerMethodArgumen ...

  2. 详解SpringMVC中Controller的方法中参数的工作原理

    Spring MVC中Controller的处理方法的参数可以是Integer,String,自定义对象,ServletRequest,ServletResponse,ModelAndView等等,非 ...

  3. 详解SpringMVC中Controller的方法中参数的工作原理——基于maven

    转自:http://www.tuicool.com/articles/F7byQn 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:ht ...

  4. 【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...

  5. SpringMVC中Controller

    详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析] 目录 前言 现象 源码分析 HandlerMethodArgumentResolver与HandlerMethodR ...

  6. SpringMVC中注解@RequestBody和@ResponseBody的使用区别

    首先上源码 在面试时经常会问到我们如何使用SpringMVC将Http请求转换为java对象,或者又是问如何将结果转换为java的呢? SpringMVC在接收到请求之后HandlerMapping像 ...

  7. 8.springMVC中的RESTful架构风格

    RESTful架构:是一种设计的风格,并不是标准,只是提供了一组设计原则和约束条件,也是目前比较流行的一种互联网软件架构.它结构清晰.符合标准.易于理解.扩展方便,所以正得到越来越多网站的采用. 关于 ...

  8. JavaEE开发之SpringMVC中的静态资源映射及服务器推送技术

    在上篇博客中,我们聊了<JavaEE开发之SpringMVC中的自定义拦截器及异常处理>.本篇博客我们继续的来聊SpringMVC的东西,下方我们将会聊到js.css这些静态文件的加载配置 ...

  9. 将SpringMVC中的HttpMessageConverter替换为Gson

    读者们看到这个标题也许会感到奇怪,SpringMVC中默认的HttpMessageConverter不是Jackson吗,但是我在使用的过程中发现Jackson并不好用,如果有一些复杂的嵌套类型,当然 ...

随机推荐

  1. Android Wear - Design Principles for Android Wear(设计原则)

    ---------------------------------------------------------------------------------------------------- ...

  2. python16_day18【Django_Form表单、分页】

    一.表单 Django的Form主要具有一下几大功能: 生成HTML标签 验证用户数据(显示错误信息) HTML Form提交保留上次提交数据 初始化页面显示内容 1.Form类 创建Form类时,主 ...

  3. java要注意的问题1

    一.优先返回空集合而非null 如果程序要返回一个不包含任何值的集合,确保返回的是空集合而不是null.这能节省大量的”if else”检查. public class getLocationName ...

  4. Team Foundation 中的错误和事件消息

    Visual Studio Team System Team Foundation 中的错误和事件消息 Team Foundation 通过显示错误消息和事件消息来通知您操作成功以及操作失败.一部分错 ...

  5. ruby underscore

    “examScore".underscore : exam_score "ExamScore".underscore: exam_score

  6. 【转】PCA与Whitening

    PCA: PCA的具有2个功能,一是维数约简(可以加快算法的训练速度,减小内存消耗等),一是数据的可视化. PCA并不是线性回归,因为线性回归是保证得到的函数是y值方面误差最小,而PCA是保证得到的函 ...

  7. Django学习笔记之Django视图View

    一个视图函数(类),简称视图,是一个简单的Python 函数(类),它接受Web请求并且返回Web响应. 响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片. ...

  8. Python 函数定义和使用

    # 函数的概念 # 概念 # 写了一段代码实现了某个小功能; 然后把这些代码集中到一块, 起一个名字; 下一次就可以根据这个名字再次使用这个代码块, 这就是函数 # 作用 # 方便代码的重用 # 分解 ...

  9. spring security使用hibernate进行查询数据库验证

    前面查询数据库采用的都是jdbc方式,如果系统使用的是hibernate,该如何进行呢,下面就是实现步骤,关键还是实现自定义的UserDetailsService 项目结构如下: 使用hibernat ...

  10. java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[]]

    1.问题描述: 对于创建的springboot项目,通过启动类启动,访问没问题,但打成war部署到tomcat上启动报错,如下: 严重: ContainerBase.addChild: start: ...