【原创】遨游springmvc之WebDataBinder

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<String, Object> 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.
	 * <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);
	}

 

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

 

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。

 

发现一个机智的导航?

转载于:https://my.oschina.net/kings0/blog/725415

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值