共计 12547 个字符,预计需要花费 32 分钟才能阅读完成。
Spring 源码系列 |
---|
Spring 整体架构 |
编译 Spring5.2.0 源码 |
Spring-AliasRegistry 别名注册 |
Spring 资源加载 |
Spring 容器初始化 |
Spring 获取单例 (一) |
Spring 获取单例 (二) |
Spring 获取单例 (三) |
Spring 解决循环依赖 |
Spring FactoryBean 缓存 |
Spring Aware 介绍 |
Spring BeanPostProcessor 介绍 |
扯淡 Spring BeanDefinition |
[探秘 Spring 的 PropertyEditor]() |
PropertyEditor & PropertyEditorSupport 介绍
java.beans.PropertyEditor
是 JDK 自带的类,是提供给 AWT。做啥用呢、就是讲用户在图形见面中输出的字符串转换位对应类型的值 (对象)。相似于一个 convertor。
public interface PropertyEditor {void setValue(Object value); | |
Object getValue(); | |
boolean isPaintable(); | |
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); | |
} |
次要办法有四个
-
void setValue(Object value);
设置属性值 -
Object getValue();
获取属性值 -
String getAsText();
把属性值转换成 String -
void setAsText(String text);
把 String 转换成属性值
而 Java 也为咱们提供了一个默认的实现类 java.beans.PropertyEditorSupport
private Object value; | |
public void setValue(Object value) { | |
this.value = value; | |
firePropertyChange();} | |
public Object getValue() {return value;} | |
public void setAsText(String text) throws java.lang.IllegalArgumentException {if (value instanceof String) {setValue(text); | |
return; | |
} | |
throw new java.lang.IllegalArgumentException(text); | |
} | |
public String getAsText() {return (this.value != null) | |
? this.value.toString() | |
: null; | |
} |
咱们只有重写 setAsText
和 getAsText
办法能够实现 String 类型到特定类型的转换了
与 Spring 的关系
说了那么久、这个跟 Spring 有什么锤子关系吗 ?
咱们想一想、当你应用 xml 配置文件给某个属性设定某个值的时候 (或者说应用 @Value 注解给定一个默认值的时候)、咱们输出的是不是一个字符串、然而咱们对应的这个属性的类型却不肯定是字符串类型、这种场景之下、是不是跟 AWT 的场景是一样的。所以 Spring 的属性解释都是继承自 PropertyEditorSupport 而后重写了 setAsText
和 getAsText
举个例子
public class CustomBooleanEditor extends PropertyEditorSupport { | |
public static final String VALUE_TRUE = "true"; | |
public static final String VALUE_FALSE = "false"; | |
public static final String VALUE_ON = "on"; | |
public static final String VALUE_OFF = "off"; | |
public static final String VALUE_YES = "yes"; | |
public static final String VALUE_NO = "no"; | |
public static final String VALUE_1 = "1"; | |
public static final String VALUE_0 = "0"; | |
// 为 true 的时候的字符串、默认为 null | |
@Nullable | |
private final String trueString; | |
// 为 false 的时候的字符串、默认为 null | |
@Nullable | |
private final String falseString; | |
// 是否容许为 null | |
// 根本类型 boolean 的时候不容许空的字符串 | |
// 援用类型 Boolean 的时候容许空的字符串 | |
private final boolean allowEmpty; | |
public CustomBooleanEditor(boolean allowEmpty) {this(null, null, allowEmpty); | |
} | |
public CustomBooleanEditor(@Nullable String trueString, @Nullable String falseString, boolean allowEmpty) { | |
this.trueString = trueString; | |
this.falseString = falseString; | |
this.allowEmpty = allowEmpty; | |
} | |
@Override | |
public void setAsText(@Nullable String text) throws IllegalArgumentException {String input = (text != null ? text.trim() : null); | |
if (this.allowEmpty && !StringUtils.hasLength(input)) { | |
// Treat empty String as null value. | |
setValue(null); | |
} else if (this.trueString != null && this.trueString.equalsIgnoreCase(input)) {setValue(Boolean.TRUE); | |
} else if (this.falseString != null && this.falseString.equalsIgnoreCase(input)) {setValue(Boolean.FALSE); | |
} else if (this.trueString == null && | |
(VALUE_TRUE.equalsIgnoreCase(input) || VALUE_ON.equalsIgnoreCase(input) || | |
VALUE_YES.equalsIgnoreCase(input) || VALUE_1.equals(input))) {setValue(Boolean.TRUE); | |
} else if (this.falseString == null && | |
(VALUE_FALSE.equalsIgnoreCase(input) || VALUE_OFF.equalsIgnoreCase(input) || | |
VALUE_NO.equalsIgnoreCase(input) || VALUE_0.equals(input))) {setValue(Boolean.FALSE); | |
} else {throw new IllegalArgumentException("Invalid boolean value [" + text + "]"); | |
} | |
} | |
@Override | |
public String getAsText() {if (Boolean.TRUE.equals(getValue())) {return (this.trueString != null ? this.trueString : VALUE_TRUE); | |
} else if (Boolean.FALSE.equals(getValue())) {return (this.falseString != null ? this.falseString : VALUE_FALSE); | |
} else {return "";} | |
} | |
} |
办法也是挺简略的就不啰嗦解释了
举个例子
public class Job { | |
private boolean completed; | |
private Boolean started; | |
// get and set ........... | |
} |
<bean class="com.demo.property.editor.Job" id="job"> | |
<property name="completed" value="on" /> | |
<property name="started" value=""/> | |
</bean> |
获取这个 bean 并打印 Job{completed=true, started=null}
相干组件介绍
PropertyEditorRegistry
一看名字就晓得是一个注册的接口
void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor); | |
void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor); | |
@Nullable | |
PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath); |
PropertyEditorRegistrySupport
PropertyEditorRegistry
的实现类。当咱们尝试去通过 Class 对象获取对应的 PropertyEditor 的时候、它会为咱们初始化一系列默认的 PropertyEditor
在 doCreateBean 的 populateBean 中会调用 getDefaultEditor 获取对应的 PropertyEditor 进行值的类型转换
// spring 默认提供的 propertyEditor | |
@Nullable | |
private Map<Class<?>, PropertyEditor> defaultEditors; | |
// 去笼罩的 默认的 property editor | |
@Nullable | |
private Map<Class<?>, PropertyEditor> overriddenDefaultEditors; | |
// 自定义的一些 property editor | |
@Nullable | |
private Map<Class<?>, PropertyEditor> customEditors; | |
// 属性的门路 / 属性名,CustomEditorHolder 蕴含的是 Class 和 PropertyEditor | |
@Nullable | |
private Map<String, CustomEditorHolder> customEditorsForPath; | |
// 如果注册的父 class、那么子类的 class 找不到的时候、就会返回这个父的 class 并且讲这个关系保留在 | |
// 这个 map 中 | |
@Nullable | |
private Map<Class<?>, PropertyEditor> customEditorCache; | |
@Nullable | |
public PropertyEditor getDefaultEditor(Class<?> requiredType) {if (!this.defaultEditorsActive) {return null;} | |
if (this.overriddenDefaultEditors != null) {PropertyEditor editor = this.overriddenDefaultEditors.get(requiredType); | |
if (editor != null) {return editor;} | |
} | |
if (this.defaultEditors == null) {createDefaultEditors(); | |
} | |
return this.defaultEditors.get(requiredType); | |
} | |
private void createDefaultEditors() {this.defaultEditors = new HashMap<>(64); | |
// Simple editors, without parameterization capabilities. | |
// The JDK does not contain a default editor for any of these target types. | |
this.defaultEditors.put(Charset.class, new CharsetEditor()); | |
this.defaultEditors.put(Class.class, new ClassEditor()); | |
this.defaultEditors.put(Class[].class, new ClassArrayEditor()); | |
this.defaultEditors.put(Currency.class, new CurrencyEditor()); | |
this.defaultEditors.put(File.class, new FileEditor()); | |
this.defaultEditors.put(InputStream.class, new InputStreamEditor()); | |
this.defaultEditors.put(InputSource.class, new InputSourceEditor()); | |
this.defaultEditors.put(Locale.class, new LocaleEditor()); | |
this.defaultEditors.put(Path.class, new PathEditor()); | |
this.defaultEditors.put(Pattern.class, new PatternEditor()); | |
this.defaultEditors.put(Properties.class, new PropertiesEditor()); | |
this.defaultEditors.put(Reader.class, new ReaderEditor()); | |
this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor()); | |
this.defaultEditors.put(TimeZone.class, new TimeZoneEditor()); | |
this.defaultEditors.put(URI.class, new URIEditor()); | |
this.defaultEditors.put(URL.class, new URLEditor()); | |
this.defaultEditors.put(UUID.class, new UUIDEditor()); | |
this.defaultEditors.put(ZoneId.class, new ZoneIdEditor()); | |
// Default instances of collection editors. | |
// Can be overridden by registering custom instances of those as custom editors. | |
this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class)); | |
this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class)); | |
this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class)); | |
this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class)); | |
this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class)); | |
// Default editors for primitive arrays. | |
this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor()); | |
this.defaultEditors.put(char[].class, new CharArrayPropertyEditor()); | |
// The JDK does not contain a default editor for char! | |
this.defaultEditors.put(char.class, new CharacterEditor(false)); | |
this.defaultEditors.put(Character.class, new CharacterEditor(true)); | |
// Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor. | |
this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false)); | |
this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true)); | |
// The JDK does not contain default editors for number wrapper types! | |
// Override JDK primitive number editors with our own CustomNumberEditor. | |
this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false)); | |
this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true)); | |
this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false)); | |
this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true)); | |
this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false)); | |
this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true)); | |
this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false)); | |
this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true)); | |
this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false)); | |
this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true)); | |
this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false)); | |
this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true)); | |
this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true)); | |
this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true)); | |
// Only register config value editors if explicitly requested. | |
if (this.configValueEditorsActive) {StringArrayPropertyEditor sae = new StringArrayPropertyEditor(); | |
this.defaultEditors.put(String[].class, sae); | |
this.defaultEditors.put(short[].class, sae); | |
this.defaultEditors.put(int[].class, sae); | |
this.defaultEditors.put(long[].class, sae); | |
} | |
} |
BeanWrapper
Spring 中用于封装 bean 的是 BeanWrapper 类型、而它又间接继承了 PropertyEditorRegistry。BeanWrapperImpl 是 BeanWrapper 的实现类、咱们在零碎中看到的大多数 PropertyEditorRegistry 都是 BeanWrapperImpl 的对象。BeanWrapperImpl 还继承了 PropertyEditorRegistrySupport 这个实现类
PropertyEditorRegistrar
property editor 的登记处
void registerCustomEditors(PropertyEditorRegistry registry);
ResourceEditorRegistrar
惟一的一个默认的实现类
public class ResourceEditorRegistrar implements PropertyEditorRegistrar { | |
private final PropertyResolver propertyResolver; | |
private final ResourceLoader resourceLoader; | |
public ResourceEditorRegistrar(ResourceLoader resourceLoader, PropertyResolver propertyResolver) { | |
this.resourceLoader = resourceLoader; | |
this.propertyResolver = propertyResolver; | |
} | |
@Override | |
public void registerCustomEditors(PropertyEditorRegistry registry) {ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader, this.propertyResolver); | |
doRegisterEditor(registry, Resource.class, baseEditor); | |
doRegisterEditor(registry, ContextResource.class, baseEditor); | |
doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor)); | |
doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor)); | |
doRegisterEditor(registry, File.class, new FileEditor(baseEditor)); | |
doRegisterEditor(registry, Path.class, new PathEditor(baseEditor)); | |
doRegisterEditor(registry, Reader.class, new ReaderEditor(baseEditor)); | |
doRegisterEditor(registry, URL.class, new URLEditor(baseEditor)); | |
ClassLoader classLoader = this.resourceLoader.getClassLoader(); | |
doRegisterEditor(registry, URI.class, new URIEditor(classLoader)); | |
doRegisterEditor(registry, Class.class, new ClassEditor(classLoader)); | |
doRegisterEditor(registry, Class[].class, new ClassArrayEditor(classLoader)); | |
if (this.resourceLoader instanceof ResourcePatternResolver) {doRegisterEditor(registry, Resource[].class, | |
new ResourceArrayPropertyEditor((ResourcePatternResolver) this.resourceLoader, this.propertyResolver)); | |
} | |
} | |
private void doRegisterEditor(PropertyEditorRegistry registry, Class<?> requiredType, PropertyEditor editor) {if (registry instanceof PropertyEditorRegistrySupport) {((PropertyEditorRegistrySupport) registry).overrideDefaultEditor(requiredType, editor); | |
} | |
else {registry.registerCustomEditor(requiredType, editor); | |
} | |
} | |
} |
先说下这个类被应用到的中央吧、只有应用 ApplicationContext 的时候这个 Registrar 才会被应用到、下面的 PropertyEditor 才会去注册或者笼罩 PropertyEditorRegistry 默认的值
调用关系链为
ClassPathXmlApplicationContext 构造函数 -> refresh -> prepareBeanFactory() -> 创立 ResourceEditorRegistrar 减少到 Set 中
继而它会在 doCreateBean 的 createBeanInstance 中将 ResourceEditorRegistrar 的默认的 PropertyEditor 注册进去
例子
public class Job { | |
private boolean completed; | |
private Content content; | |
// get and set method | |
} |
public class Content { | |
private String details; | |
private String type; | |
private int priority; | |
// get and set method | |
} |
<bean class="com.demo.property.editor.Job" id="job" lazy-init="true"> | |
<property name="completed" value="off" /> | |
<property name="content" value="关注我: 紧急:100"/> | |
</bean> |
ClassPathXmlApplicationContext classPathXmlApplicationContext = | |
new ClassPathXmlApplicationContext("property.editor/coderLi.xml"); | |
classPathXmlApplicationContext.getBeanFactory().addPropertyEditorRegistrar(registry -> {if (registry instanceof PropertyEditorRegistrySupport) {((PropertyEditorRegistrySupport) registry).overrideDefaultEditor(Content.class, new ContentPropertyEditor()); | |
System.out.println("PropertyEditorRegistrySupport"); | |
} else {registry.registerCustomEditor(Content.class, new ContentPropertyEditor()); | |
} | |
}); | |
Object job = classPathXmlApplicationContext.getBean("job"); | |
System.out.println(job); |
实现雷同成果的办法有很多、比如说 CustomEditorConfigurer、也能够实现 BeanFactoryPostProcessor 接口等等
如我下面的代码实现的话、留神一个点就是、这个 bean 必须是一个提早实例化的、因为 ApplicationContext 默认是会将所有的非 lazy 的 bean 实例化、而这个时候咱们的 PropertyEditor 还没有注册进去、将会报错