Skip to content

Commit cda7e98

Browse files
committed
Lazily override default editors when actually needed
Closes gh-34361
1 parent e92809d commit cda7e98

File tree

5 files changed

+109
-27
lines changed

5 files changed

+109
-27
lines changed

spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrar.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,4 +45,18 @@ public interface PropertyEditorRegistrar {
4545
*/
4646
void registerCustomEditors(PropertyEditorRegistry registry);
4747

48+
/**
49+
* Indicate whether this registrar exclusively overrides default editors
50+
* rather than registering custom editors, intended to be applied lazily.
51+
* <p>This has an impact on registrar handling in a bean factory: see
52+
* {@link org.springframework.beans.factory.config.ConfigurableBeanFactory#addPropertyEditorRegistrar}.
53+
* @since 6.2.3
54+
* @see PropertyEditorRegistry#registerCustomEditor
55+
* @see PropertyEditorRegistrySupport#overrideDefaultEditor
56+
* @see PropertyEditorRegistrySupport#setDefaultEditorRegistrar
57+
*/
58+
default boolean overridesDefaultEditors() {
59+
return false;
60+
}
61+
4862
}

spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -99,6 +99,9 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
9999

100100
private boolean configValueEditorsActive = false;
101101

102+
@Nullable
103+
private PropertyEditorRegistrar defaultEditorRegistrar;
104+
102105
@Nullable
103106
private Map<Class<?>, PropertyEditor> defaultEditors;
104107

@@ -155,6 +158,19 @@ public void useConfigValueEditors() {
155158
this.configValueEditorsActive = true;
156159
}
157160

161+
/**
162+
* Set a registrar for default editors, as a lazy way of overriding default editors.
163+
* <p>This is expected to be a collaborator with {@link PropertyEditorRegistrySupport},
164+
* downcasting the given {@link PropertyEditorRegistry} accordingly and calling
165+
* {@link #overrideDefaultEditor} for registering additional default editors on it.
166+
* @param registrar the registrar to call when default editors are actually needed
167+
* @since 6.2.3
168+
* @see #overrideDefaultEditor
169+
*/
170+
public void setDefaultEditorRegistrar(PropertyEditorRegistrar registrar) {
171+
this.defaultEditorRegistrar = registrar;
172+
}
173+
158174
/**
159175
* Override the default editor for the specified type with the given property editor.
160176
* <p>Note that this is different from registering a custom editor in that the editor
@@ -184,6 +200,9 @@ public PropertyEditor getDefaultEditor(Class<?> requiredType) {
184200
if (!this.defaultEditorsActive) {
185201
return null;
186202
}
203+
if (this.overriddenDefaultEditors == null && this.defaultEditorRegistrar != null) {
204+
this.defaultEditorRegistrar.registerCustomEditors(this);
205+
}
187206
if (this.overriddenDefaultEditors != null) {
188207
PropertyEditor editor = this.overriddenDefaultEditors.get(requiredType);
189208
if (editor != null) {

spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -183,7 +183,11 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single
183183
* on the given registry, fresh for each bean creation attempt. This avoids
184184
* the need for synchronization on custom editors; hence, it is generally
185185
* preferable to use this method instead of {@link #registerCustomEditor}.
186+
* <p>If the given registrar implements
187+
* {@link PropertyEditorRegistrar#overridesDefaultEditors()} to return {@code true},
188+
* it will be applied lazily (only when default editors are actually needed).
186189
* @param registrar the PropertyEditorRegistrar to register
190+
* @see PropertyEditorRegistrar#overridesDefaultEditors()
187191
*/
188192
void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar);
189193

spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java

+60-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -137,6 +137,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
137137
@Nullable
138138
private ConversionService conversionService;
139139

140+
/** Default PropertyEditorRegistrars to apply to the beans of this factory. */
141+
private final Set<PropertyEditorRegistrar> defaultEditorRegistrars = new LinkedHashSet<>(4);
142+
140143
/** Custom PropertyEditorRegistrars to apply to the beans of this factory. */
141144
private final Set<PropertyEditorRegistrar> propertyEditorRegistrars = new LinkedHashSet<>(4);
142145

@@ -883,7 +886,12 @@ public ConversionService getConversionService() {
883886
@Override
884887
public void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar) {
885888
Assert.notNull(registrar, "PropertyEditorRegistrar must not be null");
886-
this.propertyEditorRegistrars.add(registrar);
889+
if (registrar.overridesDefaultEditors()) {
890+
this.defaultEditorRegistrars.add(registrar);
891+
}
892+
else {
893+
this.propertyEditorRegistrars.add(registrar);
894+
}
887895
}
888896

