spring-framework icon indicating copy to clipboard operation
spring-framework copied to clipboard

Creating bean with annotated @Value("${test.date}") fails

Open Linda-pan opened this issue 3 years ago • 2 comments

Affects: 5.3.21


Background

TimeZone is Asia/Singapore snakeyml 1.31 spring-boot 2.6.9

Code

import java.util.Date;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
@Data
public class TestDateTime {
    @Value("${test.date:2022-10-13 00:00:00}")
    private Date test;   

}

application.yml

     test:
         date: 2022-10-13 00:00:00

Error

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testDateTime': Unsatisfied dependency expressed through field 'test'; nested exception is org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.util.Date'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.beans.factory.annotation.Value java.util.Date] for value 'Thu Oct 13 08:00:00 SGT 2022'; nested exception is java.lang.IllegalArgumentException
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:659) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:639) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1431) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.21.jar:5.3.21]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.21.jar:5.3.21]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.6.9.jar:2.6.9]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:745) [spring-boot-2.6.9.jar:2.6.9]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:420) [spring-boot-2.6.9.jar:2.6.9]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) [spring-boot-2.6.9.jar:2.6.9]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1317) [spring-boot-2.6.9.jar:2.6.9]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) [spring-boot-2.6.9.jar:2.6.9]
	at com.jpan.objectMapper.demo.ObjectMapperApplication.main(ObjectMapperApplication.java:13) [classes/:na]
Caused by: org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.util.Date'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.beans.factory.annotation.Value java.util.Date] for value 'Thu Oct 13 08:00:00 SGT 2022'; nested exception is java.lang.IllegalArgumentException
	at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:79) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1339) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1311) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:656) ~[spring-beans-5.3.21.jar:5.3.21]
	... 20 common frames omitted
Caused by: org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.beans.factory.annotation.Value java.util.Date] for value 'Thu Oct 13 08:00:00 SGT 2022'; nested exception is java.lang.IllegalArgumentException
	at org.springframework.core.convert.support.ObjectToObjectConverter.convert(ObjectToObjectConverter.java:119) ~[spring-core-5.3.21.jar:5.3.21]
	at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41) ~[spring-core-5.3.21.jar:5.3.21]
	at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:192) ~[spring-core-5.3.21.jar:5.3.21]
	at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:129) ~[spring-beans-5.3.21.jar:5.3.21]
	at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:73) ~[spring-beans-5.3.21.jar:5.3.21]
	... 23 common frames omitted
Caused by: java.lang.IllegalArgumentException: null
	at java.util.Date.parse(Date.java:617) ~[na:1.8.0_292]
	at java.util.Date.<init>(Date.java:274) ~[na:1.8.0_292]
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_292]
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_292]
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_292]
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_292]
	at org.springframework.core.convert.support.ObjectToObjectConverter.convert(ObjectToObjectConverter.java:115) ~[spring-core-5.3.21.jar:5.3.21]
	... 27 common frames omitted

Disconnected from the target VM, address: '127.0.0.1:53797', transport: 'socket'

Process finished with exit code 1

Reason

When getting the configuration from snakeyml 1.31, the value of test.date is "Thu Oct 13 08:00:00 SGT 2022", meanwhile java.util.Date#parse() cannot resolve SGT time zone.

Linda-pan avatar Oct 13 '22 03:10 Linda-pan

I believe the value in your YAML configuration needs to be quoted. Can you please try that?

snicoll avatar Oct 13 '22 09:10 snicoll

if set config

 test:
   date: 2022-10-18 00:00:00

then error log shows

Failed to convert from type [java.lang.String] to type [@org.springframework.beans.factory.annotation.Value java.util.Date] for value 'Tue Oct 18 08:00:00 SGT 2022'; 

if set config

 test:
   date: "2022-10-18 00:00:00"

then error log shows

Failed to convert from type [java.lang.String] to type [@org.springframework.beans.factory.annotation.Value java.util.Date] for value '2022-10-18 00:00:00''; 

if not config and set default value to @Value。 @Value("${test.date:2022-10-13 00:00:00}")

then error log shows

Failed to convert from type [java.lang.String] to type [@org.springframework.beans.factory.annotation.Value java.util.Date] for value '2022-10-13 00:00:00'

I think there are two problems, one is that when the @Value is in a bean, the bean cannot support the default date value in the format "yyyy-MM-dd HH:mm:ss". Another is when the @Value is in a bean and region is similar to "Asia/Singapore",the time with timezone from environment config which resolved by snakeyaml may cause a parsing error.

These are pivotal code: get value with timezone org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(DefaultListableBeanFactory:1332)

1331          if (value instanceof String) {
1332   	          String strVal = resolveEmbeddedValue((String) value);
1333	          BeanDefinition bd = (beanName != null && containsBean(beanName) ?
1334			          getMergedBeanDefinition(beanName) : null);
1335	          value = evaluateBeanDefinitionString(strVal, bd);
1336         }

and create Instance org.springframework.core.convert.support.ObjectToObjectConverter#convert(ObjectToObjectConverter.java:115)

112        else if (executable instanceof Constructor) {
113		   Constructor<?> ctor = (Constructor<?>) executable;
114		   ReflectionUtils.makeAccessible(ctor);
115		   return ctor.newInstance(source);
116	     }

then go to java.util.Date#parse

Linda-pan avatar Oct 18 '22 10:10 Linda-pan

@Linda-pan Hi ,I added a unit test, you can look at this unit test, after testing can be found to be normal conversion。Since there are many types of time formats("yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy/MM/dd",) I am not sure if it is necessary to include the SampleDatePropertyEditor code in the test case as part of the Spring core code. @snicoll

The address of my updated unit test code: https://github.com/huifer/spring-framework/tree/issues-29314

huifer avatar Dec 23 '22 05:12 huifer

Thanks for helping out @huifer.

@Linda-pan Yes so it turns out that injecting a Date the way you do don't specify the date format that you want to use so it uses the default. If you want control over the date format, you need to add @DateTimeFormat and configure the pattern to use, something like:

public class TestDateTime {
    
    @Value("${test.date:2022-10-13 00:00:00}")
    @DateTimeFormat(pattern = "<format-here>")
    private Date test;   

}

snicoll avatar Aug 27 '23 15:08 snicoll