Global Exception Handling in Jersey & Spring?

2020-08-23 16:48发布

I am developing the RESTful webservices using Jersey & Spring 3.2 along with Open CMIS.

I am not using MVC pattern of Spring and it's just Spring IOC & Jersey SpringServlet, the controller class is something like below code

@GET
@Path("/{objId:.+}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)

public statusMsg addObject(@PathParam("objId") String objId{

    return repoService.addObject(objId);
}

In the repoService I am performing the business logic to add the object using CMIS, my question is that I am catching around 5 exceptions related to CMIS then the base exception i.e Exception but for every service method I have to repeat it which I don't want to do.

I was searching on Google and found that @ControllerAdvice is the best solution for such problem wheer you can define all the checked & unchecked exceptions and wherever remove all the try catch blocks from the application. But it only work with MVC pattern.

Question 1: Is there a way I can use this in above Jersey-Spring framework?

After more reserach I found that Jersey provides ExceptionMapper to handle customized exception but I want to catch more CMIS exception or default Exception or IO Exception etc.

Question 2: How can I do it with ExceptionMapper?

Question 3: Am I on the correct approach or do you suggest any better approach to handle such issues.

Thanks in advance.

2条回答
beautiful°
2楼-- · 2020-08-23 17:10

thanks for your reply. I can see you have created multiple classes based on the exception type and behaviour.

Q1. In your services method are you throwing any exception like

public void addObject(String objId) throws WebApplicationException{ 
}

or you have not used try catch and throw anywhere across the application.

Actually, I have tried something where in my web application I am not using try, catch and throws anywhere and in my CentralControllerException I have mentioned like below:

public class CentralControllerHandler implements ExceptionMapper<Exception> {

@Override
@Produces(MediaType.APPLICATION_JSON)
public Response toResponse(Exception ex) {

    if(ex instanceof CmisContentAlreadyExistsException){

        log.error(ex);
        // send Response status as 400
    }

    if(ex instanceof IOException){

        log.error(ex);
        // send Response status as 400
    }

    return Response;

}

}

So in above approach I have created just 1 class for all the exceptions which I could expect and for any unknown exception the base Exception will be there to catch.

Now wherever in my application if any exception occurs it comes to the CentralControllerException and appropriate response with http status code is sent back.

Q.2. Do you foresee any issue in above approach.

查看更多
狗以群分
3楼-- · 2020-08-23 17:17

I use jersey2.11 with Tomcat and almost exception handle with ExceptionMapper. (In domain logic, only DB rollback process use try-catch code.)

I think ExceptionMapper with @Provider automatically choose correct ExceptionMapper. So I suppose this function is satisfied with "I want to catch more CMIS exception or default Exception or IO Exception etc."

This code is my handling ExceptionMapper design code.

1.Some Jersey Root Resource Class

@GET
@Produces("application/json")
public String getUser(@NotNull @QueryParam("id") String id, 
  @NotNull @QueryParam("token") String token) throws Exception { // This level throws exceptions handled by ExceptionMapper

  someComplexMethod(id, token); // possible throw Exception, IOException or other exceptions.

  return CLICHED_MESSAGE;
}

2.ExceptionMapper package. com.yourdomain.exceptionmapper

AbstractExceptionMapper.java (All ExceptionMapper class extends this Abstract class)

public abstract class AbstractExceptionMapper {
  private static Logger logger = LogManager.getLogger(); // Example log4j2.

  protected Response errorResponse(int status, ResponseEntity responseEntity) {
    return customizeResponse(status, responseEntity);
  }

  protected Response errorResponse(int status, ResponseEntity responseEntity, Throwable t) {
    logger.catching(t); // logging stack trace.

    return customizeResponse(status, responseEntity);
  }

  private Response customizeResponse(int status, ResponseEntity responseEntity) {
     return Response.status(status).entity(responseEntity).build();
  }
 }

ExceptionMapper.java (At least this mapper can catch any exception which is not define specify exception mapper.)

@Provider
 public class ExceptionMapper extends AbstractExceptionMapper implements
 javax.ws.rs.ext.ExceptionMapper<Exception> {

 @Override
 public Response toResponse(Exception e) {
 // ResponseEntity class's Member Integer code, String message, Object data. For response format.
 ResponseEntity re = new ResponseEntity(Code.ERROR_MISC); 

  return this.errorResponse(HttpStatus.INTERNAL_SERVER_ERROR_500, re, e);
 }
}

WebApplicationExceptionMapper.java (Specify WebApplicationException)

@Provider
public class WebApplicationExceptionMapper extends AbstractExceptionMapper implements
    ExceptionMapper<WebApplicationException> {

  @Override
  public Response toResponse(WebApplicationException e) {
    ResponseEntity re = new ResponseEntity(Code.ERROR_WEB_APPLICATION);

    return this.errorResponse(e.getResponse().getStatus(), re, e);
  }
}

ConstraintViolationExceptionMapper.java (Specify Hibernate Validator ConstraintViolationException)

@Provider
public class ConstraintViolationExceptionMapper extends AbstractExceptionMapper implements
    ExceptionMapper<ConstraintViolationException> {

  @Override
  public Response toResponse(ConstraintViolationException e) {
    ResponseEntity re = new ResponseEntity(Code.ERROR_CONSTRAINT_VIOLATION);

    List<Map<String, ?>> data = new ArrayList<>();
    Map<String, String> errorMap;
    for (final ConstraintViolation<?> error : e.getConstraintViolations()) {
      errorMap = new HashMap<>();
      errorMap.put("attribute", error.getPropertyPath().toString());
      errorMap.put("message", error.getMessage());
      data.add(errorMap);
    }

    re.setData(data);

    return this.errorResponse(HttpStatus.INTERNAL_SERVER_ERROR_500, re, e);
  }
}

.. and other specify exception can create ExceptionMapper classes.

In my experience, Exception Mapper is high level idea for focus to domain logic. It could drive out boring scattered try-catch block code from domain logic. So I hope that you feel the "Yes i am" at Question 3 to resolve the problem at your environment.

you have not used try catch and throw anywhere across the application.

My code design use throws at method like this and this make to manage by ExceptionMapper classes.

public String getUser(@NotNull @QueryParam("id") String id, 
  @NotNull @QueryParam("token") String token) throws Exception

So in above approach I have created just 1 class for all the exceptions which I could expect and for any unknown exception the base Exception will be there to catch. Now wherever in my application if any exception occurs it comes to the CentralControllerException and appropriate response with http status code is sent back. Q.2. Do you foresee any issue in above approach.

I think if simple project or never update/modify project (project lifecycle short time), your one class exception mapper approach ok. But ... i never take this approach. Simply, if need to manage more exception, this method become big and complex, and hard to read and maintain becoming.

In my policy, OOP should use pleomorphism strategy any level code(class plan, DI plan) and this approach some part aim to drive out if/switch block in code. And this idea make each method short code and simple, clear to "domain logic" and code become to resistant to modify.

So i create implements ExceptionMapper and delegate to DI which ExceptionMapper class manage to exception. (So DI manage replace your single class If block manage which exception handling, this is typically refactoring approach similar Extract xxx http://refactoring.com/catalog/extractClass.html. In our discussion case, single class and one method too busy, so extract each ExceptionMapper class approaching and DI call suitable class & method strategy.)

Btw, system processing result is same at present point. But if need to reduce future development cost ,should not took approach one class exception handling plan. Because if give up simply code and refactor status, project code is dead faster.

This is my idea and why this.

regards.

查看更多
登录 后发表回答