Uploaded image for project: 'Spring Framework'
  1. Spring Framework
  2. SPR-12297

Provider<...> declaration for @Value method argument fails with TypeMismatchException

    Details

    • Type: Bug
    • Status: Closed
    • Priority: Major
    • Resolution: Complete
    • Affects Version/s: 3.2.11, 4.0.7, 4.1.1
    • Fix Version/s: 3.2.12, 4.0.8, 4.1.2
    • Component/s: Core
    • Labels:
      None
    • Last commented by a User:
      false

      Description

      Would be very useful if Spring allowed for the injection of javax.inject.Provider along with @Value annotation for properties that could change at Runtime.

      public class Foo {
        private final Provider<Integer> changeableIntegerProperty;
       
        public Foo(Provider<Integer> changeableIntegerProperty) {
          this.changeableIntegerProperty = changeableIntegerProperty;
       }
       
       public void someOperation() {
           changeableIntegerProperty.get(); // Gets current value of the property
       }
      }
       
      @Configuration
      public class FooConfig {
        @Bean
         public Foo foo(@Value("${fooProp}") Provider<Integer> fooProp) {
              return new Foo(fooProp);
         }
      }

      If the same code shown above were attempted now, a TypeMismatchException gets thrown in TypeConverterSupport due to the nesting level of the parameter not set to 1.

      My request for improvement is to allow the injection of java.inject.Provider along with @Value.
      Thanks.

        Issue Links

          Activity

          Hide
          juergen.hoeller Juergen Hoeller added a comment -

          As of SPR-6079, this should work already, so I'll consider this as a bug. I suppose it just fails when type conversion is necessary... Anyway, if it's easy enough to fix, this is a candidate for a backport to 4.0.8 and 3.2.12 as well.

          Juergen

          Show
          juergen.hoeller Juergen Hoeller added a comment - As of SPR-6079 , this should work already, so I'll consider this as a bug. I suppose it just fails when type conversion is necessary... Anyway, if it's easy enough to fix, this is a candidate for a backport to 4.0.8 and 3.2.12 as well. Juergen
          Hide
          saacharya Sanjay Acharya added a comment -

          Thanks Juergen. This appears broken in 4.1.1 as well. My dirty workaround was to trick the TypeConverter with a custom one with the nesting level:

          public class CustomTypeConverter implements TypeConverter {
            private SimpleTypeConverter simpleTypeConverter;
           
            public CustomTypeConverter() {
              simpleTypeConverter = new SimpleTypeConverter();
            }
           
            public <T> T convertIfNecessary(Object newValue, Class<T> requiredType,
              MethodParameter methodParam) throws IllegalArgumentException {
              Type type = methodParam.getGenericParameterType();
           
              MethodParameter parameterTarget = null;
           
              if (type instanceof ParameterizedType) {
                ParameterizedType paramType = ParameterizedType.class.cast(type);
                Type rawType = paramType.getRawType();
           
                if (rawType.equals(Provider.class) && methodParam.hasParameterAnnotation(Value.class)) { // Do this only if @Value and @Provider exist on the MethodParam
                  parameterTarget = new MethodParameter(methodParam); // Don't fiddle with original, send a new MethodParameter downstream
                  parameterTarget.decreaseNestingLevel();
                }
              }
              return simpleTypeConverter.convertIfNecessary(newValue, requiredType, parameterTarget); 
            }

          Setting the above as the TypeConverter default seems to get the code to work. I don't think this is the cleanest way to handle the same.

          Show
          saacharya Sanjay Acharya added a comment - Thanks Juergen. This appears broken in 4.1.1 as well. My dirty workaround was to trick the TypeConverter with a custom one with the nesting level: public class CustomTypeConverter implements TypeConverter { private SimpleTypeConverter simpleTypeConverter;   public CustomTypeConverter() { simpleTypeConverter = new SimpleTypeConverter(); }   public <T> T convertIfNecessary(Object newValue, Class<T> requiredType, MethodParameter methodParam) throws IllegalArgumentException { Type type = methodParam.getGenericParameterType();   MethodParameter parameterTarget = null;   if (type instanceof ParameterizedType) { ParameterizedType paramType = ParameterizedType.class.cast(type); Type rawType = paramType.getRawType();   if (rawType.equals(Provider.class) && methodParam.hasParameterAnnotation(Value.class)) { // Do this only if @Value and @Provider exist on the MethodParam parameterTarget = new MethodParameter(methodParam); // Don't fiddle with original, send a new MethodParameter downstream parameterTarget.decreaseNestingLevel(); } } return simpleTypeConverter.convertIfNecessary(newValue, requiredType, parameterTarget); } Setting the above as the TypeConverter default seems to get the code to work. I don't think this is the cleanest way to handle the same.
          Hide
          saacharya Sanjay Acharya added a comment -

          I am surprised if the String version even worked, my simple test using 4.1.1.RELEASE, seems to fail with String as the Provider type:

          public class SpringTest {
            @Test
            public void test() {
              AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
              context.register(SimpleConfig.class);
              context.refresh();
              
              context.getBean(Foo.class).print();   
              
              context.close();
            }
            
            public static class SimpleConfig {
              @Bean(name = "props")
              public PropertyPlaceholderConfigurer propertyPlaceHolderConfigurer() {
                PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
                Resource location = new ClassPathResource("appProperties.properties");
                configurer.setLocation(location);
                return configurer;
              }
           
              @Bean
              public Foo foo(@Value("${foo}") Provider<String> propValue,
                @Value("${bar}") Provider<String> booleanVal) {
                return new Foo(propValue, booleanVal);
              }
            }
            
            public static class Foo {
              private final Provider<String> propVal;
              private final Provider<String> booleanVal;
              
              public Foo(Provider<String> propValue, Provider<String> booleanVal) {
                this.propVal = propValue;
                this.booleanVal = booleanVal;
              }
              
              public void print() {
                System.out.println(propVal.get() + "," + booleanVal.get());
              }    
            }
          }

          Where app.properties is simply:

          foo=5
          bar=false

          If you set the TypeConverter to the one I mentioned via the below, the test passes:

            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
           context.getBeanFactory().setTypeConverter(new CustomTypeConverter());

          Show
          saacharya Sanjay Acharya added a comment - I am surprised if the String version even worked, my simple test using 4.1.1.RELEASE, seems to fail with String as the Provider type: public class SpringTest { @Test public void test() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(SimpleConfig.class); context.refresh(); context.getBean(Foo.class).print(); context.close(); } public static class SimpleConfig { @Bean(name = "props") public PropertyPlaceholderConfigurer propertyPlaceHolderConfigurer() { PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer(); Resource location = new ClassPathResource("appProperties.properties"); configurer.setLocation(location); return configurer; }   @Bean public Foo foo(@Value("${foo}") Provider<String> propValue, @Value("${bar}") Provider<String> booleanVal) { return new Foo(propValue, booleanVal); } } public static class Foo { private final Provider<String> propVal; private final Provider<String> booleanVal; public Foo(Provider<String> propValue, Provider<String> booleanVal) { this.propVal = propValue; this.booleanVal = booleanVal; } public void print() { System.out.println(propVal.get() + "," + booleanVal.get()); } } } Where app.properties is simply: foo=5 bar=false If you set the TypeConverter to the one I mentioned via the below, the test passes: AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.getBeanFactory().setTypeConverter(new CustomTypeConverter());
          Hide
          juergen.hoeller Juergen Hoeller added a comment -

          This seems to be a 4.x regression introduced through SPR-9499, only failing for Provider-declared @Value method arguments but not for fields. And as it always is, we only had tests for such scenarios with fields...

          Juergen

          Show
          juergen.hoeller Juergen Hoeller added a comment - This seems to be a 4.x regression introduced through SPR-9499 , only failing for Provider -declared @Value method arguments but not for fields. And as it always is, we only had tests for such scenarios with fields... Juergen
          Hide
          juergen.hoeller Juergen Hoeller added a comment -

          Fixing that nestingLevel assertion makes it work, so it is indeed an accidental side effect from SPR-9499. Fixed for 4.1.2; to be backported to 4.0.8.

          For the time being, it'll work fine for fields, just not for method arguments...

          Juergen

          Show
          juergen.hoeller Juergen Hoeller added a comment - Fixing that nestingLevel assertion makes it work, so it is indeed an accidental side effect from SPR-9499 . Fixed for 4.1.2; to be backported to 4.0.8. For the time being, it'll work fine for fields, just not for method arguments... Juergen
          Hide
          saacharya Sanjay Acharya added a comment -

          Great. Thanks much for the rapid turn around. We will look toward the new release.

          Show
          saacharya Sanjay Acharya added a comment - Great. Thanks much for the rapid turn around. We will look toward the new release.
          Hide
          juergen.hoeller Juergen Hoeller added a comment -

          Actually, this fails in 3.2.11 too, just with a fix necessary in a different place. Backported to 3.2.12 as well now.

          Juergen

          Show
          juergen.hoeller Juergen Hoeller added a comment - Actually, this fails in 3.2.11 too, just with a fix necessary in a different place. Backported to 3.2.12 as well now. Juergen

            People

            • Assignee:
              juergen.hoeller Juergen Hoeller
              Reporter:
              saacharya Sanjay Acharya
              Last updater:
              St├ęphane Nicoll
            • Votes:
              1 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:
                Days since last comment:
                3 years, 2 weeks, 1 day ago