Spring source code learning series 3.2.2-How to bind String to Date

In the development of springmvc, it is often necessary to convert the interface date data (String) into the date type (Date) data of the background model.

Different versions of springmvc may have different processing methods, or the higher the version, the more optimized the processing method. Several ways to

experiment

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 + "]";
	}  
    
      
  
}  


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


Application Scenario 2:
public class UserController extends MultiActionController{  
    /**
     * login
     * @param request
     * @param response
     * @return
     */  
    public ModelAndView login(HttpServletRequest request, HttpServletResponse response, User user){

    }
}
}









1. @InitBinder--initBinder
spring 3.2.2 (not related to title)

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 configuration:
<context:component-scan base-package="com.byron.controller" />
<mvc:annotation-driven />


@InitBinder will not work if <mvc:annotation-driven /> is not configured. At this point, it is meaningless to inherit MultiActionController or not. The request address is registered through annotation.


Why does @InitBinder need to work with <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 configuration:
<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>


At this point, springmvc's mapper is configured as SimpleUrlHandlerMapping, and inherits MultiActionController, overriding the definition of initBinder in the parent class, such as:
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
		if (this.webBindingInitializer != null) {
			this.webBindingInitializer.initBinder(binder, new ServletWebRequest(request));
		}
	}




3. Custom webBindingInitializer
is similar to the second one, but in a different way. Compared with the second inheritance, this method is more in line with spring's open-close principle

<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 />



or


<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>




There are also some other extension points such as;
validators

For the above 1 and 3 methods, handlerMapping is configured through annotations, and the second method is to implement mappers through xml configuration




4. Set the annotation @DateTimeFormat(pattern = " yyyy-MM-dd")

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


<mvc:annotation-driven /> will register DefaultAnnotationHandlerMapping and AnnotationMethodHandlerAdapter, and these two classes will handle @DateTimeFormat.

Equivalent to:
<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>




In fact, the above conversion methods are sometimes the same. At the beginning, they were configured with xml, but they were optimized later, and the method of annotation configuration was directly used instead of the process of configuring various beans in pure xml.
This simplification is also a trend in spring iterative upgrades, using annotations to replace xml. But for developers, to a certain extent, the bottom-end implementation and principle of shielding are also









attached: Source Code Analysis
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); Track it in, call PropertyEditor, type conversion in conversionService

2.
After binding data, call validation 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();

First judge whether the editor is empty, if it is not empty, use the editor for type conversion, otherwise if the conversionService is not empty, then When using conversionService for type conversion

TypeConverterDelegate construction, pass in a BeanWrapperImpl instance, that is, propertyEditorRegistry



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

resolve 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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326356005&siteId=291194637