`

spring源码学习系列3.2.2-How to bind String to Date

 
阅读更多
springmvc开发中,经常需将界面日期数据(String)转换成后台模型的日期类型(Date)数据。

不同版本的springmvc可能有不同的处理方法,或者版本越高,处理方法越优化

实验的几种方式

public class User {  
    private int id;  
    private String username;  
    private String password;  
    private Date birth;
    public int getId() {  
        return id;  
    }  
    public void setId(int id) {  
        this.id = id;  
    }  
    public String getUsername() {  
        return username;  
    }  
    public void setUsername(String username) {  
        this.username = username;  
    }  
    public String getPassword() {  
        return password;  
    }  
    public void setPassword(String password) {  
        this.password = password;  
    }
	public Date getBirth() {
		return birth;
	}
	public void setBirth(Date birth) {
		this.birth = birth;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", username=" + username + ", password=" + password + ", birth=" + birth + "]";
	}  
    
      
  
}  


应用场景1:
@Controller
public class UserController {  
    /** 
     * 登陆 
     * @param request 
     * @param response 
     * @return 
     */  
	@RequestMapping(value = "/login.do", method = RequestMethod.POST)
    public ModelAndView login(HttpServletRequest request, HttpServletResponse response, User user){
}
}


应用场景2:
public class UserController extends MultiActionController{  
    /** 
     * 登陆 
     * @param request 
     * @param response 
     * @return 
     */  
    public ModelAndView login(HttpServletRequest request, HttpServletResponse response, User user){

    } 
}
}









1. @InitBinder--initBinder
spring 3.2.2(与标题无关)

UserController extends MultiActionController
@InitBinder
    public void initBinder(WebDataBinder binder) {
    	System.out.println("@InitBinder-initBinder");
    	DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
        CustomDateEditor dateEditor = new CustomDateEditor(df, true); 
        binder.registerCustomEditor(Date.class, dateEditor);
    }


springmvc配置:
<context:component-scan base-package="com.byron.controller" />
<mvc:annotation-driven />


如果没有配置<mvc:annotation-driven />,@InitBinder将不起作用。此时对于继不继承MultiActionController已经没有意义,请求地址是通过注解的方式注册的


为什么@InitBinder需配合<mvc:annotation-driven />才起作用?




2.@Override--initBinder
UserController extends MultiActionController
@Override
    protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
    	/*binder.setAutoGrowNestedPaths(true);  
        binder.setAutoGrowCollectionLimit(1024);  */
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");  
        CustomDateEditor dateEditor = new CustomDateEditor(df, true); 
        binder.registerCustomEditor(Date.class, dateEditor);
    	System.out.println("hi, i am initBinder");
    }



springmvc配置:
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
           <property name="mappings">
                  <props>
                         <prop key="/login.do">loginAction</prop>
                  </props>
           </property>
    </bean>
    
   
     <bean id="loginAction" class="com.byron.controller.UserController">
               <property name="methodNameResolver">
                        <ref local="methodNameResolver"/>
          </property>
     </bean> 
<bean id="methodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">	
        <property name="paramName"><value>method</value></property>   
        <property name="defaultMethodName"><value>execute</value></property>
	</bean>


此时,springmvc的映射器配置为SimpleUrlHandlerMapping,并且继承MultiActionController,覆盖父类中initBinder的定义,如:
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
		if (this.webBindingInitializer != null) {
			this.webBindingInitializer.initBinder(binder, new ServletWebRequest(request));
		}
	}




3.自定义webBindingInitializer
这个与第二个有异曲同工之处,只不过方式不一样,相比于第二种的继承,这种方式更符合spring的开闭原则

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">  
        <property name="cacheSeconds" value="0"/>  
        <property name="webBindingInitializer">  
            <bean class="com.byron.controller.util.MyWebBindingInitializer"/>  
        </property>  
    </bean>
     
    <context:component-scan base-package="com.byron.controller" />
    <mvc:annotation-driven />






<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
           <property name="mappings">
                  <props>
                         <prop key="/login.do">loginAction</prop>
                  </props>
           </property>
    </bean>
    
   
     <bean id="loginAction" class="com.byron.controller.UserController">
               <property name="methodNameResolver">
                        <ref local="methodNameResolver"/>
          </property>
     </bean>     
     
     <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">  
        <property name="cacheSeconds" value="0"/>  
        <property name="webBindingInitializer">  
            <bean class="com.byron.controller.util.MyWebBindingInitializer"/>  
        </property>  
    </bean> 




还留下其它的一些扩展点如;
validators

对于以上1,3种方式,handlerMapping是通过注解配置,而第二种方式是通过xml配置实现映射器




4.在model中设置注解@DateTimeFormat(pattern = "yyyy-MM-dd")

spring配置
<context:component-scan base-package="com.byron.controller" />
    <mvc:annotation-driven />


<mvc:annotation-driven />会注册DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter,由这2个类来处理@DateTimeFormat。

相当于:
<mvc:annotation-driven conversion-service="conversionService"/>
	
	<bean id="conversionService"
		class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
		<property name="converters">
			<list>
				<!-- <bean class="com.*.StringToBeanConverter" /> -->
			</list>
		</property>
	</bean>




上述各种转换方法中,其实有时是相通的,一开始都是用xml去配置,只不过后来优化,直接使用注解配置的方式,代替了纯xml中配置各种bean的过程。
这种简化也是spring迭代升级中的一种趋势,用注解代替xml。但是对于开发者来说,一定程度上,也屏蔽的底端的实现和原理









附:源码分析
MultiActionController#bind
protected void bind(HttpServletRequest request, Object command) throws Exception {
		logger.debug("Binding request parameters onto MultiActionController command");
		ServletRequestDataBinder binder = createBinder(request, command);
		binder.bind(request);
		if (this.validators != null) {
			for (Validator validator : this.validators) {
				if (validator.supports(command.getClass())) {
					ValidationUtils.invokeValidator(validator, command, binder.getBindingResult());
				}
			}
		}
		binder.closeNoCatch();
	}

1.
binder.bind(request); 跟踪进去,调用PropertyEditor,conversionService中类型转换

2.
绑定数据结束后,调用校验validators




BeanWrapperImpl#convertIfNecessary
private Object convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class<?> requiredType,
			TypeDescriptor td) throws TypeMismatchException {
		try {
			return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);
		}
		catch (ConverterNotFoundException ex) {
			PropertyChangeEvent pce =
					new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
			throw new ConversionNotSupportedException(pce, td.getType(), ex);
		}
		catch (ConversionException ex) {
			PropertyChangeEvent pce =
					new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
			throw new TypeMismatchException(pce, requiredType, ex);
		}
		catch (IllegalStateException ex) {
			PropertyChangeEvent pce =
					new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
			throw new ConversionNotSupportedException(pce, requiredType, ex);
		}
		catch (IllegalArgumentException ex) {
			PropertyChangeEvent pce =
					new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue);
			throw new TypeMismatchException(pce, requiredType, ex);
		}
	}


this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);



TypeConverterDelegate#convertIfNecessary
public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue,
			Class<T> requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException {

		Object convertedValue = newValue;

		// Custom editor for this type?
		PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

		ConversionFailedException firstAttemptEx = null;

		// No custom editor but custom ConversionService specified?
		ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
		if (editor == null && conversionService != null && convertedValue != null && typeDescriptor != null) {
			TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
			TypeDescriptor targetTypeDesc = typeDescriptor;
			if (conversionService.canConvert(sourceTypeDesc, targetTypeDesc)) {
				try {
					return (T) conversionService.convert(convertedValue, sourceTypeDesc, targetTypeDesc);
				}
				catch (ConversionFailedException ex) {
					// fallback to default conversion logic below
					firstAttemptEx = ex;
				}
			}
		}

		// Value not of required type?
		if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
			if (requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) {
				TypeDescriptor elementType = typeDescriptor.getElementTypeDescriptor();
				if (elementType != null && Enum.class.isAssignableFrom(elementType.getType())) {
					convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
				}
			}
			if (editor == null) {
				editor = findDefaultEditor(requiredType);
			}
			convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
		}

	}


PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

ConversionService conversionService = this.propertyEditorRegistry.getConversionService();

首先判断editor是否为空,不为空就用editor进行类型转换,否则如果conversionService不为空,则使用conversionService进行类型转换

TypeConverterDelegate的构造时,传入BeanWrapperImpl实例,即propertyEditorRegistry



参考:
Validation, Data Binding, and Type Conversion
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html

解决java - spring mvc one init binder for all controllers
http://www.itkeyword.com/doc/5230440400468034x402/spring-mvc-one-init-binder-for-all-controllers

https://stackoverflow.com/questions/12486512/spring-mvc-initbinder-is-not-called-when-processing-ajax-request
分享到:
评论

相关推荐

    rubyinstaller-3.2.2-1-x64.exe

    rubyinstaller-3.2.2-1-x64

    commons-collections-3.2.2-bin.zip

    本次分享的是Collections库的3.2.2版本,即"commons-collections-3.2.2-bin.zip",这是一个二进制发行版,包含了可直接使用的jar文件。 Apache Commons Collections的核心在于其对Java内置集合类的增强和补充。它...

    azarus-2.2.6-fpc-3.2.2-win64

    azarus-2.2.6-fpc-3.2.2-win64

    commons-collections-3.2.2-API文档-中文版.zip

    赠送原API文档:commons-collections-3.2.2-javadoc.jar; 赠送源代码:commons-collections-3.2.2-sources.jar; 赠送Maven依赖信息文件:commons-collections-3.2.2.pom; 包含翻译后的API文档:commons-...

    commons-collections-3.2.2-bin.tar包

    `commons-collections-3.2.2-bin.tar`是一个压缩包,它包含了Apache Commons Collections库的3.2.2版本。这个库是Java编程语言中一个非常重要的工具集,专门用于处理集合框架,如列表、映射、集合等。Apache Commons...

    commons-collections-3.2.2-

    这个"commons-collections-3.2.2-"版本是该库的一个特定发行版,主要用于解决WebLogic服务器上的反序列化漏洞问题。 在Java编程中,集合框架是处理对象数组的重要组成部分。Apache Commons Collections扩展了Java...

    matplotlib-3.2.2-cp38-cp38-win_amd64

    matplotlib-3.2.2-cp38-cp38-win_amd64

    zendoptimizer-3.2.2-windows-i386.exe

    zendoptimizer-3.2.2-windows-i386.exe

    restclient-ui-3.2.2-jar-with-dependencies

    "restclient-ui-3.2.2-jar-with-dependencies" 是一个针对RESTful API测试的用户界面工具,它的主要功能是帮助开发者和测试人员便捷地进行接口测试。REST(Representational State Transfer)是一种网络应用程序的...

    commons-collections-3.2.2-API文档-中英对照版.zip

    赠送原API文档:commons-collections-3.2.2-javadoc.jar; 赠送源代码:commons-collections-3.2.2-sources.jar; 赠送Maven依赖信息文件:commons-collections-3.2.2.pom; 包含翻译后的API文档:commons-...

    spark-3.2.2-bin-3.0.0-cdh6.3.2

    内容概要:由于cdh6.3.2的spark版本为2.4.0,并且spark-sql被阉割,现基于cdh6.3.2,scala2.12.0,java1.8,maven3.6.3,,对spark-3.2.2源码进行编译 应用:该资源可用于cdh6.3.2集群配置spark客户端,用于spark-sql

    mybatis-3.2.2-src.rar 源码

    这个源码包"mybatis-3.2.2-src.rar"包含了完整的Mybatis 3.2.2版本的源代码,对开发者来说是一份宝贵的学习资源。 源码分析可以从以下几个主要方面展开: 1. **架构设计**:Mybatis 的核心组件包括...

    360 N6-TWRP-3.2.2-0 卡刷包

    《360 N6-TWRP-3.2.2-0 卡刷包详解及应用》 在智能手机世界中,刷机是一项常见的操作,它可以让用户根据个人喜好定制手机系统,提升性能或修复问题。360 N6-TWRP-3.2.2-0 卡刷包是针对360 N6手机的一款高级定制工具...

    matplotlib-3.2.2-cp37-cp37m-win32

    matplotlib-3.2.2-cp37-cp37m-win32

    matplotlib-3.2.2-cp37-cp37m-win_amd64

    matplotlib-3.2.2-cp37-cp37m-win_amd64

    spring-framework-3.2.2-dis jar包

    《深入解析Spring Framework 3.2.2》 Spring Framework是Java开发领域中不可或缺的开源框架,它以其灵活、强大的特性以及对IoC(Inversion of Control)和AOP(Aspect Oriented Programming)的支持,成为了企业级...

    mybatis-3.2.2-sources.jar

    不需要解压,简单粗暴; 使用步骤: 选择External location-----&gt;external File------&gt;mybatis-3.2.2-sources.jar(你本地资源),等myeclipse自动加载就直接可以看到源码了

    Python库 | qt6_applications-6.0.3.2.2-py3-none-win_amd64.whl

    资源分类:Python库所属语言:Python资源全名:qt6_applications-6.0.3.2.2-py3-none-win_amd64.whl资源来源:官方安装方法:https://lanzao.blog.csdn.net/article/details/101784059

    python-cherrypy-3.2.2-4.el7.noarch.rpm

    官方离线安装包,亲测可用。使用rpm -ivh [rpm完整包名] 进行安装

    360 N6 TWRP-Recovery3.2.2-0-9008线刷包

    360 N6 TWRP_Recovery3.2.2-0_9008线刷包 360 N6 手机要先进到高通9008模式才能刷,低版本能通过USB工程线进9008模式,升级了高版本h的手机只能开手机后盖短接才能进9008模式了。

Global site tag (gtag.js) - Google Analytics