With multiple Spring controllers that consume and produce application/json
, my code is littered with long annotations like:
@RequestMapping(value = "/foo", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
Is there a way to produce a "composite/inherited/aggregated" annotation with default values for consumes
and produces
, such that I could instead write something like:
@JSONRequestMapping(value = "/foo", method = RequestMethod.POST)
How do we define something like @JSONRequestMapping
above? Notice the value
and method
passed in just like in @RequestMapping
, also good to be able to pass in consumes
or produces
if the default isn't suitable.
I need to control what I'm returning. I want the produces
/consumes
annotation-methods so that I get the appropriate Content-Type
headers.
As of Spring 4.2.x, you can create custom mapping annotations, using @RequestMapping
as a meta-annotation. So:
Is there a way to produce a "composite/inherited/aggregated"
annotation with default values for consumes and produces, such that I
could instead write something like:
@JSONRequestMapping(value = "/foo", method = RequestMethod.POST)
Yes, there is such a way. You can create a meta annotation like following:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(consumes = "application/json", produces = "application/json")
public @interface JsonRequestMapping {
@AliasFor(annotation = RequestMapping.class, attribute = "value")
String[] value() default {};
@AliasFor(annotation = RequestMapping.class, attribute = "method")
RequestMethod[] method() default {};
@AliasFor(annotation = RequestMapping.class, attribute = "params")
String[] params() default {};
@AliasFor(annotation = RequestMapping.class, attribute = "headers")
String[] headers() default {};
@AliasFor(annotation = RequestMapping.class, attribute = "consumes")
String[] consumes() default {};
@AliasFor(annotation = RequestMapping.class, attribute = "produces")
String[] produces() default {};
}
Then you can use the default settings or even override them as you want:
@JsonRequestMapping(method = POST)
public String defaultSettings() {
return "Default settings";
}
@JsonRequestMapping(value = "/override", method = PUT, produces = "text/plain")
public String overrideSome(@RequestBody String json) {
return json;
}
You can read more about AliasFor
in spring's javadoc and github wiki.
The simple answer to your question is that there is no Annotation-Inheritance in Java. However, there is a way to use the Spring annotations in a way that I think will help solve your problem.
@RequestMapping is supported at both the type level and at the method level.
When you put @RequestMapping
at the type level, most of the attributes are 'inherited' for each method in that class. This is mentioned in the Spring reference documentation. Look at the api docs for details on how each attribute is handled when adding @RequestMapping
to a type. I've summarized this for each attribute below:
name
: Value at Type level is concatenated with value at method level using '#' as a separator.
value
: Value at Type level is inherited by method.
path
: Value at Type level is inherited by method.
method
: Value at Type level is inherited by method.
params
: Value at Type level is inherited by method.
headers
: Value at Type level is inherited by method.
consumes
: Value at Type level is overridden by method.
produces
: Value at Type level is overridden by method.
Here is a brief example Controller that showcases how you could use this:
package com.example;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(path = "/",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE,
method = {RequestMethod.GET, RequestMethod.POST})
public class JsonProducingEndpoint {
private FooService fooService;
@RequestMapping(path = "/foo", method = RequestMethod.POST)
public String postAFoo(@RequestBody ThisIsAFoo theFoo) {
fooService.saveTheFoo(theFoo);
return "http://myservice.com/foo/1";
}
@RequestMapping(path = "/foo/{id}", method = RequestMethod.GET)
public ThisIsAFoo getAFoo(@PathVariable String id) {
ThisIsAFoo foo = fooService.getAFoo(id);
return foo;
}
@RequestMapping(path = "/foo/{id}", produces = MediaType.APPLICATION_XML_VALUE, method = RequestMethod.GET)
public ThisIsAFooXML getAFooXml(@PathVariable String id) {
ThisIsAFooXML foo = fooService.getAFoo(id);
return foo;
}
}
You can use the @RestController
instead of @Controller
annotation.
You shouldn't need to configure the consumes or produces attribute at all. Spring will automatically serve JSON based on the following factors.
- The accepts header of the request is application/json
- @ResponseBody annotated method
- Jackson library on classpath
You should also follow Wim's suggestion and define your controller with the @RestController annotation. This will save you from annotating each request method with @ResponseBody
Another benefit of this approach would be if a client wants XML instead of JSON, they would get it. They would just need to specify xml in the accepts header.
There are 2 annotations in Spring: @RequestBody and @ResponseBody. These annotations consumes, respectively produces JSONs. Some more info here.