Spring MVC Rest Service Controller with Error Hand

2020-02-03 10:10发布

问题:

I was wondering how to correctly implement a Spring Controller which is supposed to serve as a REST Service. Especially I want to try and make the interface as RESTful as possible. Also i'd like to make use of HTTP Error codes so my Clients can act accordingly.

I was wondering how to implement my Methods, so they return JSON if everything works fine(in the body of the response) or toss a http error code as well as a custom reason why it didnt work(maybe errors that came from the DAO or the database). However I'm not sure which one is the right way? return a String and add the values to return to a Model, or return a HashMap and put my stuff in there? or return the objects directly? but then what if an error occures and i cannot return said Class? return null instead? I post 2-3 ways of doing it that i could imagine:

@RequestMapping(value="/addUser", method= RequestMethod.POST)
public String addUser(@RequestBody User user, HttpServletResponse response, Model model) throws Exception{

    try{
        userService.addUser(user);
        model.addAttribute("user", userService.getUser(user.getUsername(), user.getPassword()));
        return "user";
    }catch(Exception e){
        model.addAttribute("error", e.toString());
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.toString());
        return "error";
    }
}

Or rather this way:

@RequestMapping(value="/addUser", method= RequestMethod.POST)
public @ResponseBody Map addUser(@RequestBody User user, HttpServletResponse response){
    Map map = new HashMap();
    try{
        userService.addUser(user);
        map.put("success", true);
        map.put("username", user.getUsername());
    }catch (KeyAlreadyExistsException e){
        map.put("success", false);
        map.put("Error", e.toString());
        response.sendError(HttpServletResponse.SC_FORBIDDEN, e.toString());
    }catch(Exception e){
        map.put("success", false);
        map.put("Error", e.toString());
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.toString());
    }
    finally {
        return map;
    }
}

I realize code is not "just right" but i cannot figure out how to make it the way it needs to be. Maybe some experiences responses would help? Thx for the support already

回答1:

You could also catch your exceptions with @ExceptionHandler annotated methos within your Rest Controller.

@ExceptionHandler(Exception.class)
@ResponseBody
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public String handleException(Exception e) {
    return "return error object instead";
}

this will make your acutal controller/business logic cleaner.



回答2:

Firstly, I think you should always return a object when returning JSON. Even when something goes horribly wrong.

When something goes wrong you simply set response.setStatus() and return a resource describing the error.

public class ErrorResource implements Resource {
    private final int status;
    private final String message;

    public ErrorResource(int s, String m) {
        status = s;
        message = m;
    }

    public int getStatus() {
        return status;
    }

    public String getMessage() {
        return message;
    }
}

The resource gets serialized and the outcome would be

{"status":500, "message":"Yay!"}

Using a Map will work but I would like to advise you to write some resource classes which define the object to be returned. They would be easier to maintain. Maps don't offer any structure while structure is a very important part when creating a REST service.

I don't think you should return a resource with the raw exception message embedded. It can potentially leak information you don't want anyone to see.



回答3:

you can use @ExceptionHandler with @ControllerAdvice check this link



回答4:

use ResponseEntity class to exploit error with http status code.

You could try the following code:

@RequestMapping(value = "/profile", method = RequestMethod.GET) 
@ResponseBody @ResponseStatus(value = HttpStatus.OK) 

public ResponseEntity<UserVO> getUserProfile() 
{ 
   string userName = getUserAuthentication().getName(); 
   if (StringUtils.isEmpty(userName)) RestUtil.defaultJsonResponse(""); 
   User user = userService.getUserByUserNameWithCounters(userName); 
   return RestUtil.getJsonResponse(new UserVO(user)); 
}


回答5:

If you want your whole Exception with stackTrace to be transmitted toward your client, as @Bart said you should send an "ErrorResource" object.

A library has it off-the-shelf :

<dependency>
  <groupId>com.github.zg2pro</groupId>
  <artifactId>spring-rest-basis</artifactId>
  <version>0.2</version>
</dependency>

add it to your project and then just add a "@ControllerAdvice" class to your beans, as it is explained in the project wiki.

That should handle your errors nicely!