diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java index 1530b8daad..621a7f91f5 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java @@ -25,12 +25,12 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.ConverterRegistry; /** - * A specialization of {@link GenericConversionService} configured by default with - * converters appropriate for most environments. + * A specialization of {@link GenericConversionService} configured by default + * with converters appropriate for most environments. * *

Designed for direct instantiation but also exposes the static - * {@link #addDefaultConverters(ConverterRegistry)} utility method for ad hoc use against any - * {@code ConverterRegistry} instance. + * {@link #addDefaultConverters(ConverterRegistry)} utility method for ad-hoc + * use against any {@code ConverterRegistry} instance. * * @author Chris Beams * @author Juergen Hoeller @@ -39,6 +39,32 @@ import org.springframework.core.convert.converter.ConverterRegistry; */ public class DefaultConversionService extends GenericConversionService { + private static volatile DefaultConversionService sharedInstance; + + + /** + * Return a shared default {@code ConversionService} instance, + * lazily building it once needed. + *

NOTE: 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 * {@linkplain DefaultConversionService#addDefaultConverters(ConverterRegistry) default converters}. diff --git a/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java index 20d690b5a0..3bdf2b0489 100644 --- a/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java +++ b/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java @@ -22,8 +22,11 @@ import java.util.Set; import org.apache.commons.logging.Log; 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.DefaultConversionService; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.PropertyPlaceholderHelper; import org.springframework.util.SystemPropertyUtils; @@ -38,7 +41,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe protected final Log logger = LogFactory.getLog(getClass()); - protected ConfigurableConversionService conversionService = new DefaultConversionService(); + private volatile ConfigurableConversionService conversionService; private PropertyPlaceholderHelper nonStrictHelper; @@ -57,11 +60,21 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe @Override 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 public void setConversionService(ConfigurableConversionService conversionService) { + Assert.notNull(conversionService, "ConversionService must not be null"); this.conversionService = conversionService; } @@ -72,6 +85,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe */ @Override public void setPlaceholderPrefix(String placeholderPrefix) { + Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null"); this.placeholderPrefix = placeholderPrefix; } @@ -82,6 +96,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe */ @Override public void setPlaceholderSuffix(String placeholderSuffix) { + Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null"); this.placeholderSuffix = placeholderSuffix; } @@ -113,8 +128,10 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe @Override public void setRequiredProperties(String... requiredProperties) { - for (String key : requiredProperties) { - this.requiredProperties.add(key); + if (requiredProperties != null) { + 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 convertValueIfNecessary(Object value, Class 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, diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java index 8022f21e6e..c5f9073126 100644 --- a/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java +++ b/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java @@ -80,7 +80,7 @@ public class PropertySourcesPropertyResolver extends AbstractPropertyResolver { value = resolveNestedPlaceholders((String) value); } logKeyFound(key, propertySource, value); - return this.conversionService.convert(value, targetValueType); + return convertValueIfNecessary(value, targetValueType); } } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java index 253e4bb389..6243c2d754 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java @@ -36,8 +36,6 @@ import org.springframework.util.Assert; */ public class StandardTypeConverter implements TypeConverter { - private static volatile ConversionService defaultConversionService; - private final ConversionService conversionService; @@ -45,10 +43,7 @@ public class StandardTypeConverter implements TypeConverter { * Create a StandardTypeConverter for the default ConversionService. */ public StandardTypeConverter() { - if (defaultConversionService == null) { - defaultConversionService = new DefaultConversionService(); - } - this.conversionService = defaultConversionService; + this.conversionService = DefaultConversionService.getSharedInstance(); } /** diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java index 05986bb533..51833133f1 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java @@ -88,7 +88,7 @@ public class BeanPropertyRowMapper implements RowMapper { private boolean primitivesDefaultedForNullValue = false; /** 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 */ private Map mappedFields; diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/GenericMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/GenericMessageConverter.java index f9b5f4427a..df9e597a7f 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/GenericMessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/GenericMessageConverter.java @@ -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"); * 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}. */ public GenericMessageConverter() { - this.conversionService = new DefaultConversionService(); + this.conversionService = DefaultConversionService.getSharedInstance(); } /** diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java index ea033b7ef5..8f26c0c9e8 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java @@ -76,7 +76,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle * values are not expected to contain expressions */ protected AbstractNamedValueMethodArgumentResolver(ConversionService cs, ConfigurableBeanFactory beanFactory) { - this.conversionService = (cs != null ? cs : new DefaultConversionService()); + this.conversionService = (cs != null ? cs : DefaultConversionService.getSharedInstance()); this.configurableBeanFactory = beanFactory; this.expressionContext = (beanFactory != null ? new BeanExpressionContext(beanFactory, null) : null); }