Spring扩展自定义属性编辑器

本文学习自马士兵MAC课程-源码五班

需求

自定义一个 Address 类,包含三个字段 province、city、town,给 Address 用字符串进行赋值,字符串用下划线分割 province、city、town,最终能使 BeanFactory 成功解析。

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="customer" class="com.eitan.editor.entity.Customer">
<property name="name" value="zs"></property>
<property name="address" value="福建省_福州市_闽侯县"></property>
</bean>
</beans>

实现步骤

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Customer {
private String name;

private Address address;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Address getAddress() {
return address;
}

public void setAddress(Address address) {
this.address = address;
}

@Override
public String toString() {
return "Customer{" +
"name='" + name + '\'' +
", address=" + address +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Address {
private String province;

private String city;

private String town;

public String getProvince() {
return province;
}

public void setProvince(String province) {
this.province = province;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getTown() {
return town;
}

public void setTown(String town) {
this.town = town;
}

@Override
public String toString() {
return "Address{" +
"province='" + province + '\'' +
", city='" + city + '\'' +
", town='" + town + '\'' +
'}';
}
}

自定义一个继承 PropertyEditorSupport 的子类

1
2
3
4
5
6
7
8
9
10
11
public class AddressPropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
String[] s = text.split("_");
Address address = new Address();
address.setProvince(s[0]);
address.setCity(s[1]);
address.setTown(s[2]);
this.setValue(address);
}
}

该方法用于编写处理属性的具体逻辑

自定义一个实现 PropertyEditorRegistrar 接口的子类

1
2
3
4
5
6
public class AddressPropertyEditorRegistrar implements PropertyEditorRegistrar {
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(Address.class,new AddressPropertyEditor());
}
}

对 Spring 中注册一个 CustomEditorConfigurer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="customer" class="com.eitan.editor.entity.Customer">
<property name="name" value="zs"></property>
<property name="address" value="福建省_福州市_闽侯县"></property>
</bean>
<!-- 方式一 -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<bean class="com.eitan.editor.registrar.AddressPropertyEditorRegistrar"></bean>
</list>
</property>
</bean>
<!-- 方式二 -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="com.eitan.editor.entity.Address">
<value>com.eitan.editor.editor.AddressPropertyEditor</value>
</entry>
</map>
</property>
</bean>
</beans>

注册 CustomEditorConfigurer 主要是为了将 PropertyEditor 注册进 BeanFactory

注:PropertyEditor 的实现类是 PropertyEditorSupport

源码解析

CustomEditorConfigurer 是怎么将 PropertyEditorRegistrar 注册进 BeanFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
ublic class CustomEditorConfigurer implements BeanFactoryPostProcessor, Ordered {

protected final Log logger = LogFactory.getLog(getClass());

private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered

@Nullable
private PropertyEditorRegistrar[] propertyEditorRegistrars;

@Nullable
private Map<Class<?>, Class<? extends PropertyEditor>> customEditors;


public void setOrder(int order) {
this.order = order;
}

@Override
public int getOrder() {
return this.order;
}

/**
* Specify the {@link PropertyEditorRegistrar PropertyEditorRegistrars}
* to apply to beans defined within the current application context.
* <p>This allows for sharing {@code PropertyEditorRegistrars} with
* {@link org.springframework.validation.DataBinder DataBinders}, etc.
* Furthermore, it avoids the need for synchronization on custom editors:
* A {@code PropertyEditorRegistrar} will always create fresh editor
* instances for each bean creation attempt.
* @see ConfigurableListableBeanFactory#addPropertyEditorRegistrar
*/
public void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {
this.propertyEditorRegistrars = propertyEditorRegistrars;
}

/**
* Specify the custom editors to register via a {@link Map}, using the
* class name of the required type as the key and the class name of the
* associated {@link PropertyEditor} as value.
* @see ConfigurableListableBeanFactory#registerCustomEditor
*/
public void setCustomEditors(Map<Class<?>, Class<? extends PropertyEditor>> customEditors) {
this.customEditors = customEditors;
}


@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (this.propertyEditorRegistrars != null) {
for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar);
}
}
if (this.customEditors != null) {
this.customEditors.forEach(beanFactory::registerCustomEditor);
}
}

}
  1. 从源码可知:CustomEditorConfigurer 实现了 BeanFactoryPostProcessor,因此在 BeanFactory 加载完 BeanDefinition 后会调用 postProcessBeanFactory 方法
  2. 在 postProcessBeanFactory 中会将 propertyEditorRegistrar 放入 BeanFactory 的 propertyEditorRegistrars 集合(Set)中,将 customEditors 放入 BeanFactory 的 customEditors 集合(Map)中
  3. customEditors 的数据类型为: Map<Class<?>, Class<? extends PropertyEditor>>,其中 key 为要处理的类,而 value 的是对应的处理逻辑类

BeanFactory 是怎么讲 PropertyEditor 放入 BeanWrapper 中

代码在 doCreateBean 中的 createBeanInstance(beanName, mbd, args) 里,该方法最终掉了一个 instantiateBean(beanName, mbd) 返回一个 BeanWrapper:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) {
try {
Object beanInstance;
if (System.getSecurityManager() != null) {
beanInstance = AccessController.doPrivileged(
(PrivilegedAction<Object>) () -> getInstantiationStrategy().instantiate(mbd, beanName, this),
getAccessControlContext());
}
else {
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);
}
BeanWrapper bw = new BeanWrapperImpl(beanInstance);
initBeanWrapper(bw);
return bw;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
}
}

