Search code examples
javaspringspring-el

Evaluating ${my.property} as a SpEL expression within a @Value annotation


Long story short:

Is there a way to interpret the string resulting from ${my.property} as a SpEL expression within a @Value annotation without using converters, e.g. something like @Value("#{${my.property}})?


I have an abstract factory (simplified) that lets me build some common objects that are part of the configuration of my system.

@Component
public class Factory {
  public Product makeVal(int x) { return new Product(5); }
}

In order to be more flexible, I'd like to let users write SpEL expressions in the app.properties file, so that the factory can directly be accessed:

my.property = @Factory.makeVal(12)

Now, in the class needing this property, to achieve my goal I wrote the following code.

@Value("#{${my.property}}")
private Product obj;

I thought that ${my.property} would be be macro-expanded and then evaluated by #{} as the corresponding SpEL expression, @Factory.makeVal(12) in the example above. Unfortunately, this wasn't the case, and loading the Spring context resulted in an error saying that it could not convert a string (the property's value ${my.property}) to the destination type Product.

Now, I solved this by writing a class implementing Converter<String, Product>, but it's very convoluted as I need there to programmatically evaluate the string as a SpEL expression by instantiating the ExpressionParser and so on.

But is there a simpler solution? Is there a single SpEL expression to be put in @Value annotations that lets me simply evaluate ${my.property} as a SpEL expression by itself, please?


Solution

  • Maybe it just a matter of replacing @Factory with factory in the property value. This test passes for me:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = { SpelTest.Config.class })
    public class SpelTest
    {
        @Value("#{${my.property}}")
        Product _product;
    
        @Test
        public void evaluating_spel_from_property_value() throws Exception
        {
            Assert.assertEquals(1234, _product.value);
        }
    
        @Component
        public static class Factory
        {
            public Product makeVal(int x) { return new Product(x); }
        }
    
        public static class Product
        {
            public final int value;
    
            public Product(final int value) { this.value = value; }
        }
    
        @Configuration
        @ComponentScan(basePackageClasses = SpelTest.class)
        public static class Config
        {
            @Bean
            public Factory factory() { return new Factory(); }
    
            @Bean
            public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
                final PropertySourcesPlaceholderConfigurer psc = new PropertySourcesPlaceholderConfigurer();
                final MutablePropertySources sources = new MutablePropertySources();
                sources.addFirst(new MockPropertySource()
                    .withProperty("my.property", 
                              "factory.makeVal(1234)"));
                psc.setPropertySources(sources);
                return psc;
            }
        }
    }