先上原理圖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。