0

In Spring (Boot) I can externalize annotation values to application / environment properties using the ${...} syntax:

@RequestMapping("${some.path.property}")

I can also map a controller to more than one path:

@RequestMapping("/one", "/two")

How do I combine the two? I would like to define a list of paths in my properties, either as comma-separated /one, /two or (preferably) as a list in my application.yaml:

some.path.property:
    - /one
    - /two

But how can I interpolate either kind of list into the annotation?

@RequestMapping(???)

Edit: I couldn't figure out how to read the entire list from YAML (maybe because it's turned into separate properties some.path.property[0], some.path.property[1]... at YAML parse time?)

For the simpler case of a single CSV property, say:

some.csv.property: /one, /two

I can use a property substitution: "${some.csv.property}" or an explicit SpEL split: "#{'${some.csv.property}'.split('[, ]+')}" to convert it into an array, but in both cases it only work for @Value annotations. If I try it on @RequestMapping, I always end up with a single path.


Edit2: I can do this, where -- is just a random string that is not a valid path, but it's super ugly:

@RequestMapping(
    "${some.path.property[0]:--}",
    "${some.path.property[1]:--}",
    "${some.path.property[2]:--}",
    "${some.path.property[3]:--}",
    "${some.path.property[4]:--}",
    "${some.path.property[5]:--}",
    "${some.path.property[6]:--}",
    "${some.path.property[7]:--}",
    "${some.path.property[8]:--}",
    "${some.path.property[9]:--}"
)
Tobia
  • 17,856
  • 6
  • 74
  • 93

3 Answers3

1

Have You tried this?:

some:
  path:
    property: /one, /two

And then

@RequestMapping("${some.path.property}") 

Based on this answer https://stackoverflow.com/a/41462567/7425783 it should work fine

  • Yes, I tried it (with and without a space after the comma) and it doesn't work. I'm on the latest Spring Boot 2.1.2 – Tobia Mar 12 '19 at 13:40
  • To expand, I tried setting an array from a CSV property and it works: `@Value("${some.csv.property}") String[] myValue;` but when I use the same property for an annotation itself, it is not split on commas, but is just taken as a single string: `@RequestMapping("${some.csv.property}")` sets a single path with value `"/one, /two"` – Tobia Mar 12 '19 at 13:58
  • I figured out how to use SpEL to explicitly split a string into an array: `"#{'${some.csv.property}'.split('[, ]+')}"` but again, it works for `@Value` but not for `@RequestMapping` – Tobia Mar 12 '19 at 14:06
1

If you have a yaml property file (don't repeat your self principle :) ), you can do it like so:

some:
  path:
    property:
      one: /path1
      two: /path2

If you're using @GetMapping ( or @RequestMapping ) you can do it like this in your controller :

@GetMapping(value={"${some.path.property.one}", "${some.path.property.two}"})

And here is the log

 Mapped "{[/path1 || /path2],methods=[GET]}" onto public java.util.List<com.zero.SimpleController> com.zero.SimpleController.hello()
Abdelghani Roussi
  • 2,707
  • 2
  • 21
  • 39
  • Yes, I have `application.yaml`. I can do that, but then I cannot change the **number** of paths from the config file – Tobia Mar 12 '19 at 13:41
  • I think @Michal solution is more suited for your case, the only issue is that you can't inherit path from super class ( if you have an `AbstractController` with `RequestMapping("/api/v1" )` and your `SimpleController extends AbstractController`), in this case either you set the full path in your property file or prefix/suffix the value from property file with another part of the complete path – Abdelghani Roussi Mar 12 '19 at 13:50
  • In my case don't have the issue of inheriting the path from a super class. But @Michal's solution is simply not working. I can use it to initialize an array: `@Value("${some.csv.property}") String[] myValue;` but not an annotation value itself. In that case the string is not split into an array. – Tobia Mar 12 '19 at 13:56
0

You can try create HandlerMapping to add urls, here is just an example to use SimpleUrlHandlerMapping

@RestController
public class WelcomeController {

    public String ping() {
        return "pong";
    }
}

@SpringBootApplication
@Slf4j
@RestController
public class StackOverflowApplication {

    @Autowired
    WelcomeController welcomeController;

    @Value("${paths}")
    List<String> paths;


    public static void main(String[] args) {
        SpringApplication.run(StackOverflowApplication.class, args);
    }



    @Bean
    public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
        SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
        Map<String, Object> map = new HashMap<>();
        final Method getUser = ReflectionUtils.findMethod(WelcomeController.class, "ping");
        final HandlerMethod handlerMethod = new HandlerMethod(welcomeController, getUser);
        for (String path : paths) {
            map.put(path, handlerMethod);
        }
        simpleUrlHandlerMapping.setUrlMap(map);
        simpleUrlHandlerMapping.setOrder(Ordered.HIGHEST_PRECEDENCE);

        return simpleUrlHandlerMapping;
    }


}

yml file

paths: ping, ping1, ping2, ping3

Here is the code in github

chaoluo
  • 2,596
  • 1
  • 17
  • 29