New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@Value Optional<...> field injection fails in case of registered ConversionService [SPR-17607] #22139

Closed
spring-issuemaster opened this Issue Dec 18, 2018 · 5 comments

Comments

Projects
None yet
2 participants
@spring-issuemaster
Copy link
Collaborator

spring-issuemaster commented Dec 18, 2018

ssundell opened SPR-17607 and commented

We've used Optional fields in our project to handle configurable features where configuration properties can be defined or left out. However, after upgrading from Springboot 1.5.3 to Springboot 2.1 and Spring Framework 5.x value injection fails.

For example, we've used annotations such as this:

@Value("${optional.value:#{null}}")
private Optional<String> optionalConfiguration;

This initializes fine, but when you actually try to use the Optional, it fails with an exception:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'nameOfTheBean': Invocation of init method failed; nested exception is java.lang.ClassCastException: java.util.Optional cannot be cast to java.lang.String
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:139)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:419)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1737)

 
In other words: the type of the field is evaluated twice, and as a result, the post processor injects the intializing bean with Optional<Optional<String>> instead of Optional<String>.

The flow seems to be like this:

  1. DefaultListableBeanFactory.resolveDependency recognized Optional and calls
  2. DefaultListableBeanFactory.createOptionalDependency which then resolves the dependency and, through a couple of methods, ends up calling
  3. TypeConverterDelegate.convertIfNecessary with both requiredType == String and field's TypeDescriptor, which then identifies the type as Optional<String>. This ignores the requiredType enough so that it ends up calling
  4. ObjectToOptionalConverter, which, naturally, converts the provided property String into Optional<String>.
  5. This brings us back to DefaultListableBeanFactory.createOptionalDependency, which now has Optional<String> created in step 4 in its hands.... and it wraps it inside another Optional by calling Optional.ofNullable.

Affects: 5.1.3

Attachments:

Referenced from: commits e714fc5, 183f367, 9cb5369

Backported to: 5.0.12, 4.3.22

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator

spring-issuemaster commented Dec 19, 2018

Juergen Hoeller commented

I haven't managed to reproduce this: All test case variants that I tried resolve fine for me, with and without actual conversion needs in an Optional<...> structure. Please try to narrow it down a bit, ideally providing a test case for it...

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator

spring-issuemaster commented Dec 19, 2018

ssundell commented

Hmm, strange. I attached a simple demo project which produces the Exception above.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator

spring-issuemaster commented Dec 20, 2018

Juergen Hoeller commented

Thanks, that helped! I was trying to isolate a core framework test case, and it is Boot's default ConversionService that really triggers this behavior. As per your analysis above, ObjectToOptionalConverter is involved here which only appears in case of a ConversionService registered with the application context... which is indeed the case in Boot 2.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator

spring-issuemaster commented Dec 20, 2018

Juergen Hoeller commented

This turns out to be specific to the use of @Value on fields in combination with a ConversionService. Nesting is properly adapted for method/constructor parameters but unfortunately not for fields here. I'll fix this for 5.1.4, to be backported to 5.0.12 and 4.3.22.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator

spring-issuemaster commented Jan 8, 2019

Juergen Hoeller commented

DependencyDescriptor fully supports TypeDescriptor resolution for fields now. This allows for proper nested type conversion in @Value Optional fields analogous to method parameters, through a new TypeDescriptor-based method in the TypeConverter SPI in 5.1.4. As an additional and less involved measure that is worth backporting, DefaultListableBeanFactory defensively checks for pre-converted Optional wrappers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment