Merge branch '6.2.x'

# Conflicts:
#	spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java
This commit is contained in:
Juergen Hoeller
2025-02-04 13:31:37 +01:00
5 changed files with 109 additions and 28 deletions

View File

@@ -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.
* <p>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;
}
}

View File

@@ -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.
@@ -98,6 +98,8 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
private boolean configValueEditorsActive = false;
private @Nullable PropertyEditorRegistrar defaultEditorRegistrar;
@SuppressWarnings("NullAway.Init")
private Map<Class<?>, PropertyEditor> defaultEditors;
@@ -149,6 +151,19 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
this.configValueEditorsActive = true;
}
/**
* Set a registrar for default editors, as a lazy way of overriding default editors.
* <p>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.
* <p>Note that this is different from registering a custom editor in that the editor
@@ -176,6 +191,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) {

View File

@@ -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.
@@ -179,7 +179,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}.
* <p>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);

View File

@@ -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.
@@ -133,6 +133,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
/** Spring ConversionService to use instead of PropertyEditors. */
private @Nullable ConversionService conversionService;
/** Default PropertyEditorRegistrars to apply to the beans of this factory. */
private final Set<PropertyEditorRegistrar> defaultEditorRegistrars = new LinkedHashSet<>(4);
/** Custom PropertyEditorRegistrars to apply to the beans of this factory. */
private final Set<PropertyEditorRegistrar> propertyEditorRegistrars = new LinkedHashSet<>(4);
@@ -870,7 +873,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);
}
}
/**
@@ -1098,6 +1106,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;
@@ -1297,36 +1306,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<PropertyEditorRegistrar> 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
@@ -2075,4 +2096,20 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
final List<MergedBeanDefinitionPostProcessor> 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);
}
}
}

View File

@@ -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;
}
}