889897
/**
@@ -1114,6 +1122,7 @@ public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) {
11141122
setBeanExpressionResolver(otherFactory.getBeanExpressionResolver());
11151123
setConversionService(otherFactory.getConversionService());
11161124
if (otherFactory instanceof AbstractBeanFactory otherAbstractFactory) {
1125+
this.defaultEditorRegistrars.addAll(otherAbstractFactory.defaultEditorRegistrars);
11171126
this.propertyEditorRegistrars.addAll(otherAbstractFactory.propertyEditorRegistrars);
11181127
this.customEditors.putAll(otherAbstractFactory.customEditors);
11191128
this.typeConverter = otherAbstractFactory.typeConverter;
@@ -1313,36 +1322,48 @@ protected void initBeanWrapper(BeanWrapper bw) {
13131322
protected void registerCustomEditors(PropertyEditorRegistry registry) {
13141323
if (registry instanceof PropertyEditorRegistrySupport registrySupport) {
13151324
registrySupport.useConfigValueEditors();
1325+
if (!this.defaultEditorRegistrars.isEmpty()) {
1326+
// Optimization: lazy overriding of default editors only when needed
1327+
registrySupport.setDefaultEditorRegistrar(new BeanFactoryDefaultEditorRegistrar());
1328+
}
1329+
}
1330+
else if (!this.defaultEditorRegistrars.isEmpty()) {
1331+
// Fallback: proactive overriding of default editors
1332+
applyEditorRegistrars(registry, this.defaultEditorRegistrars);
13161333
}
1334+
13171335
if (!this.propertyEditorRegistrars.isEmpty()) {
1318-
for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) {
1319-
try {
1320-
registrar.registerCustomEditors(registry);
1321-
}
1322-
catch (BeanCreationException ex) {
1323-
Throwable rootCause = ex.getMostSpecificCause();
1324-
if (rootCause instanceof BeanCurrentlyInCreationException bce) {
1325-
String bceBeanName = bce.getBeanName();
1326-
if (bceBeanName != null && isCurrentlyInCreation(bceBeanName)) {
1327-
if (logger.isDebugEnabled()) {
1328-
logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() +
1329-
"] failed because it tried to obtain currently created bean '" +
1330-
ex.getBeanName() + "': " + ex.getMessage());
1331-
}
1332-
onSuppressedException(ex);
1333-
continue;
1334-
}
1335-
}
1336-
throw ex;
1337-
}
1338-
}
1336+
applyEditorRegistrars(registry, this.propertyEditorRegistrars);
13391337
}
13401338
if (!this.customEditors.isEmpty()) {
13411339
this.customEditors.forEach((requiredType, editorClass) ->
13421340
registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass)));
13431341
}
13441342
}
13451343

1344+
private void applyEditorRegistrars(PropertyEditorRegistry registry, Set<PropertyEditorRegistrar> registrars) {
1345+
for (PropertyEditorRegistrar registrar : registrars) {
1346+
try {
1347+
registrar.registerCustomEditors(registry);
1348+
}
1349+
catch (BeanCreationException ex) {
1350+
Throwable rootCause = ex.getMostSpecificCause();
1351+
if (rootCause instanceof BeanCurrentlyInCreationException bce) {
1352+
String bceBeanName = bce.getBeanName();
1353+
if (bceBeanName != null && isCurrentlyInCreation(bceBeanName)) {
1354+
if (logger.isDebugEnabled()) {
1355+
logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() +
1356+
"] failed because it tried to obtain currently created bean '" +
1357+
ex.getBeanName() + "': " + ex.getMessage());
1358+
}
1359+
onSuppressedException(ex);
1360+
return;
1361+
}
1362+
}
1363+
throw ex;
1364+
}
1365+
}
1366+
}
13461367

13471368
/**
13481369
* Return a merged RootBeanDefinition, traversing the parent bean definition
@@ -2095,4 +2116,20 @@ static class BeanPostProcessorCache {
20952116
final List<MergedBeanDefinitionPostProcessor> mergedDefinition = new ArrayList<>();
20962117
}
20972118

2119+
2120+
/**
2121+
* {@link PropertyEditorRegistrar} that delegates to the bean factory's
2122+
* default registrars, adding exception handling for circular reference
2123+
* scenarios where an editor tries to refer back to the currently created bean.
2124+
*
2125+
* @since 6.2.3
2126+
*/
2127+
class BeanFactoryDefaultEditorRegistrar implements PropertyEditorRegistrar {
2128+
2129+
@Override
2130+
public void registerCustomEditors(PropertyEditorRegistry registry) {
2131+
applyEditorRegistrars(registry, defaultEditorRegistrars);
2132+
}
2133+
}
2134+
20982135
}

spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -135,4 +135,12 @@ private void doRegisterEditor(PropertyEditorRegistry registry, Class<?> required
135135
}
136136
}
137137

138+
/**
139+
* Indicate the use of {@link PropertyEditorRegistrySupport#overrideDefaultEditor} above.
140+
*/
141+
@Override
142+
public boolean overridesDefaultEditors() {
143+
return true;
144+
}
145+
138146
}

0 commit comments

Comments
 (0)