How to set defaults for #RequestMapping? - java

I'm using #RestController with #RequestMapping annotations to define all of my servlets with spring-mvc.
Question: how can I define some defaults for those annotation, so I don't have to repeat the same configuration regarding eg consumes and produces?
I'd like to always apply the following config, without having to repeat it on each path:
#GetMapping(produces = {APPLICATION_XML_VALUE, APPLICATION_JSON_VALUE})
#PostMapping(
consumes = {APPLICATION_XML_VALUE, APPLICATION_JSON_VALUE},
produces = {APPLICATION_XML_VALUE, APPLICATION_JSON_VALUE})

Probably it's easiest to just create a custom #RestController annotation and use that on classlevel. Then I only have to repeat the #PostMapping(consumes...) mappings:
#Target(ElementType.TYPE)
#Retention(value=RUNTIME)
#RestController
#RequestMapping(produces = {APPLICATION_JSON_VALUE, APPLICATION_XML_VALUE})
public #interface DefaultRestController {
}
Usage like:
#DefaultRestController
public class MyServlet {
#GetMapping("/getmap") //inherits the 'produces' mapping
public void getmap() {
}
#PostMapping("/postmap", consumes = {APPLICATION_JSON_VALUE, APPLICATION_XML_VALUE})
public void postmap() {
}
}
Better than nothing.

The target for RequestMapping annotation could be either a method or class. It can be used instead of GetMapping and PostMapping annotations that target only methods.
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/GetMapping.html
Specifically, #GetMapping is a composed annotation that acts as a
shortcut for #RequestMapping(method = RequestMethod.GET).
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/PostMapping.html
Specifically, #PostMapping is a composed annotation that acts as a
shortcut for #RequestMapping(method = RequestMethod.POST).
Assuming your Controller Name is HelloController, add the RequestMapping annotation with appropriate methods at Class level so that it applies automatically to all the paths.
#Controller
#RequestMapping(method={RequestMethod.GET,RequestMethod.POST}, consumes = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE },produces = { MediaType.APPLICATION_XML_VALUE,MediaType.APPLICATION_JSON_VALUE },)
class HelloController{
}
This confgiuration can be overridden by annotating it in individual methods.

You can put an annotation on class. Here is an example:
#RestController
#RequestMapping(
consumes = {APPLICATION_XML_VALUE, APPLICATION_JSON_VALUE},
produces = {APPLICATION_XML_VALUE, APPLICATION_JSON_VALUE}
)
public class MyClass {
// after that you don't have to put any
// #RequestMapping default values before methods
}

Related

Difference between path and value attributes in #RequestMapping annotation

What is the difference between below two attributes and which one to use when?
#GetMapping(path = "/usr/{userId}")
public String findDBUserGetMapping(#PathVariable("userId") String userId) {
return "Test User";
}
#RequestMapping(value = "/usr/{userId}", method = RequestMethod.GET)
public String findDBUserReqMapping(#PathVariable("userId") String userId) {
return "Test User";
}
As mentioned in the comments (and the documentation), value is an alias to path. Spring often declares the value element as an alias to a commonly used element. In the case of #RequestMapping (and #GetMapping, ...) this is the path property:
This is an alias for path(). For example #RequestMapping("/foo") is equivalent to #RequestMapping(path="/foo").
The reasoning behind this is that the value element is the default when it comes to annotations, so it allows you to write code in a more concise way.
Other examples of this are:
#RequestParam (value → name)
#PathVariable (value → name)
...
However, aliases aren't limited to annotation elements only, because as you demonstrated in your example, #GetMapping is an alias for #RequestMapping(method = RequestMethod.GET).
Just looking for references of AliasFor in their code allows you to see that they do this quite often.
#GetMapping is a shorthand for #RequestMapping(method = RequestMethod.GET).
In your case.
#GetMapping(path = "/usr/{userId}") is a shorthand for #RequestMapping(value = "/usr/{userId}", method = RequestMethod.GET).
Both are equivalent. Prefer using shorthand #GetMapping over the more verbose alternative. One thing that you can do with #RequestMapping which you can't with #GetMapping is to provide multiple request methods.
#RequestMapping(value = "/path", method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT)
public void handleRequet() {
}
Use #RequestMapping when you need to provide multiple Http verbs.
Another usage of #RequestMapping is when you need to provide a top level path for a controller. For e.g.
#RestController
#RequestMapping("/users")
public class UserController {
#PostMapping
public void createUser(Request request) {
// POST /users
// create a user
}
#GetMapping
public Users getUsers(Request request) {
// GET /users
// get users
}
#GetMapping("/{id}")
public Users getUserById(#PathVariable long id) {
// GET /users/1
// get user by id
}
}
#GetMapping is an alias for #RequestMapping
#GetMapping is a composed annotation that acts as a shortcut for #RequestMapping(method = RequestMethod.GET).
value method is an alias for path method.
This is an alias for path(). For example #RequestMapping("/foo") is equivalent to #RequestMapping(path="/foo").
So both methods are similar in that sense.

