Введение в PropertyEditor и PropertyEditorSupport
java.beans.PropertyEditor
Это класс, который поставляется с JDK и предоставляется AWT. Для чего он используется, чтобы говорить о значении (объекте) соответствующего типа бита преобразования строки, введенного пользователем в графическом интерфейсе. Похож на преобразователь.
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();
Преобразование значения свойства в строку -
void setAsText(String text);
Преобразование строки в значение свойства
И 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?
Давайте подумаем об этом, когда вы используете файл конфигурации xml для установки определенного значения для атрибута, независимо от того, вводим ли мы строку, но тип атрибута, которому мы соответствуем, не обязательно является строковым типом, этот вид под сценой, это то же самое, что и сцена 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>
получить этот боб и распечатать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
класс реализации. Когда мы пытаемся получить соответствующий PropertyEditor через объект Class, он инициализирует для нас серию PropertyEditors по умолчанию.
В populateBean doCreateBean будет вызываться 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 для инкапсуляции бинов используется тип BeanWrapper, который, в свою очередь, косвенно наследует PropertyEditorRegistry. BeanWrapperImpl — это класс реализации BeanWrapper, и большинство реестров PropertyEditorRegistries, которые мы видим в системе, являются объектами BeanWrapperImpl. BeanWrapperImpl также наследует класс реализации PropertyEditorRegistrySupport.
PropertyEditorRegistrar
реестр редактора свойств
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);
}
}
}
Давайте поговорим о том, где используется этот класс.Этот Registrar будет использоваться только тогда, когда используется ApplicationContext, а вышеприведенный PropertyEditor зарегистрирует или переопределит значение по умолчанию PropertyEditorRegistry.
Цепочка взаимосвязей вызовов
ClassPathXmlApplicationContext 构造函数 -> refresh -> prepareBeanFactory() -> 创建 ResourceEditorRegistrar 增加到 Set 中
Затем он зарегистрирует PropertyEditor по умолчанию ResourceEditorRegistrar в createBeanInstance doCreateBean.
пример
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 будет создавать экземпляры всех неленивых bean-компонентов по умолчанию, и в это время наш PropertyEditor не был зарегистрирован, сообщит об ошибке
В этой статье используетсяmdniceнабор текста