示例
定义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中获取。这样的用处是,自定义的属性编辑器可以“覆盖”系统默认定义的属性编辑器,提高了系统扩展性。