Spring request mapping custom annotations - ambiguous mapping

I have the following Spring MVC Controller:
#RestController
#RequestMapping(value = "my-rest-endpoint")
public class MyController {
#GetMapping
public List<MyStuff> defaultGet() {
...
}
#GetMapping(params = {"param1=value1", "param2=value2"})
public MySpecificStuff getSpecific() {
...
}
#GetMapping(params = {"param1=value1", "param2=value3"})
public MySpecificStuff getSpecific2() {
return uiSchemas.getRelatedPartyUi();
}
}
What I need is to make it more generic using custom annotations:
#RestController
#RequestMapping(value = "my-rest-endpoint")
public class MyController {
#GetMapping
public List<MyStuff> defaultGet() {
...
}
#MySpecificMapping(param2 = "value2")
public MySpecificStuff getSpecific() {
...
}
#MySpecificMapping(param2 = "value3")
public MySpecificStuff getSpecific2() {
return uiSchemas.getRelatedPartyUi();
}
}
I know that Spring meta annotations could help me with that.
So I define the annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#RequestMapping(method = RequestMethod.GET, params = {"param1=value1"})
public #interface MySpecificMapping {
String param2() default "";
}
That alone won't do the trick.
So I add an interceptor to deal with that "param2":
public class MyInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// get annotation of the method
MySpecificMapping mySpecificMapping = handlerMethod.getMethodAnnotation(MySpecificMapping.class);
if (mySpecificMapping != null) {
// get the param2 value from the annotation
String param2 = mySpecificMapping.param2();
if (StringUtils.isNotEmpty(param2)) {
// match the query string with annotation
String actualParam2 = request.getParameter("param2");
return param2 .equals(actualParam2);
}
}
}
return true;
}
}
And include it into the Spring configuration of course.
That works fine but only if I have one such custom mapping per controller.
If I add two methods annotated with #MySpecificMapping even having different values of "param2" then I get an "ambiguous mapping" error of the application start:
java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'myController' method
public com.nailgun.MySpecificStuff com.nailgun.MyController.getSpecific2()
to {[/my-rest-endpoint],methods=[GET],params=[param1=value1]}: There is already 'myController' bean method
public com.nailgun.MySpecificStuff com.nailgun.MyController.getSpecific() mapped.
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.assertUniqueMethodMapping(AbstractHandlerMethodMapping.java:576)
- Application startup failed
I understand why it happens.
But can you help me to give Spring a hint that those are two different mappings?
I am using Spring Boot 1.4.3 with Spring Web 4.3.5
#AliasFor is annotation for do things like this.
Here is an example of custom annotation with #RequestMapping
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
#RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public #interface JsonGetMapping {
#AliasFor(annotation = RequestMapping.class, attribute = "value")
String value() default "";
}
and also example of use
#JsonGetMapping("/category/{categoryName}/books")
public List<Book> getBooksByCategory(#PathVariable("categoryName") String categoryName){
return bookRepository.getBooksByCategory(categoryName);
}
You can not bind annotations in the stack with their params and Spring will consider these two methods as methods with equal #RequestMapping.
But you could try make a trick: embed somehow your custom annotation enhancer before mapping builder and perform there annotations replacing:
Get all methods with annotation #MySpecificMapping:
MySpecificMapping myMapping = ...;
Read #RequestMapping annotation for each such method, let say it will be
RequestMapping oldMapping = ...;
Create new instance of the #RequestMapping class:
RequestMapping newMapping = new RequestMapping() {
// ... rest methods
#Override
public String[] params() {
// here merge params from old and MySpecificMapping:
String[] params = new String[oldMapping.params().length + 1];
// todo: copy old one
// ...
params[params.length-1] = myMapping.param2();
return params;
}
}
Forcly assign this new newMapping to each method correspondingly instead of oldMapping.
This is quite tricky and complex, but this is only one way to achieve what you want, I believe.
I think the best way around this is to move your #RequestMapping annotation to the method level instead of the class level.
The error Spring is giving you is because Spring is binding multiple handlers on one path which is invalid. Maybe give us an example of the URL's you'd like to expose so we have a better overview of what you're trying to build.

