I am trying to configure an Interface with some REST methods with Spring Boot. But when I implement that interface on a concrete class, the #PathVariable or #RequestParam do not work properly unless I repeat the configuration on my concrete method (which I dont want because the interface is being generated by a maven plugin). Lets take a look at the interface and the concrete class.
#RequestMapping("employees")
public interface EmployeesResource {
#RequestMapping(method = RequestMethod.GET, produces = {"application/json"})
EmployeesResource.GetEmployeesResponse getEmployees(
#RequestParam(value = "pageNum", defaultValue = "0")
long pageNum,
#RequestParam(value = "pageSize", defaultValue = "10")
long pageSize)
throws Exception;
#RequestMapping(method = RequestMethod.GET, value = "{employeeId}", produces = {
"application/json"
})
EmployeesResource.GetEmployeesByEmployeeIdResponse getEmployeesByEmployeeId(
#PathVariable("employeeId")
long employeeId)
throws Exception;
}
Now lets take a look at the concrete class.
#RestController
public class EmployeesResourceImpl implements EmployeesResource {
#Override
public EmployeesResource.GetEmployeesByEmployeeIdResponse getEmployeesByEmployeeId(long employeeId) {
//omitted
return EmployeesResource.GetEmployeesByEmployeeIdResponse.withJsonOK(e);
}
#Override
public EmployeesResource.GetEmployeesResponse getEmployees(long pageNum, long pageSize)
throws Exception {
//omitted
return EmployeesResource.GetEmployeesResponse.withJsonOK(list);
}
}
When I make a call to http://127.0.0.1:8080/employees/1 it throws an exception.
java.lang.IllegalStateException: Optional long parameter 'employeeId' is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as object wrapper for the corresponding primitive type.
I know that this is because me employeeId is long and not Long, but that is not the question because if I add the #PathVariable("employeeId") to the method on the concrete class, it all works.
So, what I want is to not repeat the configuration from the interface into the concrete class. Same goes for the #RequestParam(value = "pageNum", defaultValue = "0").
I have googled a lot and did not found that capacity of inheritance of annotations inside Spring Boot. Any help would be appreciated.
EDIT: Andy Wilkinson suggested this post Spring MVC Annotated Controller Interface but the guy concluded that there is no way around this due to AOP injections that Spring does. Is there really nothing to do here?
Related
This is my controller:
#Controller
#RequestMapping("/area")
public class AreaController {
#RequestMapping("/{id}")
public String getPage(#PathVariable("id") int id){
return "asd3333";
}
}
and this is what I get when I access http://localhost:8080/area/1:
Failed to convert value of type 'java.lang.String' to required type 'int'; nested exception is java.lang.NumberFormatException: For input string: "asd3333"]
I tested this random return just to show what is happening...
The method is beeing called first with the #PathVariable = 1 from the request, and then right after that, is called again with the whethever the method resulsts, in this case, it tries to pass the #PathVariable = "asd3333".
I have NO IDEA of what tha heck is happening, pls help
Sounds very strange indeed. I will start with a question
#RequestMapping("/{id}")
public String getPage(#PathVariable("id") int id){
return "asd3333";
}
Does this method need to be called for all method types (Get, Post, Delete, ...). If no try to restrict with a specific method call.
ex
#RequestMapping(value = "/{id}", method = POST)
GOTCHA.
Also add this to the method because you return a simple string
#RequestMapping("/{id}")
#ResponseBody
public String getPage(#PathVariable("id") int id)
Also if you don't plan to use this API as a web MVC application but instead as a rest API backend switch from #Controller to #RestController.
I just found that even if I omit the #RequestParam annotation on the organization parameter, Spring is still able to bind it.
#RequestMapping(value="", method = RequestMethod.POST)
#ResponseBody
public String save(String organization){
logger.info(organization); // it works
}
Can anyone points to the documentation that clarifies this behaviour? I have always though that #RequestParam was mandatory for binding to work.
Thanks
Take a look at https://reversecoding.net/spring-mvc-requestparam-binding-request-parameters/ There is an explanation:
Examples without #RequestParam
Based on the list of
HandlerMethodArgumentResolver configured in your application,
#RequestParam can also be omitted. If you have a look at the code of
method getDefaultArgumentResolvers() of RequestMappingHandlerAdapter
there is the following piece of code at the end:
// Catch-all resolvers.add(new
RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
// Catch-all resolvers.add(new
RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
Basically, it’s added to the resolvers a
RequestParamMethodArgumentResolver with useDefaultResolution set to
true. Looking at the documentation we can see that this means that
method argument that is a simple type, as defined in
BeanUtils.isSimpleProperty(java.lang.Class), is treated as a
request parameter even if it isn’t annotated. The request parameter
name is derived from the method parameter name.
Your resolvers do it automatically. When you pass the HandlerMethodArgumentResolver bean to your resolver, the BeanUtil checks if the parameter is a primitive value or a simple String. If so, it does the binding itself.
#RequestMapping(value = "/rest")
#ResponseBody
public String save(String username, String password) {
return String.format("username=%s password=%s", username, password);
}
Hit the service http://localhost:8080/rest?username=mypwd&password=uname
You will be able to see the result given below.
Output: username=pwd password=uname
here I have some examples for the #RequestParam to provide you,hope they can help you:
#RequestMapping(value = "/selection/findByField", method = RequestMethod.POST)
public #ResponseBody List<selectionsDO> add(#RequestParam(value = "field", required = true) String field,#RequestParam(value = "value", required = true) String value)
{ return mongoService.findByField(field,value);
}
The words "required = true" means that this field must submit at request.
Recently I've faced a strange behavior in Spring MVC (v4.3.7.RELEASE), especially from the RequestParamMethodArgumentResolver. What I tried to do is getting a link to my controller using MvcUriComponentsBuilder, so here are 2 examples:
What's clear
Having a controller like this
#Controller
#RequestMapping("/test")
public class TestController {
#GetMapping("/get")
#ResponseBody
public ModelAndView get(#RequestParam(required = false) String param1,
#RequestParam(required = false) String param2) {
...
}
}
And then using MvcUriComponentsBuilder as fromMethodCall(on(TestController.class).get(null, null)).toUriString() results in expected link to /test/get.
What's not clear
Same controller, but w/o #RequestParam annotations; same usage of MvcUriComponentsBuilder. But the result is /test/get?param1¶m2, i.e. it behaves as if they were annotated with #RequestParam(required = true)...
So before creating a bug for Spring just wanted to clarify if I'm not mistaken anywhere: should the absence of #RequestParam really behave the same as #RequestParam(required = false)? I was not able to find it in documentation, but it's in the code actually (see https://github.com/spring-projects/spring-framework/blob/v4.3.7.RELEASE/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java#L79)
I have a controller annotated with #RestController and it implements an interface:
public interface ContratEndpoint {
String ROOT = "/api/contrats";
String GET_CONTRAT = "";
String GET_CONTRAT_PER_PK = "/{idContrat}";
#RequestMapping(value = GET_CONTRAT)
Contrat getContrat(#RequestParam(value = "contratId")Long contratId);
#RequestMapping(value = GET_CONTRAT_PER_ID)
ExtContrat getContratById(#PathVariable("idContrat") Long idContrat);
}
The controller:
#RestController
#RequestMapping(value = ContratEndpoint.ROOT)
public class ContratController implements ContratEndpoint {
//Injecting Services....
#Resource
private Mapper mapper;
#Override
public Contrat getContrat(Long contratId) {
return mapper.map(contratService.get(contratId),Contrat.class);
}
#Override
public ExtContrat getContratById(#PathVariable("idContrat") Long idContrat){
Preconditions.checkArgument(idContrat !=null);
return mapper.map(contratService.get(idContrat),ExtContrat.class);
}
.The above Code works just fine.
. But For the first inherited method , I didn't have to annotate arguments with #RequestParam and it worked just fine.
As for the second method I tried at first :
#Override
public ExtContrat getContratById(Long idContrat){
Preconditions.checkArgument(idContrat !=null);
return mapper.map(contratService.get(idContrat),ExtContrat.class);
}
. I expected the same behaviour Like the first Method, But i was wrong and the code ended up firing an IllegalArgumentException because of the check in ligne Preconditions.checkArgument(idContrat!=null).
My question is what is so specific about #PathVariable that i've missed ?
Or is it just something is wrong with my approach?
Thanks.
There is difference between Request param and path variable,seee below post that you can confirm with your uri the cause for the exception :
#PathVariable is to obtain some placeholder from the uri (Spring call it an URI Template) — see Spring Reference Chapter 16.3.2.2 URI Template Patterns
#RequestParam is to obtain an parameter — see Spring Reference Chapter 16.3.3.3 Binding request parameters to method parameters with #RequestParam
Assume this Url http://localhost:8080/SomeApp/user/1234/invoices?date=12-05-2013 (to get the invoices for user 1234 for today)
#RequestMapping(value="/user/{userId}/invoices", method = RequestMethod.GET)
public List<Invoice> listUsersInvoices(
#PathVariable("userId") int user,
#RequestParam(value = "date", required = false) Date dateOrNull) {
...
}
How come this code just works? I didn't specify any custom converter or annotation (like #RequestBody or #ModelAttribute) before argument ? Request is filled correctly from this GET call:
http://localhost:8080/WS/foo?token=C124EBD7-D9A5-4E21-9C0F-3402A1EE5E9B&lastSync=2001-01-01T00:00:00&pageNo=1
Code:
#RestController
#RequestMapping(value = "/foo")
public class FooController {
#RequestMapping(method = RequestMethod.GET)
public Result<Foo> excursions(Request request) {
// ...
}
}
Request is just POJO with getters and setters. I use it to shorten argument code because plenty methods uses those same arguments ...
public class Request {
private String token;
#DateTimeFormat(pattern = IsoDateTime.DATETIME)
private Date lastSync;
private Integer pageNo;
// getters and setters
}
This was my original method before introducing Request.
#RestController
#RequestMapping(value = "/foo")
public class FooController {
#RequestMapping(method = RequestMethod.GET)
public Result<Foo> excursions(#RequestParam String token, #RequestParam #DateTimeFormat(pattern = IsoDateTime.DATETIME) Date lastSync, #RequestParam Integer pageNo) {
// ...
}
}
Request parameters will be mapped to POJOs, as it is happening in your case, by default. Additionally, if you use #ModelAttribute, an attribute in the Model will be created. That attribute can be then used in views, e.g. JSPs, to access the object.
#RequestBody annotation tells that the body of the request is NOT a set of form parameters like
token=C124EBD7-D9A5-4E21-9C0F-3402A1EE5E9B&lastSync=2001-01-01T00:00:00&pageNo=1
but is in some other format, such as JSON.
This is a feature provided by Spring MVC:
Customizable binding and validation. Type mismatches as application-level validation errors that keep the offending value, localized date and number binding, and so on instead of String-only form objects with manual parsing and conversion to business objects.
You can see it in the doc: http://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/htmlsingle/