Sub-Resources in spring REST
Solution 1
In Spring boot, we can implement JAX-RS subresource concept using @Autowired Spring Concept. Create an object of your child resource class and Spring will initialize at runtime and return that object. Don't create an Object manually. like: Above mention scenario
- MessageResource.java
@RestController
@RequestMapping("/messages")
public class MessageResource {
MessageService messageService = new MessageService();
@Autowired
@Qualifier("comment")
CommentResource comment;
@RequestMapping(value = "/{messageId}/comments")
public CommentResource getCommentResource() {
return comment;
}
}
- CommentResource.java
@RestController("comment")
@RequestMapping("/")
public class CommentResource {
private CommentService commentService = new CommentService();
@RequestMapping(method = RequestMethod.GET, value="/abc")
public String test2() {
return "this is test comment";
}
}
Now it will work like sub-resource
http://localhost:8080/messages/1/comments/abc
You can send any type of request.
Solution 2
Your url (http://localhost:8080/messages/1/comments/abc) suggests that comments are nested in the message. Your controller should look like this:
@RestController
@RequestMapping("/messages")
public class MessageResource {
@RequestMapping(value = "/{messageId}")
public String getCommentResource(@PathVariable("messageId") String messageId) {
//test
return messageId;
}
@RequestMapping(value = "/{messageId}/comments/{commentsContent}")
public String getCommentResource(
@PathVariable("messageId") String messageId,
@PathVariable("commentsContent") String commentsContent) {
//test
return messageId + "/" + commentsContent;
}
}
I'm not entirely sure what you wish to do in your MessageResource class, but the idea is there.
Rest - HTTP methods
For now, these use are Get requests. You should however consider using the appropriate Http Method:
- Get: read a resource
- Post: create a resource
- Put: update
- Delete: delete :)
Take a look at this: http://www.restapitutorial.com/lessons/httpmethods.html
Example with Post:
@RequestMapping(method=RequestMethod.POST, value = "/{messageId}/comments/{commentsContent}")
public ResponseEntity<String> getCommentResource(
@PathVariable("messageId") String messageId,
@RequestBody Comment comment) {
//fetch the message associated with messageId
//add the comment to the message
//return success
return new ResponseEntity<String>(HttpStatus.OK);
}
Class names
Also, I would personally rename these classes to MessageController and CommentController.
Edit after comments - Split controllers
You can just literally split the controllers (closer to what you had):
@RestController
@RequestMapping("/messages")
public class MessageResource {
@RequestMapping(value = "/{messageId}")
public String getCommentResource(@PathVariable("messageId") String messageId) {
//test
return messageId;
}
}
@RestController
@RequestMapping("/messages")
public class CommentResource {
@RequestMapping(value = "/{messageId}/comments/{commentsContent}")
public String getCommentResource(
@PathVariable("messageId") String messageId,
@PathVariable("commentsContent") String commentsContent) {
//test
return messageId + "/" + commentsContent;
}
}
Solution 3
What you are looking for is supported in JAX-RS
implementations such as Jersey and is called Sub-Resources
. When building large APIs which become nested in nature, sub resources are an extremly useful feature.
Spring Boot default rest implementation is not a JAX-RS
but SpringMVC
. Whilst it is possible to use Jersey in Spring Boot, it's a bit involved trying to set it up, and doesn't appear to be well used/supported in the community.
On a side note, DropWizard is awesome!
Solution 4
I'm also being, forcedly, migrated from JAX-RS to Spring-MVC.
I'm still looking for an elegant way to do this just like I do with JAX-RS.
I'm sharing what I've tried.
@RestController
@RequestMapping("parents")
public class ParentsController {
@RequestMapping(method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<List<Parent>> read() {
}
@RequestMapping(method = RequestMethod.GET,
path = "/{id:\\d+}",
produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Parent> read(@PathVariable("id") final long id) {
}
}
And the ChildrenController
.
@RestController
@RequestMapping("/parents/{parentId:\\d+}/children")
public class ChildrenController {
@RequestMapping(method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON_VALUE})
@ResponseBody
public List<Child> read(@PathVariable("parentId") final long parentId) {
}
@RequestMapping(method = RequestMethod.GET, path = "/{id:\\d+}",
produces = {MediaType.APPLICATION_JSON_VALUE})
@ResponseBody
public Child read(@PathVariable("parentId") final long parentId,
@PathVariable("id") final long id) {
}
}
Two problems I've found,
@PathVariable
is not applicable on fields.
I just can't do @PathVariable("parentId") private long parentId;
No free will for multiple mapping for ChildrenController
A beauty of JAX-RS is that we can map ChildrenController
for different paths like even the ChildrenController
has a class-level @Path
with it.
@Path("/children");
public class ChildrenResource {
}
@Path("/parents")
public class ParentsResource {
@Path("/{id}/children")
public ChildrenResource resourceChildren() {
}
}
/children
/parents/{id}/children
Solution 5
You can keep it simple. Just create two classes and use a constant to refer the children resource with parent resource. This helps to make a link between the two classes and make the developer understand the relationship between them.
So:
@RequestMapping(value= MessageController.URL)
public class MessageController {
public static final String URL= "/messages";
}
And:
@RequestMapping(value = MessageController.URL + "/{idMessage}/comments")
public class CommentController {
}
You can also split the controllers in different packages, showing this hierarchy in your package organization too:
com.company.web.message.MessageController
com.company.web.message.comment.CommentController
prranay
Updated on June 07, 2022Comments
-
prranay almost 2 years
I'm trying to build messanger app.
I've to call CommentResource from MessageResource.
I want separate MessageResources and CommentResources.
I'm doing something like this :
MessageResource.java
@RestController @RequestMapping("/messages") public class MessageResource { MessageService messageService = new MessageService(); @RequestMapping(value = "/{messageId}/comments") public CommentResource getCommentResource() { return new CommentResource(); } }
CommentResource.java
@RestController @RequestMapping("/") public class CommentResource { private CommentService commentService = new CommentService(); @RequestMapping(method = RequestMethod.GET, value="/abc") public String test2() { return "this is test comment"; } }
I want
http://localhost:8080/messages/1/comments/abc
to return "this is test comment".
Any Idea??
PS: In a simple word, I want to know
JAX-RS sub-resource
equivalent implementation inspring-rest
-
prranay over 7 yearsYour solution works perfectly but I want to keep these two controllers separate and don't want to combine.
-
prranay over 7 yearsyeah it solves he problem and code looks clean As MessageResource contains only Message APIs and CommentResource contains Comment APIs. Below is the code i'm using, Your solution gave me new direction to look into the problem. Thanks :)
-
MrSham almost 6 years@RestController @RequestMapping("/messages/{messageId}/comments") public class CommentResource { ////// Will be much better. Since comments are always will be part of some message and you have to have some message Id to get the comments. So using common uri path as much as possible to the class level is better.
-
Ahmed Anwar over 5 yearsYou did not answer his question. He asked for a JAX-RS subresource alternative, you just showed him how to give his REST service a URL
-
spaghettifunk over 4 yearsWorks like a charm! Sad that I couldn't find an example like this in the official documentation.
-
Mariusz Zawadzki about 4 yearsHow were you able to run it? Does it require some configuration? At the current version of spring boot (2.2.4.RELEASE) it treats CommentResource as response value to serialize. If I add a field with getter I get json representation of it. And on calling GET localhost:8080/messages/1/comments/abc I get 404.
-
TheJeff over 3 yearsMan I'm not sure how to make sense out of this. First off, the "public CommentResource getCommentResource()" returns the CommentResource but doesn't inject the messageId context into it or use it in any way.. so I'm not sure if this is a mistake in the answer or my knowledge gap - clarification would be appreciated. Also the Comment resource has a different path "comment" as opposed to "comments" - eg singular vs. plural so I'm not sure if this is intentional - I think it is a mistake. Thanks for any correction or response.