Shared DefaultConversionService instance for simple fallback purposes

Issue: SPR-14948
This commit is contained in:
Juergen Hoeller
2016-11-24 15:29:17 +01:00
parent 34c6c9ffc2
commit 80931b211c
7 changed files with 82 additions and 19 deletions

View File

@@ -25,12 +25,12 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.core.convert.converter.ConverterRegistry;
/** /**
* A specialization of {@link GenericConversionService} configured by default with * A specialization of {@link GenericConversionService} configured by default
* converters appropriate for most environments. * with converters appropriate for most environments.
* *
* <p>Designed for direct instantiation but also exposes the static * <p>Designed for direct instantiation but also exposes the static
* {@link #addDefaultConverters(ConverterRegistry)} utility method for ad hoc use against any * {@link #addDefaultConverters(ConverterRegistry)} utility method for ad-hoc
* {@code ConverterRegistry} instance. * use against any {@code ConverterRegistry} instance.
* *
* @author Chris Beams * @author Chris Beams
* @author Juergen Hoeller * @author Juergen Hoeller
@@ -39,6 +39,32 @@ import org.springframework.core.convert.converter.ConverterRegistry;
*/ */
public class DefaultConversionService extends GenericConversionService { public class DefaultConversionService extends GenericConversionService {
private static volatile DefaultConversionService sharedInstance;
/**
* Return a shared default {@code ConversionService} instance,
* lazily building it once needed.
* <p><b>NOTE:</b> We highly recommend constructing individual
* {@code ConversionService} instances for customization purposes.
* This accessor is only meant as a fallback for code paths which
* need simple type coercion but cannot access a longer-lived
* {@code ConversionService} instance any other way.
* @return the shared {@code ConversionService} instance (never {@code null})
* @since 4.3.5
*/
public static ConversionService getSharedInstance() {
if (sharedInstance == null) {
synchronized (DefaultConversionService.class) {
if (sharedInstance == null) {
sharedInstance = new DefaultConversionService();
}
}
}
return sharedInstance;
}
/** /**
* Create a new {@code DefaultConversionService} with the set of * Create a new {@code DefaultConversionService} with the set of
* {@linkplain DefaultConversionService#addDefaultConverters(ConverterRegistry) default converters}. * {@linkplain DefaultConversionService#addDefaultConverters(ConverterRegistry) default converters}.

View File

@@ -22,8 +22,11 @@ import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.PropertyPlaceholderHelper; import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.SystemPropertyUtils; import org.springframework.util.SystemPropertyUtils;
@@ -38,7 +41,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
protected final Log logger = LogFactory.getLog(getClass()); protected final Log logger = LogFactory.getLog(getClass());
protected ConfigurableConversionService conversionService = new DefaultConversionService(); private volatile ConfigurableConversionService conversionService;
private PropertyPlaceholderHelper nonStrictHelper; private PropertyPlaceholderHelper nonStrictHelper;
@@ -57,11 +60,21 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
@Override @Override
public ConfigurableConversionService getConversionService() { public ConfigurableConversionService getConversionService() {
return this.conversionService; // Need to provide an independent DefaultConversionService, not the
// shared DefaultConversionService used by PropertySourcesPropertyResolver.
if (this.conversionService == null) {
synchronized (this) {
if (this.conversionService == null) {
this.conversionService = new DefaultConversionService();
}
}
}
return conversionService;
} }
@Override @Override
public void setConversionService(ConfigurableConversionService conversionService) { public void setConversionService(ConfigurableConversionService conversionService) {
Assert.notNull(conversionService, "ConversionService must not be null");
this.conversionService = conversionService; this.conversionService = conversionService;
} }
@@ -72,6 +85,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
*/ */
@Override @Override
public void setPlaceholderPrefix(String placeholderPrefix) { public void setPlaceholderPrefix(String placeholderPrefix) {
Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
this.placeholderPrefix = placeholderPrefix; this.placeholderPrefix = placeholderPrefix;
} }
@@ -82,6 +96,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
*/ */
@Override @Override
public void setPlaceholderSuffix(String placeholderSuffix) { public void setPlaceholderSuffix(String placeholderSuffix) {
Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");
this.placeholderSuffix = placeholderSuffix; this.placeholderSuffix = placeholderSuffix;
} }
@@ -113,8 +128,10 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
@Override @Override
public void setRequiredProperties(String... requiredProperties) { public void setRequiredProperties(String... requiredProperties) {
for (String key : requiredProperties) { if (requiredProperties != null) {
this.requiredProperties.add(key); for (String key : requiredProperties) {
this.requiredProperties.add(key);
}
} }
} }
@@ -218,6 +235,31 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
}); });
} }
/**
* Convert the given value to the specified target type, if necessary.
* @param value the original property value
* @param targetType the specified target type for property retrieval
* @return the converted value, or the original value if no conversion
* is necessary
* @since 4.3.5
*/
@SuppressWarnings("unchecked")
protected <T> T convertValueIfNecessary(Object value, Class<T> targetType) {
if (targetType == null) {
return (T) value;
}
ConversionService csToUse = this.conversionService;
if (csToUse == null) {
// Avoid initialization of shared DefaultConversionService if
// no standard type conversion is needed in the first place...
if (ClassUtils.isAssignableValue(targetType, value)) {
return (T) value;
}
csToUse = DefaultConversionService.getSharedInstance();
}
return csToUse.convert(value, targetType);
}
/** /**
* Retrieve the specified property as a raw String, * Retrieve the specified property as a raw String,

View File

@@ -80,7 +80,7 @@ public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
value = resolveNestedPlaceholders((String) value); value = resolveNestedPlaceholders((String) value);
} }
logKeyFound(key, propertySource, value); logKeyFound(key, propertySource, value);
return this.conversionService.convert(value, targetValueType); return convertValueIfNecessary(value, targetValueType);
} }
} }
} }

View File

@@ -36,8 +36,6 @@ import org.springframework.util.Assert;
*/ */
public class StandardTypeConverter implements TypeConverter { public class StandardTypeConverter implements TypeConverter {
private static volatile ConversionService defaultConversionService;
private final ConversionService conversionService; private final ConversionService conversionService;
@@ -45,10 +43,7 @@ public class StandardTypeConverter implements TypeConverter {
* Create a StandardTypeConverter for the default ConversionService. * Create a StandardTypeConverter for the default ConversionService.
*/ */
public StandardTypeConverter() { public StandardTypeConverter() {
if (defaultConversionService == null) { this.conversionService = DefaultConversionService.getSharedInstance();
defaultConversionService = new DefaultConversionService();
}
this.conversionService = defaultConversionService;
} }
/** /**

View File

@@ -88,7 +88,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
private boolean primitivesDefaultedForNullValue = false; private boolean primitivesDefaultedForNullValue = false;
/** ConversionService for binding JDBC values to bean properties */ /** ConversionService for binding JDBC values to bean properties */
private ConversionService conversionService = new DefaultConversionService(); private ConversionService conversionService = DefaultConversionService.getSharedInstance();
/** Map of the fields we provide mapping for */ /** Map of the fields we provide mapping for */
private Map<String, PropertyDescriptor> mappedFields; private Map<String, PropertyDescriptor> mappedFields;

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -44,7 +44,7 @@ public class GenericMessageConverter extends SimpleMessageConverter {
* Create a new instance with a default {@link ConversionService}. * Create a new instance with a default {@link ConversionService}.
*/ */
public GenericMessageConverter() { public GenericMessageConverter() {
this.conversionService = new DefaultConversionService(); this.conversionService = DefaultConversionService.getSharedInstance();
} }
/** /**

View File

@@ -76,7 +76,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
* values are not expected to contain expressions * values are not expected to contain expressions
*/ */
protected AbstractNamedValueMethodArgumentResolver(ConversionService cs, ConfigurableBeanFactory beanFactory) { protected AbstractNamedValueMethodArgumentResolver(ConversionService cs, ConfigurableBeanFactory beanFactory) {
this.conversionService = (cs != null ? cs : new DefaultConversionService()); this.conversionService = (cs != null ? cs : DefaultConversionService.getSharedInstance());
this.configurableBeanFactory = beanFactory; this.configurableBeanFactory = beanFactory;
this.expressionContext = (beanFactory != null ? new BeanExpressionContext(beanFactory, null) : null); this.expressionContext = (beanFactory != null ? new BeanExpressionContext(beanFactory, null) : null);
} }