Spring之属性编辑器源码解析

示例

定义Bean

@Data
public class Person {

    private String name;

    private Integer age;

    private String sex;

}

定义Spring配置文件

<?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="person" class="com.spring.beans.Person">
		<property name="name" value="zhangsan"/>
		<property name="age" value="20"/>
		<property name="sex" value="man"/>
	</bean>

</beans>

测试代码

public class PersonTest {

    public static void main(String[] args) {

        ClassPathXmlApplicationContext context = 
            new ClassPathXmlApplicationContext("SpringContext.xml");

        Person person = context.getBean("person", Person.class);
        System.out.println(person);

        context.close();
    }

}

输出结果:

Person(name=zhangsan, age=20, sex=man, birthday=null)

Spring属性编辑器

<bean id="person" class="com.spring.beans.Person">
    <property name="name" value="zhangsan"/>
    <property name="age" value="20"/>
    <property name="sex" value="man"/>
</bean>

对于上面的Bean配置来说,Spring是如何发现配置的property的数据类型呢?

属性编辑器原理代码说明

通过属性编辑器。其实原理很简单,就是通过一个映射,对于Bean中的每一个属性,都定义响应的属性类型的解析方式。下面举例进行说明其原理。

定义属性编辑器接口

/**
 * 属性编辑器接口
 */
public interface PropertiesEditor<T> {

    public T parse(String valueStr) throws Exception;

}

属性编辑器实现类

// 属性为Integer的属性编辑器
public class IntegerPropertiesEditor implements PropertiesEditor<Integer> {

	@Override
	public Integer parse(String valueStr) throws Exception {
		if(valueStr == null || valueStr.trim().isEmpty()) {
			return null;
		}
		return Integer.valueOf(valueStr);
	}
}

// 属性为int的属性编辑器
public class IntPropertiesEditor implements PropertiesEditor<Integer> {

	@Override
	public Integer parse(String valueStr) throws Exception {
		if(valueStr == null || valueStr.trim().isEmpty()) {
			// int属性默认值为0
			return 0;
		}
		return Integer.valueOf(valueStr);
	}
}

// 属性为String的属性编辑器
public class StringPropertiesEditor implements PropertiesEditor<String> {

	@Override
	public String parse(String valueStr) throws Exception {
		return valueStr;
	}
}

// 属性为org.springframework.core.io.Resource的属性编辑器
public class ResourcePropertiesEditor implements PropertiesEditor<Resource> {

	@Override
	public Resource parse(String valueStr) throws Exception {
		return new ClassPathResource(valueStr);
	}
}

属性编辑器工具类

public class PropertiesEditorUtil {

    private static Map<Class<?>, PropertiesEditor<?>> PROPERTIES_EPDITOR_MAP = new HashMap<>();

    static {
        PROPERTIES_EPDITOR_MAP.put(String.class, new StringPropertiesEditor());
        PROPERTIES_EPDITOR_MAP.put(int.class, new IntPropertiesEditor());
        PROPERTIES_EPDITOR_MAP.put(Integer.class, new IntegerPropertiesEditor());
        PROPERTIES_EPDITOR_MAP.put(Resource.class, new ResourcePropertiesEditor());
    }

    public static <T> T parse(Class<T> clazz, Map<String, String> valueMap) throws Exception {

        T data = clazz.newInstance();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            String name = field.getName();
            Class<?> type = field.getType();
            PropertiesEditor<?> editor = PROPERTIES_EPDITOR_MAP.get(type);
            if(editor == null) {
				// 此处可以考虑跳过,即不处理
				throw new Exception("not exist editor for " + clazz.getName() + " property[" + name + "]");
			}
            String propertyValue = valueMap.get(name);
            Object value = editor.parse(propertyValue);
            field.setAccessible(true);
            field.set(data, value);
        }

        return data;
    }
}

测试代码

public class PropertiesEditorTest {

    public static void main(String[] args) throws Exception {

        // 模拟<bean>中Person的定义
        Map<String, String> valueMap = new HashMap<>();
        valueMap.put("name", "zhangsan");
        valueMap.put("age", "20");
        valueMap.put("sex", "man");
        valueMap.put("grade", "100");
        valueMap.put("resource", "SpringContext.xml");

        Person person = PropertiesEditorUtil.parse(Person.class, valueMap);
        System.out.println(person);
    }
}

输出结果:

Person(name=zhangsan, age=20, grade=100, sex=man, resource=class path resource [SpringContext.xml])

通过上面的代码演示,展示了Spring属性编辑器的实现原理。

Spring实现属性编辑器的逻辑

通过上述原理代码说明,我们可以通过设置一个不正确的参数,使解析抛异常进而跟踪异常栈,来找出Spring实现属性编辑器的逻辑:

<bean id="person" class="com.spring.beans.Person">
    <property name="name" value="zhangsan"/>
    <!-- 将Integer类型的属性赋一个非法的值error -->
    <property name="age" value="error"/>
    <property name="sex" value="man"/>
</bean>

