4.盘点springmvc的常用接口之PropertyEditor###

java.beans.PropertyEditor严格上来说,其实并不能算spring框架的接口,很明显看包名就明白此类是JDK自带的。是Sun所制定的一套JavaBean规范,是为IDE图形化界面准备设置属性值的接口。看接口源码上的说明:

A PropertyEditor class provides support for GUIs that want to allow users to edit a property value of a giventype.

这接口原本是使用在GUI图形程序中,允许用户给giventype设定属性值的。(不熟悉图形化界面API,不知道giventype是什么,怎么翻译)

接口说明
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);
}

不要被这么多的方法给吓到,对于我们使用者来说其实重点只有下面4个方法:

  • void setValue(Object value);设置属性值
  • Object getValue();获取属性值
  • String getAsText(); 把属性值转换成string
  • void setAsText(String text);把string转换成属性值

所以Java很机智地提供了一个适配器java.beans.PropertyEditorSupport来帮助我们实现属性值的转换,它帮助我们实现了GUI部分的接口,我们只需要重写getAsTextsetAsText的逻辑。

那么这个接口跟spring有什么鸟关系呢,这个接口有什么用呢?请看示例1。

示例1

一个由区号和号码组成的座机号码实体类:

package com.demo.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Telephone {

	/* 区号 */
	private String areaCode;
	/* 号码 */
	private String phone;

	@Override
	public String toString() {
		return areaCode + "-" + phone;
	}
}

一个人的实体(屏蔽了无关属性,只有座机-_-):

package com.demo.domain;

import org.springframework.beans.factory.annotation.Value;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class PersonEntity {

	@Value("010-12345")
	private Telephone telephone;
}

@Value是spring的注解,相当于xml配置<property name="" value=""/>里面的value,给属性赋值的。但是请注意,这里赋的是字符串。

那么问题来了,这个010-12345的字符串是怎么赋到Telephone这个类对象上的呢?类型根本转不过来。

答案就是实现转换器PropertyEditor

package com.demo.mvc.component;

import java.beans.PropertyEditorSupport;

import com.demo.domain.Telephone;

public class TelephonePropertyEditor extends PropertyEditorSupport {

	@Override
	public void setAsText(String text) throws IllegalArgumentException {
		if (text.matches("^\\d+\\-\\d+$")) {
			String[] strs = text.split("\\-");
			setValue(new Telephone(strs[0], strs[1]));
		} else {
			throw new IllegalArgumentException();
		}
	}

	@Override
	public String getAsText() {
		return ((Telephone) getValue()).toString();
	}
}

实现完了接口还得注册到spring容器让spring管理:

<bean class="com.demo.domain.PersonEntity" />

<bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
  <property name="customEditors">
    <map>
      <entry key="com.demo.domain.Telephone" value="com.demo.mvc.component.TelephonePropertyEditor" />
    </map>
  </property>
</bean>

这个CustomEditorConfigurer类顾名思义就是配置自定义的属性转换器的。把自定义的转换器统统放进这个名叫customEditors的Map结构里。用key指定Telephone,value指定TelephonePropertyEditor。

就是告诉spring,当遇到Telephone这个类型时,使用TelephonePropertyEditor把字符串转化成Telephone。

单元测试:

package com.junit;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.demo.domain.PersonEntity;
import com.demo.domain.Telephone;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = "classpath:spring/spring-context.xml")
public class PropertyEditorlJunitTest {

	@Resource
	private PersonEntity personEntity;

	@Test
	public void test() {
		Telephone telephone = personEntity.getTelephone();
		System.out.printf("区号:%s,号码:%s\n", telephone.getAreaCode(), telephone.getPhone());
	}
}

最后控制台输出结果:

区号:010,号码:12345
示例2

示例1是普通spring情况下使用PropertyEditor的例子,那么怎么用在springmvc上呢?

package com.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.demo.domain.Telephone;
import com.demo.mvc.component.TelephonePropertyEditor;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping("demo4")
public class PropertyEditorDemoController {

	@InitBinder
	public void initBinder(WebDataBinder binder) {
		binder.registerCustomEditor(Telephone.class, new TelephonePropertyEditor());
	}

	@ResponseBody
	@RequestMapping(method = RequestMethod.POST)
	public String postTelephone(@RequestParam Telephone telephone) {
		log.info(telephone.toString());
		return telephone.toString();
	}
}

使用@InitBinder注解,在WebDataBinder对象上注册转换器。

这样在访问POST http://localhost:8080/demo4 传表单数据 telephone = 010-12345

010-12345的字符串会自动转换为Telephone对象。

当然这种方式有一个缺点,该转换器只在当前的controller下有效。

想要实现全局转换,那就得实现WebBindingInitializer接口

package com.demo.mvc.component;

import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.WebRequest;

import com.demo.domain.Telephone;

public class MyWebBindingInitializer implements WebBindingInitializer {

	@Override
	public void initBinder(WebDataBinder binder, WebRequest request) {
		binder.registerCustomEditor(Telephone.class, new TelephonePropertyEditor());
	}

}

在这里可以批量注册转换器。

spring-boot注册:

package com.demo;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import com.demo.mvc.component.MyWebBindingInitializer;

@SpringBootApplication
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

	@Override
	public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
		RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();
		adapter.setWebBindingInitializer(new MyWebBindingInitializer());
		return adapter;
	}
}

xml注册:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">  
  <property name="webBindingInitializer">  
    <bean class="com.demo.mvc.component.MyWebBindingInitializer"/>  
  </property>  
</bean>

p.s.:这一章介绍了这么大篇幅的PropertyEditor的使用,最后再说一句(不要打我哦)上述介绍的方法其实都已过时-_-。

在spring3.x后,新出了一个更强大的转换器机制,Converter!

那么下一章再来讲解强大的Converter吧。