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

猜你喜欢

转载自newjava-sina-cn.iteye.com/blog/2379132