Is it possible to dynamically set RequestMappings in Spring MVC?
I spent a long time trying to get this to work, but finally managed to find a solution that returns a ResponseEntity
instead of the older ModelAndView
. This solution also has the added benefit of avoiding any explicit interaction with Application Context
.
Endpoint Service
@Service
public class EndpointService {
@Autowired
private QueryController queryController;
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
public void addMapping(String urlPath) throws NoSuchMethodException {
RequestMappingInfo requestMappingInfo = RequestMappingInfo
.paths(urlPath)
.methods(RequestMethod.GET)
.produces(MediaType.APPLICATION_JSON_VALUE)
.build();
requestMappingHandlerMapping.
registerMapping(requestMappingInfo, queryController,
QueryController.class.getDeclaredMethod("handleRequests")
);
}
}
Controller to handle newly mapped requests
@Controller
public class QueryController {
public ResponseEntity<String> handleRequests() throws Exception {
//Do clever stuff here
return new ResponseEntity<>(HttpStatus.OK);
}
}
Following construct configures and implements handler methods in a single class.
It is a combination of dynamic and static mapping - all the MVC annotations can be used like @RequestParam
, @PathVariable
, @RequestBody
, etc.
BTW: @RestController
annotation creates bean out of the class and adds @ResponseBody
to every handler method so that it does not have to be done manually.
@RestController
public class MyController {
@Inject
private RequestMappingHandlerMapping handlerMapping;
/***
* Register controller methods to various URLs.
*/
@PostConstruct
public void init() throws NoSuchMethodException {
/**
* When "GET /simpleHandler" is called, invoke, parametrizedHandler(String,
* HttpServletRequest) method.
*/
handlerMapping.registerMapping(
RequestMappingInfo.paths("/simpleHandler").methods(RequestMethod.GET)
.produces(MediaType.APPLICATION_JSON_VALUE).build(),
this,
// Method to be executed when above conditions apply, i.e.: when HTTP
// method and URL are called)
MyController.class.getDeclaredMethod("simpleHandler"));
/**
* When "GET /x/y/z/parametrizedHandler" is called invoke
* parametrizedHandler(String, HttpServletRequest) method.
*/
handlerMapping.registerMapping(
RequestMappingInfo.paths("/x/y/z/parametrizedHandler").methods(RequestMethod.GET)
.produces(MediaType.APPLICATION_JSON_VALUE).build(),
this,
// Method to be executed when above conditions apply, i.e.: when HTTP
// method and URL are called)
MyController.class.getDeclaredMethod("parametrizedHandler", String.class, HttpServletRequest.class));
}
// GET /simpleHandler
public List<String> simpleHandler() {
return Arrays.asList("simpleHandler called");
}
// GET /x/y/z/parametrizedHandler
public ResponseEntity<List<String>> parametrizedHandler(
@RequestParam(value = "param1", required = false) String param1,
HttpServletRequest servletRequest) {
return ResponseEntity.ok(Arrays.asList("parametrizedHandler called", param1));
}
}
Spring MVC performs URL mappings using implementations of the HandlerMapping
interface. The ones usually used out of the box are the default implementations, namely SimpleUrlHandlerMapping
, BeanNameUrlHandlerMapping
and DefaultAnnotationHandlerMapping
.
If you want to implement your own mapping mechanism, this is fairly easy to do - just implement that interface (or, perhaps more likely, extend AbstractUrlHandlerMapping
), declare the class as a bean in your context, and it will be consulted by DispatcherServlet
when a request needs to be mapped.
Note that you can have as many HandlerMapping
implementations as you like in the one context. They will be consulted in turn until one of them has a match.