#JsonView doesn't work for me (Spring 4.1.5, Jackson 2.5.1)

I have a field in my Entity with #JsonView annotation:
#JsonView(View.Secure.class)
private String password;
Inside my controller:
#RequestMapping(method = RequestMethod.GET, produces = "application/json")
#JsonView(View.Secure.class)
public ResponseEntity<?> getAllUsers(){
return createUserListResponse();
}
My View class:
public class View {
public static class Secure {}
}
I've expected that response will contain only "password" field, but instead it contains nothing. When i remove annotation #JsonView(View.Secure.class) from Controller - it works as usual and returns all fields. What am i doing wrong? Is it required to add some additional configuration into Spring config?
I used this tutorial: https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring

#Path equivalent in spring-boot

Say I have controller with a method defined like this:
#RestController
#RequestMapping("/{user-id}/foo")
public class FooController {
#RequestMapping("bar")
public BarController someBarLogic() {
//
}
}
Is it possible to go further than {user-id}/foo/bar without specifying the whole root-path ? (Is there a way to relativize the path like #Path annotation in Jersey or an equivalent annotation in Spring-Boot ?)
It looks like you're looking for ant-style wildcards in path patterns. Have a look here:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-patterns
Accordingly you can define a RequestMapping like this
#RestController
#RequestMapping("/foo")
public class DemoController {
#RequestMapping(value = "/bar/**", method = RequestMethod.GET)
public String getDemo() {
return "Hello world!";
}
}
which will match /foo/bar/baz.
Ok, another example based on your comment below:
#RestController
public class DemoController {
#RequestMapping(value = "/**/baz/**", method = RequestMethod.GET)
public String getDemo() {
return "Hello world!";
}
}
This will match the same url as above, and also /foo/bar/baz/bar/foo

Automatically add "headers" and "produces" attributes to my #RequestMapping

I implemented a REST API via Spring MVC. Here is an example of a mapping:
#RequestMapping(value = "/videos", method = RequestMethod.GET, headers = "Accept=application/json", produces = "application/json")
There are many of them, so I wonder if it's possible to factorize the headers and produces attributes, so that I don't have to specify them in each mapping, in order to lighten my code?
The best would be a custom annotation which automatically sets the two attributes, for example:
#JsonRequestMapping(value = "/videos", method = RequestMethod.GET)
But I haven't been able to implement such one...
You can put #RequestMapping also on a class next to a method (see reference guide). If you want globally available attributes put a #RequestMapping on a class, this will be merged with the one on the method.
#Controller
#RequestMapping(headers = "Accept=application/json", produces = "application/json")
public class YourController { ... }
Then your method only contains the method and url.
#RequestMapping(value="/videos", method=RequestMethod.GET)
public Object someMethod(...) { ... }
You also might want to take a look at #RestController as that also configures some defaults for your controller. Like not needing a #ResponseBody anymore on your methods.
#RestController
public class YourController { ... }

Categories