先上原理图java
在咱们学习servlet的时候咱们知道有一个方法叫作:request.getParameter("paramName"),它返回的是一个String类型,可是若是一切都是这样子咱们开发程序的时候就会显得特别麻烦,由于java引入了对象的概念,咱们每每把一个表单的数据封装在一个业务中的一个javaBean对象里面,javaBean对象里面的属性会有不一样类型,如:int,double,byte等等。因此须要几个东西来把String转化成服务端真正的类型,为了解决这个问题,springmvc引入了WebDataBinder。web
WebDataBinder不须要咱们本身去新建,WebDataBinder继承了spring-context中的DataBinder,DataBinder中定义了属性编辑器注册的方法spring
源码1.1编程
@Override //针对某个类型 public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) { getPropertyEditorRegistry().registerCustomEditor(requiredType, propertyEditor); } @Override //针对某个属性 public void registerCustomEditor(Class<?> requiredType, String field, PropertyEditor propertyEditor) { getPropertyEditorRegistry().registerCustomEditor(requiredType, field, propertyEditor); }
DataBinder还有个很是重要的做用就是用来作数据验证(JSR-303验证框架) 这边就不作展开了安全
spring-bean已经给咱们提供了一些经常使用的属性编辑器,在org.springframework.beans.propertyeditors包下如图图1.1所示:mvc
图1.1app
咱们先开看一段普通bean参数解析器ModelAttributeMethodProcessor中的一段代码,在解析参数的时候,都会由一个工厂类WebDataBinderFactory来建立一个WebDataBinder,而这个工厂类WebDataBinderFactory是从哪里来的呢?请看遨游springmvc之HandlerAdapter源码2.2.3,WebDataBinderFactory在其实现类InitBinderDataBinderFactory中实现了初始化如源码2.1.2,在 WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);这句代码执行的时候,它首先会执行controller加了@initBinder的方法(binderMethods),经过for循环咱们知道binderMethods能够是多个,因此咱们能够controller加入多个绑定的方法。框架
源码2.1.1编辑器
@Override public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { String name = ModelFactory.getNameForParameter(parameter); Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest)); if (!mavContainer.isBindingDisabled(name)) { ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null && !ann.binding()) { mavContainer.setBindingDisabled(name); } } WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { //绑定参数 bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } // Add resolved attribute and BindingResult at the end of the model Map<String, Object> bindingResultModel = binder.getBindingResult().getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); }
源码2.1.2ide
/** * Initialize a WebDataBinder with {@code @InitBinder} methods. * If the {@code @InitBinder} annotation specifies attributes names, it is * invoked only if the names include the target object name. * @throws Exception if one of the invoked @{@link InitBinder} methods fail. */ @Override public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception { for (InvocableHandlerMethod binderMethod : this.binderMethods) { if (isBinderMethodApplicable(binderMethod, binder)) { //@InitBinder的方法必须是void Object returnValue = binderMethod.invokeForRequest(request, null, binder); if (returnValue != null) { throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod); } } } }
全部的binder最终都会执行bind方法,它最终将会把绑定出错的信息也给返回到bindResult也就是咱们的数据验证
源码2.1.3
/** * Bind the parameters of the given request to this binder's target, * also binding multipart files in case of a multipart request. * <p>This call can create field errors, representing basic binding * errors like a required field (code "required"), or type mismatch * between value and bean property (code "typeMismatch"). * <p>Multipart files are bound via their parameter name, just like normal * HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property, * invoking a "setUploadedFile" setter method. * <p>The type of the target property for a multipart file can be MultipartFile, * byte[], or String. The latter two receive the contents of the uploaded file; * all metadata like original file name, content type, etc are lost in those cases. * @param request request with parameters to bind (can be multipart) * @see org.springframework.web.multipart.MultipartHttpServletRequest * @see org.springframework.web.multipart.MultipartFile * @see #bind(org.springframework.beans.PropertyValues) */ public void bind(ServletRequest request) { MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request); MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class); if (multipartRequest != null) { bindMultipart(multipartRequest.getMultiFileMap(), mpvs); } addBindValues(mpvs, request); doBind(mpvs); }
上面讲了一堆关于WebDataBinder的东西,咱们再来学习下WebDataBinder中注册的属性编辑器PropertyEditor
PropertyEditor是jdk下java.beans下的用于图形用户接口(GUI)的一个接口,spring并非直接使用PropertyEditor而是用PropertyEditorSupport来实现属性的转换,由于PropertyEditorSupport已经提供了一些方法的默认实现,而且屏蔽了一些如paintValue的方法。
那么
spring是如何将
PropertyEditor嵌入到上线文当中呢?
它是经过PropertyEditorRegistry接口,而PropertyEditorRegistrySupport则实现了PropertyEditorRegistry,而且加入了图1.1中的一些默认属性编辑器的支持
private void createDefaultEditors() { this.defaultEditors = new HashMap<Class<?>, PropertyEditor>(64); // Simple editors, without parameterization capabilities. // The JDK does not contain a default editor for any of these target types. this.defaultEditors.put(Charset.class, new CharsetEditor()); this.defaultEditors.put(Class.class, new ClassEditor()); this.defaultEditors.put(Class[].class, new ClassArrayEditor()); this.defaultEditors.put(Currency.class, new CurrencyEditor()); this.defaultEditors.put(File.class, new FileEditor()); this.defaultEditors.put(InputStream.class, new InputStreamEditor()); this.defaultEditors.put(InputSource.class, new InputSourceEditor()); this.defaultEditors.put(Locale.class, new LocaleEditor()); this.defaultEditors.put(Pattern.class, new PatternEditor()); this.defaultEditors.put(Properties.class, new PropertiesEditor()); this.defaultEditors.put(Reader.class, new ReaderEditor()); this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor()); this.defaultEditors.put(TimeZone.class, new TimeZoneEditor()); this.defaultEditors.put(URI.class, new URIEditor()); this.defaultEditors.put(URL.class, new URLEditor()); this.defaultEditors.put(UUID.class, new UUIDEditor()); if (zoneIdClass != null) { this.defaultEditors.put(zoneIdClass, new ZoneIdEditor()); } // Default instances of collection editors. // Can be overridden by registering custom instances of those as custom editors. this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class)); this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class)); this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class)); this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class)); this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class)); // Default editors for primitive arrays. this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor()); this.defaultEditors.put(char[].class, new CharArrayPropertyEditor()); // The JDK does not contain a default editor for char! this.defaultEditors.put(char.class, new CharacterEditor(false)); this.defaultEditors.put(Character.class, new CharacterEditor(true)); // Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor. this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false)); this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true)); // The JDK does not contain default editors for number wrapper types! // Override JDK primitive number editors with our own CustomNumberEditor. this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false)); this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true)); this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false)); this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true)); this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false)); this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true)); this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false)); this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true)); this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false)); this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true)); this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false)); this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true)); this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true)); this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true)); // Only register config value editors if explicitly requested. if (this.configValueEditorsActive) { StringArrayPropertyEditor sae = new StringArrayPropertyEditor(); this.defaultEditors.put(String[].class, sae); this.defaultEditors.put(short[].class, sae); this.defaultEditors.put(int[].class, sae); this.defaultEditors.put(long[].class, sae); } }
通常状况下PropertyEditorRegistry是由它的子类BeanWrapperImpl或者DataBinder来实现
BeanWrapperImpl实现了javaBean规范中的setter和getter方法,并且它也拥有了父类PropertyEditorSupport中的属性编辑器
setter、getter方法能够以下实现如:
Person p = new Person(); BeanWrapperImpl bw = new BeanWrapperImpl(p); bw.setPropertyValue("name","ws"); System.out.println(p.getName());//ws
除了一些基础的属性编辑器,咱们有时还须要加入一些特制的属性编辑器。spring提供一个类将第三方的属性编辑器专门注入到spring上下文,它就是CustomEditorConfigurer。
customEditors是一个Map,key是Class,而value就是
PropertyEditor
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { if (this.propertyEditorRegistrars != null) { for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) { beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar); } } if (this.customEditors != null) { for (Map.Entry<Class<?>, Class<? extends PropertyEditor>> entry : this.customEditors.entrySet()) { Class<?> requiredType = entry.getKey(); Class<? extends PropertyEditor> propertyEditorClass = entry.getValue(); beanFactory.registerCustomEditor(requiredType, propertyEditorClass); } } }
因此咱们能够经过配置注入自定义属性编辑器
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <entry key="com.kings.template.entity.Telephone" value="com.kings.template.mvc.propeditor.TelephonePropertyEditor"/> </map> </property> </bean>
以上配置是普通spring项目使用PropertyEditor的例子,那么Springmvc是如何实现的呢?
还记得上面的WebDataBinder吗?它是DataBind的子类,而DataBind则是PropertyEditorRegistry的子类,因此WebDataBinder具有注册属性编辑器的特性。
springmvc利用WebDataBinder注册属性编辑器,在咱们控制器中加入以下代码,就能使该控制器得到属性转化的功能
@InitBinder public void initBinder(WebDataBinder binder){ binder.registerCustomEditor(Telephone.class,new TelephonePropertyEditor()); }
可是上面的方法只能在当前的控制器下起做用
有时候咱们但愿有一个全局的属性转换器,那么咱们就须要用到一个叫WebBindingInitializer的接口,而且在实现方法initBinder中加入自定义的PropertyEditor
public class MyWebBindingInitializer implements WebBindingInitializer { @Override public void initBinder(WebDataBinder binder, WebRequest request) { binder.registerCustomEditor(Telephone.class,new TelephonePropertyEditor()); } }
加入了自定义的WebBindingInitializer咱们须要配置来启用它
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="webBindingInitializer"> <bean class="com.kings.template.mvc.MyWebBindingInitializer"/> </property> </bean>
可是请注意
若是你配置了<mvc:annotation-driven/>,那么上面的配置必须放在<mvc:annotation-driven/>前
若是你不用<mvc:annotation-driven/>,那么能够这么配置
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="webBindingInitializer"> <bean class="com.kings.template.mvc.MyWebBindingInitializer"/> </property> </bean>
PropertyEditor接口方法以下
public interface PropertyEditor { void setValue(Object value); Object getValue(); boolean isPaintable(); void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box); String getJavaInitializationString(); String getAsText(); void setAsText(String text) throws java.lang.IllegalArgumentException; String[] getTags(); java.awt.Component getCustomEditor(); boolean supportsCustomEditor(); void addPropertyChangeListener(PropertyChangeListener listener); void removePropertyChangeListener(PropertyChangeListener listener); }
咱们发如今PropertyEditor中有一大堆方法,可是咱们不须要"虚",由于咱们只须要继承PropertyEditorSupport,而后主要关注如下几个方法
void setValue(Object value);//设置属性值 Object getValue();//获取属性值 String getAsText(); //把属性值转换成String void setAsText(String text);//把String转换成属性值
来一个简单的例子
实体类
加入了一个叫Telephone的类
@Data public class Person { private String name; private Telephone telephone; } @Data @AllArgsConstructor @NoArgsConstructor public class Telephone { private String areaCode; private String phoneNumber; @Override public String toString() { return areaCode+"-"+phoneNumber; } }
属性编辑器
public class TelephonePropertyEditor extends PropertyEditorSupport{ @Override public void setAsText(String text) throws IllegalArgumentException { if(text.matches("\\d{3,4}-\\d{7,8}")){ String[] telephoneArray = text.split("-"); setValue(new Telephone(telephoneArray[0],telephoneArray[1])); } else { throw new IllegalArgumentException("错误的电话号码"); } } @Override public String getAsText() { return ((Telephone)getValue()).toString(); } }
控制器
TelephonePropertyEditor将url上telephone=010-12345678的参数转化成Telephone实体
//注册属性编辑器 @InitBinder public void initBinder(WebDataBinder binder){ binder.registerCustomEditor(Telephone.class,new TelephonePropertyEditor()); } @RequestMapping (value="/bind/1",method= RequestMethod.GET) @ResponseBody public String detail(Person p) { return p.getTelephone().toString(); }
花了这么多文字修饰了PropertyEditor,结果它是spring3以前的玩意,好尴尬!竟然把缺点放在最后,让大家看完了一整篇!整的想喷垃圾话
由于它有以下缺点:
(一、PropertyEditor被设计为只能String<——>Object之间转换,不能任意对象类型<——>任意类型,如咱们常见的Long时间戳到Date类型的转换是办不到的;
(二、PropertyEditor是线程不安全的,也就是有状态的,所以每次使用时都须要建立一个,不可重用;
(三、PropertyEditor不是强类型的,setValue(Object)能够接受任意类型,所以须要咱们本身判断类型是否兼容;
(四、须要本身编程实现验证,Spring3支持更棒的注解验证支持;
(五、在使用SpEL表达式语言或DataBinder时,只能进行String<--->Object之间的类型转换;
(六、不支持细粒度的类型转换/格式化,如UserModel的registerDate须要转换/格式化相似“2012-05-01”的数据,而OrderModel的orderDate须要转换/格式化相似“2012-05-01 15:11:13”的数据,由于你们都为java.util.Date类型,所以不太容易进行细粒度转换/格式化
spring3以后出了更高级的类型换换系统Converter。