为什么80%的码农都做不了架构师?>>>
1.前言
先上原理图
在我们学习servlet的时候我们知道有一个方法叫做:request.getParameter("paramName"),它返回的是一个String类型,但是如果一切都是这样子我们开发程序的时候就会显得特别麻烦,因为java引入了对象的概念,我们往往把一个表单的数据封装在一个业务中的一个javaBean对象里面,javaBean对象里面的属性会有不同类型,如:int,double,byte等等。所以需要几个东西来把String转化成服务端真正的类型,为了解决这个问题,springmvc引入了WebDataBinder。
WebDataBinder不需要我们自己去新建,WebDataBinder继承了spring-context中的DataBinder,DataBinder中定义了属性编辑器注册的方法
源码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所示:
图1.1
2.WebDataBinder源码解剖
2.1 源码解刨
我们先开看一段普通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 bindingResultModel = binder.getBindingResult().getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
源码2.1.2
/**
* 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.
* 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").
*
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.
*
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);
}
3.PropertyEditor原理
上面讲了一堆关于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, 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 extends PropertyEditor>> entry : this.customEditors.entrySet()) {
Class> requiredType = entry.getKey();
Class extends PropertyEditor> propertyEditorClass = entry.getValue();
beanFactory.registerCustomEditor(requiredType, propertyEditorClass);
}
}
}
所以我们可以通过配置注入自定义属性编辑器
以上配置是普通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我们需要配置来启用它
但是请注意
如果你配置了
如果你不用
4.实现自定义PropertyEditor
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();
}
5.总结
花了这么多文字修饰了PropertyEditor,结果它是spring3之前的玩意,好尴尬!居然把缺点放在最后,让你们看完了一整篇!整的想喷垃圾话
因为它有如下缺点:
(1、PropertyEditor被设计为只能String<——>Object之间转换,不能任意对象类型<——>任意类型,如我们常见的Long时间戳到Date类型的转换是办不到的;
(2、PropertyEditor是线程不安全的,也就是有状态的,因此每次使用时都需要创建一个,不可重用;
(3、PropertyEditor不是强类型的,setValue(Object)可以接受任意类型,因此需要我们自己判断类型是否兼容;
(4、需要自己编程实现验证,Spring3支持更棒的注解验证支持;
(5、在使用SpEL表达式语言或DataBinder时,只能进行String<--->Object之间的类型转换;
(6、不支持细粒度的类型转换/格式化,如UserModel的registerDate需要转换/格式化类似“2012-05-01”的数据,而OrderModel的orderDate需要转换/格式化类似“2012-05-01 15:11:13”的数据,因为大家都为java.util.Date类型,因此不太容易进行细粒度转换/格式化
spring3之后出了更高级的类型换换系统Converter。