javaspringspring-bootspring-propertiesproperty-placeholder

Post-processing YAML properties in Spring based on prefix to retrieve property from REST service


I have a Spring boot configuration YAML with something like

spring:
  application:
    name: my-app
a: a literal
b: <<external due to special first and last chars>>

What i'm trying to do is to add some kind of resolver that will detect that the value of b is of the form <<X>> and will trigger retrieving that value from an external rest api to overwrite in memory the value that was in the YAML before it gets passed to the bean that holds the configurations at runtime

I tried and failed using an EnvironmentPostProcessor because I can't get ahold of the actual property values, just the property sources, so I can't post-process the values.

What currently works for me is in the @Configuration bean that holds the fields a and b, implement something in the setters to detect if the value that spring is trying to set starts with << and ends with >> and if so, overwrite what gets loaded into the pojo with the version that i retrieve from the rest api. This is not ideal because I end up with a lot of duplication

What's the right way to implement something like this in Spring 5? I know spring properties support references to other properties using the syntax ${a} so there must be some mechanism that already allows to add custom placeholder resolvers


Solution

  • I ended up changing things a bit to mark the special properties. Then I created my own PropertySource kind of like @Andreas suggested. It was all inspired by org.springframework.boot.env.RandomValuePropertySource

    The trick was changing the special chars << and >> to the syntax used already by spring: ${}, but like the random resolver which uses ${random.int} I did something like ${rest.XXX}. What I didn't know before is that by doing that, Spring will invoke all the property sources a second time with a new property name coming from the placeholder value (rest.XXX in my previous example). This way in the property source I can handle the value if the name of the property starts with my prefix rest.

    Here is a simplified version of my solution

    public class MyPropertySource extends PropertySource<RestTemplate> {
      private static final String PREFIX = "rest.";
    
      public MyPropertySource() {
        super(MyPropertySource.class.getSimpleName());
      }
    
      @Override
      public Object getProperty(@Nonnull String name) {
        String result = null;
        if (name.startsWith(PREFIX)) {
            result = getValueFromRest(name.substring(PREFIX.length()));
        }
    
        return result;
      }
    }
    

    Finally, to register the property source I used an EnvironmentPostProcessor as described here. I couldn't find a simpler way that doesn't entail maintaining a new file META-INF/spring.factories