Make getSharedInstance() unmodifiable
Update `ApplicationConversionService.getSharedInstance()` so that the instance returned is unmodifiable and converters cannot be added or removed from it. Closes gh-26088
This commit is contained in:
		
							parent
							
								
									47709ec0e4
								
							
						
					
					
						commit
						5581ec0e29
					
				|  | @ -60,8 +60,6 @@ import org.springframework.context.support.AbstractApplicationContext; | |||
| import org.springframework.context.support.GenericApplicationContext; | ||||
| import org.springframework.core.GenericTypeResolver; | ||||
| import org.springframework.core.annotation.AnnotationAwareOrderComparator; | ||||
| import org.springframework.core.convert.ConversionService; | ||||
| import org.springframework.core.convert.support.ConfigurableConversionService; | ||||
| import org.springframework.core.env.CommandLinePropertySource; | ||||
| import org.springframework.core.env.CompositePropertySource; | ||||
| import org.springframework.core.env.ConfigurableEnvironment; | ||||
|  | @ -512,8 +510,7 @@ public class SpringApplication { | |||
| 	 */ | ||||
| 	protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { | ||||
| 		if (this.addConversionService) { | ||||
| 			ConversionService conversionService = ApplicationConversionService.getSharedInstance(); | ||||
| 			environment.setConversionService((ConfigurableConversionService) conversionService); | ||||
| 			environment.setConversionService(new ApplicationConversionService()); | ||||
| 		} | ||||
| 		configurePropertySources(environment, args); | ||||
| 		configureProfiles(environment, args); | ||||
|  | @ -633,7 +630,7 @@ public class SpringApplication { | |||
| 			} | ||||
| 		} | ||||
| 		if (this.addConversionService) { | ||||
| 			context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance()); | ||||
| 			context.getBeanFactory().setConversionService(context.getEnvironment().getConversionService()); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ | |||
| 
 | ||||
| package org.springframework.boot.convert; | ||||
| 
 | ||||
| import java.lang.annotation.Annotation; | ||||
| import java.util.LinkedHashSet; | ||||
| import java.util.Set; | ||||
| 
 | ||||
|  | @ -23,11 +24,13 @@ import org.springframework.beans.factory.ListableBeanFactory; | |||
| import org.springframework.core.convert.ConversionService; | ||||
| import org.springframework.core.convert.TypeDescriptor; | ||||
| import org.springframework.core.convert.converter.Converter; | ||||
| import org.springframework.core.convert.converter.ConverterFactory; | ||||
| import org.springframework.core.convert.converter.ConverterRegistry; | ||||
| import org.springframework.core.convert.converter.GenericConverter; | ||||
| import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; | ||||
| import org.springframework.core.convert.support.ConfigurableConversionService; | ||||
| import org.springframework.core.convert.support.DefaultConversionService; | ||||
| import org.springframework.format.AnnotationFormatterFactory; | ||||
| import org.springframework.format.Formatter; | ||||
| import org.springframework.format.FormatterRegistry; | ||||
| import org.springframework.format.Parser; | ||||
|  | @ -52,15 +55,96 @@ public class ApplicationConversionService extends FormattingConversionService { | |||
| 
 | ||||
| 	private static volatile ApplicationConversionService sharedInstance; | ||||
| 
 | ||||
| 	private boolean unmodifiable; | ||||
| 
 | ||||
| 	public ApplicationConversionService() { | ||||
| 		this(null); | ||||
| 	} | ||||
| 
 | ||||
| 	public ApplicationConversionService(StringValueResolver embeddedValueResolver) { | ||||
| 		this(embeddedValueResolver, false); | ||||
| 	} | ||||
| 
 | ||||
| 	private ApplicationConversionService(StringValueResolver embeddedValueResolver, boolean unmodifiable) { | ||||
| 		if (embeddedValueResolver != null) { | ||||
| 			setEmbeddedValueResolver(embeddedValueResolver); | ||||
| 		} | ||||
| 		configure(this); | ||||
| 		this.unmodifiable = unmodifiable; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void addPrinter(Printer<?> printer) { | ||||
| 		assertModifiable(); | ||||
| 		super.addPrinter(printer); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void addParser(Parser<?> parser) { | ||||
| 		assertModifiable(); | ||||
| 		super.addParser(parser); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void addFormatter(Formatter<?> formatter) { | ||||
| 		assertModifiable(); | ||||
| 		super.addFormatter(formatter); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) { | ||||
| 		assertModifiable(); | ||||
| 		super.addFormatterForFieldType(fieldType, formatter); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void addConverter(Converter<?, ?> converter) { | ||||
| 		assertModifiable(); | ||||
| 		super.addConverter(converter); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser) { | ||||
| 		assertModifiable(); | ||||
| 		super.addFormatterForFieldType(fieldType, printer, parser); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void addFormatterForFieldAnnotation( | ||||
| 			AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory) { | ||||
| 		assertModifiable(); | ||||
| 		super.addFormatterForFieldAnnotation(annotationFormatterFactory); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, | ||||
| 			Converter<? super S, ? extends T> converter) { | ||||
| 		assertModifiable(); | ||||
| 		super.addConverter(sourceType, targetType, converter); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void addConverter(GenericConverter converter) { | ||||
| 		assertModifiable(); | ||||
| 		super.addConverter(converter); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void addConverterFactory(ConverterFactory<?, ?> factory) { | ||||
| 		assertModifiable(); | ||||
| 		super.addConverterFactory(factory); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void removeConvertible(Class<?> sourceType, Class<?> targetType) { | ||||
| 		assertModifiable(); | ||||
| 		super.removeConvertible(sourceType, targetType); | ||||
| 	} | ||||
| 
 | ||||
| 	private void assertModifiable() { | ||||
| 		if (this.unmodifiable) { | ||||
| 			throw new UnsupportedOperationException("This ApplicationConversionService cannot be modified"); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
|  | @ -101,7 +185,7 @@ public class ApplicationConversionService extends FormattingConversionService { | |||
| 			synchronized (ApplicationConversionService.class) { | ||||
| 				sharedInstance = ApplicationConversionService.sharedInstance; | ||||
| 				if (sharedInstance == null) { | ||||
| 					sharedInstance = new ApplicationConversionService(); | ||||
| 					sharedInstance = new ApplicationConversionService(null, true); | ||||
| 					ApplicationConversionService.sharedInstance = sharedInstance; | ||||
| 				} | ||||
| 			} | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ import java.util.List; | |||
| import java.util.Locale; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| import org.assertj.core.api.ThrowableAssert.ThrowingCallable; | ||||
| import org.junit.jupiter.api.Test; | ||||
| 
 | ||||
| import org.springframework.context.ConfigurableApplicationContext; | ||||
|  | @ -34,6 +35,7 @@ import org.springframework.format.Parser; | |||
| import org.springframework.format.Printer; | ||||
| 
 | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| import static org.assertj.core.api.Assertions.assertThatExceptionOfType; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.verify; | ||||
| import static org.mockito.Mockito.verifyNoMoreInteractions; | ||||
|  | @ -113,6 +115,28 @@ class ApplicationConversionServiceTests { | |||
| 		assertThat(conversionService.isConvertViaObjectSourceType(sourceType, targetType)).isFalse(); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	void sharedInstanceCannotBeModified() { | ||||
| 		ApplicationConversionService instance = (ApplicationConversionService) ApplicationConversionService | ||||
| 				.getSharedInstance(); | ||||
| 		assertUnmodifiableExceptionThrown(() -> instance.addPrinter(null)); | ||||
| 		assertUnmodifiableExceptionThrown(() -> instance.addParser(null)); | ||||
| 		assertUnmodifiableExceptionThrown(() -> instance.addFormatter(null)); | ||||
| 		assertUnmodifiableExceptionThrown(() -> instance.addFormatterForFieldType(null, null)); | ||||
| 		assertUnmodifiableExceptionThrown(() -> instance.addConverter((Converter<?, ?>) null)); | ||||
| 		assertUnmodifiableExceptionThrown(() -> instance.addFormatterForFieldType(null, null, null)); | ||||
| 		assertUnmodifiableExceptionThrown(() -> instance.addFormatterForFieldAnnotation(null)); | ||||
| 		assertUnmodifiableExceptionThrown(() -> instance.addConverter(null, null, null)); | ||||
| 		assertUnmodifiableExceptionThrown(() -> instance.addConverter((GenericConverter) null)); | ||||
| 		assertUnmodifiableExceptionThrown(() -> instance.addConverterFactory(null)); | ||||
| 		assertUnmodifiableExceptionThrown(() -> instance.removeConvertible(null, null)); | ||||
| 	} | ||||
| 
 | ||||
| 	private void assertUnmodifiableExceptionThrown(ThrowingCallable throwingCallable) { | ||||
| 		assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(throwingCallable) | ||||
| 				.withMessage("This ApplicationConversionService cannot be modified"); | ||||
| 	} | ||||
| 
 | ||||
| 	static class ExampleGenericConverter implements GenericConverter { | ||||
| 
 | ||||
| 		@Override | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue