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 放入 BeanFactorypropertyEditorRegistrars 集合(Set)中,将 customEditors 放入 BeanFactorycustomEditors 集合(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) 中,会将 BeanFactorypropertyEditorRegistrars 调用 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

该过程发生在 doCreateBeanpopulateBean(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 时将 PropertyEditorPropertyEditorRegistrar 注册进 BeanFactory
  2. doCreateBean 时,BeanFactory 会在初始化 BeanWrapper 时将 PropertyEditorPropertyEditorRegistrar 放入 BeanWrapper
  3. 最终在调用 populateBean 是会遍历各个属性,找到其类对应的 PropertyEditor
  4. 再通过调用 PropertyEditor 的 setAsText(newTextValue) 方法实现自定义处理属性,通过 editor.getValue() 获取处理后的值