Skip to content
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-projects-issues opened this issue Dec 18, 2018 · 5 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: regression A bug that is also a regression
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

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-projects-issues
Copy link
Collaborator Author

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-projects-issues
Copy link
Collaborator Author

ssundell commented

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

@spring-projects-issues
Copy link
Collaborator Author

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-projects-issues
Copy link
Collaborator Author

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-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues spring-projects-issues added type: regression A bug that is also a regression status: backported An issue that has been backported to maintenance branches in: core Issues in core modules (aop, beans, core, context, expression) labels Jan 11, 2019
@spring-projects-issues spring-projects-issues added this to the 5.1.4 milestone Jan 11, 2019
This was referenced Jan 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

2 participants