配置完后执行程序,发现抛出以下关键异常信息:

Caused by: org.springframework.beans.TypeMismatchException: Failed to convert property value of type 'java.lang.String' to required type 'java.lang.Integer' for property 'age'; nested exception is java.lang.NumberFormatException: For input string: "ass"
at org.springframework.beans.BeanWrapperImpl.convertIfNecessary(BeanWrapperImpl.java:470)
at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:496)
at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:490)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.convertForProperty(AbstractAutowireCapableBeanFactory.java:1437)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1396)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1132)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:522)
... 11 more

从最下面的AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:522)位置开始解读:

// AbstractAutowireCapableBeanFactory#doCreateBean
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, 
                              final Object[] args) {
    //...
    try {
        populateBean(beanName, mbd, instanceWrapper);
        //...
    }
    //...
}

// AbstractAutowireCapableBeanFactory#populateBean
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
    PropertyValues pvs = mbd.getPropertyValues();
    //...
    applyPropertyValues(beanName, mbd, bw, pvs);
}

// AbstractAutowireCapableBeanFactory#applyPropertyValues
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, 
                                   PropertyValues pvs) {
    //...
    List<PropertyValue> deepCopy = new ArrayList<PropertyValue>(original.size());
    for (PropertyValue pv : original) {
    	// 进行解析操作,单独一个代码块进行属性解析的讲解 
        // TODO
    }
        
	//将解析得到的属性值赋值给bean对应的属性
    bw.setPropertyValues(new MutablePropertyValues(deepCopy));

}

属性解析的详细步骤详解:

AbstractAutowireCapableBeanFactory#doCreateBean
	AbstractAutowireCapableBeanFactory#populateBean
		AbstractAutowireCapableBeanFactory#applyPropertyValues
			TypeConverter = BeanWrapperImpl
			BeanDefinitionValueResolver#resolveValueIfNecessary:将在PropertyValue对象的属性值取出,默认是String类型。
			AbstractAutowireCapableBeanFactory#convertForProperty
				BeanWrapperImpl#convertForProperty(Object, String):其中Object为属性值,String为属性名称。
					PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);:
					此代码是获取属性所属的Bean信息,为获取该属性对应的setter/getter方法,以便获取属性的数据类型。
					BeanWrapperImpl#convertForProperty(String, Object, Object, PropertyDescriptor)
						TypeConverterDelegate#convertIfNecessary(String, Object, Object, Class<T>, TypeDescriptor):TypeDescriptor为属性类型的包装类
							PropertyEditorRegistrySupport#findCustomEditor
								PropertyEditorRegistrySupport#getCustomEditor(Class<?>):获取自定义的属性PropertyEditor。
								TypeConverterDelegate#findDefaultEditor:获取Spring系统设置的默认PropertyEditor。
									PropertyEditorRegistrySupport#getDefaultEditor:
									第一次获取的时候,会获取不到属性类型对应的PropertyEditor,因为尚未初始化HashMap:PropertyEditorRegistrySupport#defaultEditors。
									当defaultEditors==null时,才会进行初始化defaultEditors。初始化完成后,从defaultEditors取出属性类型对应的PropertyEditor
										PropertyEditorRegistrySupport#createDefaultEditors初始化Spring系统设置的默认PropertyEditor。
								TypeConverterDelegate#doConvertValue:进行属性转换操作
									TypeConverterDelegate#doConvertTextValue:因为value值是String类型,所以调用此方法将String转化为其他类型值。
		PropertyAccessor#setPropertyValues(org.springframework.beans.PropertyValues):将解析得到的属性值赋值给bean对应的属性。			

上面的排版不美观,专门截图来进行对比:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uwsrAFQy-1582393385801)(./images/Spring属性编辑器详细步骤说明.png)]

到此完成了Spring容器中Bean的属性解析。

自定义属性编辑器

在PropertyEditorRegistrySupport#createDefaultEditors方法中,Spring并没有提供常用的Date类型的转换,因为Date有很多种日期格式,Spring不会限制你具体使用哪一种,而是提供一个接口让你自定义属性转换。

修改Bean和配置文件配置:

@Data
public class Person {

	private String name;
	
	private Integer age;
	
	private String sex;
	
	private Date birthday;
	
}

<bean id="person" class="com.spring.beans.Person">
    <property name="name" value="zhangsan"/>
    <property name="age" value="20"/>
    <property name="sex" value="man"/>
    <property name="birthday" value="2020-01-01 10:10:10"/>
</bean>

执行后发现,抛出异常了:

Caused by: org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'birthday'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [java.util.Date] for property 'birthday': no matching editors or conversion strategy found
at org.springframework.beans.BeanWrapperImpl.convertIfNecessary(BeanWrapperImpl.java:465)
at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:496)
at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:490)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.convertForProperty(AbstractAutowireCapableBeanFactory.java:1437)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1396)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1132)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:522)
... 11 more

java.beans.PropertyEditor

PropertyEditor接口定义了对属性转换的相关操作,一般都是继承PropertyEditor接口的实现类java.beans.PropertyEditorSupport来完成自定义属性编辑器。

public class DatePropertyEditor extends PropertyEditorSupport {

	private String pattern;
    
    public DatePropertyEditor(String pattern) {
		this.pattern = pattern;
	}
	
	public void setPattern(String pattern) {
		this.pattern = pattern;
	}

	@Override
	public void setAsText(String text) throws IllegalArgumentException {
		SimpleDateFormat sdf = new SimpleDateFormat(pattern);
        try {
            Date date = sdf.parse(text);
            setValue(date);
        } catch (ParseException e) {
            throw new IllegalArgumentException(e);
        }
	}
	
}

进行自定义属性编辑器配置

方式1

<?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="person" class="com.spring.beans.Person">
        <property name="name" value="zhangsan"/>
        <property name="age" value="20"/>
        <property name="sex" value="man"/>
        <property name="birthday" value="2020-01-01 10:10:10"/>
    </bean>

    <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="customEditors">
            <map>
                <!-- key的值为属性类型的全路径-->
                <entry key="java.util.Date">
                    <!-- 属性类型对应的自定义属性编辑器,该属性将由此属性编辑器解析 -->
                    <bean id="datePropertyEditor"
						class="com.spring.editor.DatePropertyEditor">
						<property name="pattern" value="yyyy-MM-dd HH:mm:ss"/>
					</bean>
                </entry>
            </map>
        </property>
    </bean>

</beans>


测试代码

public static void main(String[] args) {

    ClassPathXmlApplicationContext context = 
        new ClassPathXmlApplicationContext("SpringContext.xml");

    Person person = context.getBean("person", Person.class);
    System.out.println(person);

    context.close();
}

输出结果:

Person(name=zhangsan, age=20, sex=man, birthday=Wed Jan 01 10:10:10 CST 2020)

说明对Date类型的属性,可以解析成功了。

方式2

编写属性编辑器注册器:

public class DatePropertyEditorRegistrar implements PropertyEditorRegistrar {

    @Override
    public void registerCustomEditors(PropertyEditorRegistry registry) {
        registry.registerCustomEditor(Date.class, 
                                      new DatePropertyEditor("yyyy-MM-dd HH:mm:ss"));
    }

}

配置文件配置变更:

<?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="person" class="com.spring.beans.Person">
        <property name="name" value="zhangsan"/>
        <property name="age" value="20"/>
        <property name="sex" value="man"/>
        <property name="birthday" value="2020-01-01 10:10:10"/>
    </bean>

    <bean
          class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="propertyEditorRegistrars">
            <list>
                <bean class="com.spring.editor.DatePropertyEditorRegistrar"/>
            </list>
        </property>
    </bean>

</beans>

测试代码

public static void main(String[] args) {

    ClassPathXmlApplicationContext context = 
        new ClassPathXmlApplicationContext("SpringContext.xml");

    Person person = context.getBean("person", Person.class);
    System.out.println(person);

    context.close();
}

输出结果:

Person(name=zhangsan, age=20, sex=man, birthday=Wed Jan 01 10:10:10 CST 2020)

说明对Date类型的属性,可以解析成功了。

自定义属性编辑器的工作原理

org.springframework.beans.factory.config.CustomEditorConfigurer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4hu4NowB-1582393385802)(./images/CustomEditorConfigurer.png)]

Spring提供了一个配置类CustomEditorConfigurer,可以将自定义的属性编辑器注入到Spring中。此类实现了BeanFactoryPostProcessor接口,BeanFactoryPostProcessor可以在Bean实例化之前,往Spring容器中注入其他Bean。

org.springframework.beans.factory.config.BeanFactoryPostProcessor

从上面的CustomEditorConfigurer继承关系图可以看出,CustomEditorConfigurer实现了BeanFactoryPostProcessor接口。

BeanFactoryPostProcessor:是BeanFactory的后置处理器。在BeanFactory标准初始化之后调用,这时所有的bean定义已经保存加载到beanFactory,但是bean的实例还未创建。

BeanFactoryPostProcessor能干什么:来定制和修改BeanFactory的内容,如覆盖或添加属性。

理清楚BeanFactoryPostProcessor的功能作用后,下面进行分析CustomEditorConfigurer的工作原理。

CustomEditorConfigurer工作原理分析

// CustomEditorConfigurer#postProcessBeanFactory
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);
        }
    }
}

上面的代码逻辑很简单,就是将配置到CustomEditorConfigurer的属性编辑器进行存储到BeanFactory(AbstractBeanFactory)中。然后在Spring启动的时候,将其注册到PropertyEditorRegistrySupport#customEditors中。这样在解析属性编辑器的时候,首先从自定义的属性编辑器PropertyEditorRegistrySupport#customEditors中获取,获取不到再从PropertyEditorRegistrySupport#defaultEditors中获取。这样的用处是,自定义的属性编辑器可以“覆盖”系统默认定义的属性编辑器,提高了系统扩展性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值