From cda7e9864393819095541498b4e58ac0d482bbe4 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 4 Feb 2025 13:26:16 +0100 Subject: [PATCH] Lazily override default editors when actually needed Closes gh-34361 --- .../beans/PropertyEditorRegistrar.java | 16 +++- .../beans/PropertyEditorRegistrySupport.java | 21 ++++- .../config/ConfigurableBeanFactory.java | 6 +- .../factory/support/AbstractBeanFactory.java | 85 +++++++++++++------ .../support/ResourceEditorRegistrar.java | 10 ++- 5 files changed, 110 insertions(+), 28 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrar.java b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrar.java index 69e2a68b3e..f96972e812 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrar.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,4 +45,18 @@ public interface PropertyEditorRegistrar { */ void registerCustomEditors(PropertyEditorRegistry registry); + /** + * Indicate whether this registrar exclusively overrides default editors + * rather than registering custom editors, intended to be applied lazily. + *

This has an impact on registrar handling in a bean factory: see + * {@link org.springframework.beans.factory.config.ConfigurableBeanFactory#addPropertyEditorRegistrar}. + * @since 6.2.3 + * @see PropertyEditorRegistry#registerCustomEditor + * @see PropertyEditorRegistrySupport#overrideDefaultEditor + * @see PropertyEditorRegistrySupport#setDefaultEditorRegistrar + */ + default boolean overridesDefaultEditors() { + return false; + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java index af3e0cc00c..0802b5788c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -99,6 +99,9 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry { private boolean configValueEditorsActive = false; + @Nullable + private PropertyEditorRegistrar defaultEditorRegistrar; + @Nullable private Map, PropertyEditor> defaultEditors; @@ -155,6 +158,19 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry { this.configValueEditorsActive = true; } + /** + * Set a registrar for default editors, as a lazy way of overriding default editors. + *

This is expected to be a collaborator with {@link PropertyEditorRegistrySupport}, + * downcasting the given {@link PropertyEditorRegistry} accordingly and calling + * {@link #overrideDefaultEditor} for registering additional default editors on it. + * @param registrar the registrar to call when default editors are actually needed + * @since 6.2.3 + * @see #overrideDefaultEditor + */ + public void setDefaultEditorRegistrar(PropertyEditorRegistrar registrar) { + this.defaultEditorRegistrar = registrar; + } + /** * Override the default editor for the specified type with the given property editor. *

Note that this is different from registering a custom editor in that the editor @@ -184,6 +200,9 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry { if (!this.defaultEditorsActive) { return null; } + if (this.overriddenDefaultEditors == null && this.defaultEditorRegistrar != null) { + this.defaultEditorRegistrar.registerCustomEditors(this); + } if (this.overriddenDefaultEditors != null) { PropertyEditor editor = this.overriddenDefaultEditors.get(requiredType); if (editor != null) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java index af9616b44a..e4fb00877b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -183,7 +183,11 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single * on the given registry, fresh for each bean creation attempt. This avoids * the need for synchronization on custom editors; hence, it is generally * preferable to use this method instead of {@link #registerCustomEditor}. + *

If the given registrar implements + * {@link PropertyEditorRegistrar#overridesDefaultEditors()} to return {@code true}, + * it will be applied lazily (only when default editors are actually needed). * @param registrar the PropertyEditorRegistrar to register + * @see PropertyEditorRegistrar#overridesDefaultEditors() */ void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 60029e60a9..6587479b8f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -137,6 +137,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp @Nullable private ConversionService conversionService; + /** Default PropertyEditorRegistrars to apply to the beans of this factory. */ + private final Set defaultEditorRegistrars = new LinkedHashSet<>(4); + /** Custom PropertyEditorRegistrars to apply to the beans of this factory. */ private final Set propertyEditorRegistrars = new LinkedHashSet<>(4); @@ -883,7 +886,12 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp @Override public void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar) { Assert.notNull(registrar, "PropertyEditorRegistrar must not be null"); - this.propertyEditorRegistrars.add(registrar); + if (registrar.overridesDefaultEditors()) { + this.defaultEditorRegistrars.add(registrar); + } + else { + this.propertyEditorRegistrars.add(registrar); + } } /** @@ -1114,6 +1122,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp setBeanExpressionResolver(otherFactory.getBeanExpressionResolver()); setConversionService(otherFactory.getConversionService()); if (otherFactory instanceof AbstractBeanFactory otherAbstractFactory) { + this.defaultEditorRegistrars.addAll(otherAbstractFactory.defaultEditorRegistrars); this.propertyEditorRegistrars.addAll(otherAbstractFactory.propertyEditorRegistrars); this.customEditors.putAll(otherAbstractFactory.customEditors); this.typeConverter = otherAbstractFactory.typeConverter; @@ -1313,36 +1322,48 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp protected void registerCustomEditors(PropertyEditorRegistry registry) { if (registry instanceof PropertyEditorRegistrySupport registrySupport) { registrySupport.useConfigValueEditors(); - } - if (!this.propertyEditorRegistrars.isEmpty()) { - for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) { - try { - registrar.registerCustomEditors(registry); - } - catch (BeanCreationException ex) { - Throwable rootCause = ex.getMostSpecificCause(); - if (rootCause instanceof BeanCurrentlyInCreationException bce) { - String bceBeanName = bce.getBeanName(); - if (bceBeanName != null && isCurrentlyInCreation(bceBeanName)) { - if (logger.isDebugEnabled()) { - logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() + - "] failed because it tried to obtain currently created bean '" + - ex.getBeanName() + "': " + ex.getMessage()); - } - onSuppressedException(ex); - continue; - } - } - throw ex; - } + if (!this.defaultEditorRegistrars.isEmpty()) { + // Optimization: lazy overriding of default editors only when needed + registrySupport.setDefaultEditorRegistrar(new BeanFactoryDefaultEditorRegistrar()); } } + else if (!this.defaultEditorRegistrars.isEmpty()) { + // Fallback: proactive overriding of default editors + applyEditorRegistrars(registry, this.defaultEditorRegistrars); + } + + if (!this.propertyEditorRegistrars.isEmpty()) { + applyEditorRegistrars(registry, this.propertyEditorRegistrars); + } if (!this.customEditors.isEmpty()) { this.customEditors.forEach((requiredType, editorClass) -> registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass))); } } + private void applyEditorRegistrars(PropertyEditorRegistry registry, Set registrars) { + for (PropertyEditorRegistrar registrar : registrars) { + try { + registrar.registerCustomEditors(registry); + } + catch (BeanCreationException ex) { + Throwable rootCause = ex.getMostSpecificCause(); + if (rootCause instanceof BeanCurrentlyInCreationException bce) { + String bceBeanName = bce.getBeanName(); + if (bceBeanName != null && isCurrentlyInCreation(bceBeanName)) { + if (logger.isDebugEnabled()) { + logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() + + "] failed because it tried to obtain currently created bean '" + + ex.getBeanName() + "': " + ex.getMessage()); + } + onSuppressedException(ex); + return; + } + } + throw ex; + } + } + } /** * Return a merged RootBeanDefinition, traversing the parent bean definition @@ -2095,4 +2116,20 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp final List mergedDefinition = new ArrayList<>(); } + + /** + * {@link PropertyEditorRegistrar} that delegates to the bean factory's + * default registrars, adding exception handling for circular reference + * scenarios where an editor tries to refer back to the currently created bean. + * + * @since 6.2.3 + */ + class BeanFactoryDefaultEditorRegistrar implements PropertyEditorRegistrar { + + @Override + public void registerCustomEditors(PropertyEditorRegistry registry) { + applyEditorRegistrars(registry, defaultEditorRegistrars); + } + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java b/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java index f5c9312176..45ea32de3f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java +++ b/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -135,4 +135,12 @@ public class ResourceEditorRegistrar implements PropertyEditorRegistrar { } } + /** + * Indicate the use of {@link PropertyEditorRegistrySupport#overrideDefaultEditor} above. + */ + @Override + public boolean overridesDefaultEditors() { + return true; + } + }