在 initBeanWrapper(bw) 中,会将 BeanFactory 的 propertyEditorRegistrars 调用 registerCustomEditors,并把 BeanFactory 的 customEditors 放入 BeanWrapper 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
protected void initBeanWrapper(BeanWrapper bw) {
bw.setConversionService(getConversionService());
registerCustomEditors(bw);
}

protected void registerCustomEditors(PropertyEditorRegistry registry) {
if (registry instanceof PropertyEditorRegistrySupport) {
((PropertyEditorRegistrySupport) registry).useConfigValueEditors();
}
if (!this.propertyEditorRegistrars.isEmpty()) {
for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) {
try {
// 调用 PropertyEditorRegistrar 的 registerCustomEditors 方法
registrar.registerCustomEditors(registry);
}
catch (BeanCreationException ex) {
Throwable rootCause = ex.getMostSpecificCause();
if (rootCause instanceof BeanCurrentlyInCreationException) {
BeanCreationException bce = (BeanCreationException) rootCause;
String bceBeanName = bce.getBeanName();
if (bceBeanName != null && isCurrentlyInCreation(bceBeanName)) {
if (logger.isDebugEnabled()) {
logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() +
"] failed because it tried to obtain currently created bean '" +
ex.getBeanName() + "': " + ex.getMessage());
}
onSuppressedException(ex);
continue;
}
}
throw ex;
}
}
}
if (!this.customEditors.isEmpty()) {
// 直接将 customEditors 注册到 BeanWrapper 中
this.customEditors.forEach((requiredType, editorClass) ->
registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass)));
}
}

BeanWrapper 是怎么编辑属性的

即如何把 福建省_福州市_闽侯县 转化为对象 Address

该过程发生在 doCreateBean 的 populateBean(beanName, mbd, instanceWrapper) 方法中,该方法是对对bean的属性进行填充,将各个属性值注入

继续 DeBug 源码,找到 applyPropertyValues 方法里的 *valueResolver.resolveValueIfNecessary(pv, originalValue)*,最终找到 typeConverter.convertIfNecessary(valueObject, resolvedTargetType)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {

// Custom editor for this type?
// 找到对应的 PropertyEditor
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

ConversionFailedException conversionAttemptEx = null;

// No custom editor but custom ConversionService specified?
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
// fallback to default conversion logic below
conversionAttemptEx = ex;
}
}
}

Object convertedValue = newValue;

// Value not of required type?
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
convertedValue instanceof String) {
TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
if (elementTypeDesc != null) {
Class<?> elementType = elementTypeDesc.getType();
if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
}
}
if (editor == null) {
editor = findDefaultEditor(requiredType);
}
// 在此进行转化
convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
}

......

return (T) convertedValue;
}

进入 doConvertValue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
private Object doConvertValue(@Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<?> requiredType, @Nullable PropertyEditor editor) {

Object convertedValue = newValue;

if (editor != null && !(convertedValue instanceof String)) {
// Not a String -> use PropertyEditor's setValue.
// With standard PropertyEditors, this will return the very same object;
// we just want to allow special PropertyEditors to override setValue
// for type conversion from non-String values to the required type.
try {
editor.setValue(convertedValue);
Object newConvertedValue = editor.getValue();
if (newConvertedValue != convertedValue) {
convertedValue = newConvertedValue;
// Reset PropertyEditor: It already did a proper conversion.
// Don't use it again for a setAsText call.
editor = null;
}
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex);
}
// Swallow and proceed.
}
}

Object returnValue = convertedValue;

if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String[]) {
// Convert String array to a comma-separated String.
// Only applies if no PropertyEditor converted the String array before.
// The CSV String will be passed into a PropertyEditor's setAsText method, if any.
if (logger.isTraceEnabled()) {
logger.trace("Converting String array to comma-delimited String [" + convertedValue + "]");
}
convertedValue = StringUtils.arrayToCommaDelimitedString((String[]) convertedValue);
}

if (convertedValue instanceof String) {
if (editor != null) {
// Use PropertyEditor's setAsText in case of a String value.
if (logger.isTraceEnabled()) {
logger.trace("Converting String to [" + requiredType + "] using property editor [" + editor + "]");
}
String newTextValue = (String) convertedValue;
// 最终做事的方法
return doConvertTextValue(oldValue, newTextValue, editor);
}
else if (String.class == requiredType) {
returnValue = convertedValue;
}
}

return returnValue;
}

追踪 doConvertTextValue

1
2
3
4
5
6
7
8
9
10
11
12
13
private Object doConvertTextValue(@Nullable Object oldValue, String newTextValue, PropertyEditor editor) {
try {
editor.setValue(oldValue);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex);
}
// Swallow and proceed.
}
editor.setAsText(newTextValue);
return editor.getValue();
}

可以看到,最终将调用我们自定义继承 PropertyEditorSupport 的子类的 setAstext() 方法,并且通过 editor.getValue() 获取处理后的值

总结

  1. CustomEditorConfigurer 会在 BeanFactoryPostProcessor 调用 postProcessBeanFactory 时将 PropertyEditor 和 PropertyEditorRegistrar 注册进 BeanFactory
  2. 在 doCreateBean 时,BeanFactory 会在初始化 BeanWrapper 时将 PropertyEditor 和 PropertyEditorRegistrar 放入 BeanWrapper
  3. 最终在调用 populateBean 是会遍历各个属性,找到其类对应的 PropertyEditor
  4. 再通过调用 PropertyEditor 的 setAsText(newTextValue) 方法实现自定义处理属性,通过 editor.getValue() 获取处理后的值