不同版本的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