Jersey 开发RESTful(十六) Jersey统一异常处理

RESTFul 专栏收录该内容
18 篇文章 8 订阅

【原创文章,转载请注明原文章地址,谢谢!】

统一异常处理是所有的web应用都需要考虑的。在Jersey中,也提供了很简单的统一异常处理方式。

Jersey中的异常处理

Web应用的异常处理基本思路,服务层隐藏底层的Checked Exception,服务层的异常统一包装为RuntimeException抛出到Web层,由Web层统一对服务层异常进行处理。常见的两种处理方式,针对Json格式的请求,返回包含异常代码,异常消息或者异常数据的对象返回,针对web页面的请求,统一返回到异常结果展示页面。

WebApplicationException

第一种方式,我们可以利用Jersey本身的异常对象,在资源方法中使用HTTP代码来给客户端返回指定错误。比如直接返回500错误等。

在Jersey中,提供了一个javax.ws.rs.WebApplicationException类,在资源方法中,可以简单的通过返回该异常类,让Jersey统一处理异常返回:

@Path("exception")
public class ExceptionResource {

    @POST
    @Path("register")
    public Response register(@FormParam("name")String username) {
        if ("admin".equals(username)) {
            throw new WebApplicationException("用户名已经存在!",
                    Response.Status.CONFLICT);
        } else {
            return Response.ok("注册成功!", MediaType.TEXT_PLAIN).build();
        }
    }
}

这里提供了一段代码,exception/register资源方法返回一个Response,我们假设传入一个用户名username,在代码中,我们最简单的模拟了一个业务:如果传入的用户名为admin,则判定为用户名已经存在(在实际的代码中,可能是由业务层返回一个注册成功后的用户对象,如果该用户对象为空,则表明用户名冲突),则直接抛出一个WebApplicationException,在该类的构造方法中,传入了消息内容和响应状态码,这里我们设置的异常消息为:用户名已经存在,使用的状态码为Status.CONFLICT(409);

当我们在POSTMAN中对代码进行测试
image.png
如果传入的参数不是admin,则提示注册成功,如果传入的参数为admin:
image.png

可以看到服务端正确返回409异常状态码;
再简单介绍一下WebApplicationException:
1,WebApplicationException可以在资源方法中,或者Provider中(拦截器,过滤器,Entity Provider)抛出;
2,WebApplicationException有非常多的构造方法,可以传入不同的参数构造Response;具体的可以看看对应的API文档;
3,使用WebApplicationException对于统一异常处理,还是差距很远,最重要的原因在于针对实际的开发,要求客户端针对不同的状态码进行处理,还是比较麻烦。

ExceptionMapper

第二种方式,Jersey提供了ExceptionMapper接口来更方便的根据异常类型来执行对应的异常处理方法。
我们先来看看ExceptionMapper接口声明:

public interface ExceptionMapper<E extends Throwable> {
    Response toResponse(E exception);
}

在该接口中,定义了Response toResponse(E exception);方法,很容易理解,针对匹配的exception类型,怎么去生成一个对应的Response对象。

在我们具体构建我们自己的异常处理之前,我们先来看看第三方框架中做好的示例代码。在Jackson框架中,如果在JSON->实体对象的映射过程中,出现解析错误,Jackson都会抛出一个com.fasterxml.jackson.databind.JsonMappingException异常,注意一下,这个异常是一个checked exception,因为这个异常属于框架级别异常(集成IOException)。

接着,Jackson为我们提供了一个专门用于处理JsonMappingException的错误处理,我们就从这个类开始学习:

public class JsonMappingExceptionMapper implements ExceptionMapper<JsonMappingException> {
    @Override
    public Response toResponse(JsonMappingException exception) {
        return Response.status(Response.Status.BAD_REQUEST)
                  .entity(exception.getMessage())
                  .type("text/plain").build();
    }
}

首先我们看到,JsonMappingExceptionMapper实现了ExceptionMapper接口,在接口的泛型类型中只针对JsonMappingException进行处理。在实现的toResponse方法中,使用Response构造了Status.BAD_REQUEST(400状态码),并传入异常消息返回。

要使用ExceptionMapper,也只需要添加@Provider注解或者通过ResourceConfig.register方法注册即可。

我们简单介绍一种统一处理方法。首先确定目标结果,我们就针对Json的数据格式。其次,针对整个应用,我们构建一个基础的异常类,再针对不同的服务错误,创建不同的异常子类。在ExceptionMapper中,统一将错误包装为响应对象返回,下面简单展示一些代码:

首先我们创建一个异常类型枚举类,用来表示不同的应用异常类型和对应的异常状态码:

@Getter
public enum ExceptionCode {
    DEFAULT_ERROR(0), 
    PERMISSION_EXCEPTION(1), 
    MONEY_CHECK_EXCEPTION(2), 
    ACCOUNT_STATUS_ERROR(3);

    private ExceptionCode(int code) {
        this.code = code;
    }

    private int code;
}

接着,创建一个应用的基础异常类:

@Getter
public abstract class ApplicationException extends RuntimeException {

    private static final long serialVersionUID = 1L;
    private ExceptionCode code = ExceptionCode.DEFAULT_ERROR;

    public ApplicationException(String msg) {
        super(msg);
    }

    public ApplicationException(String msg, ExceptionCode code) {
        super(msg);
        this.code = code;
    }

    public ApplicationException(String msg, ExceptionCode code,
            Throwable cause) {
        super(msg, cause);
        this.code = code;
    }
}

在该基础异常类中,额外保存了一个异常类型;接着针对不同的服务,提供不同的异常子类,比如针对权限访问的异常:

public class PermissionException extends ApplicationException {

    private static final long serialVersionUID = 1L;

    public PermissionException(String msg, Throwable ex) {
        super(msg, ExceptionCode.PERMISSION_EXCEPTION, ex);
    }
}

代码很简单,仅仅只是额外规定了异常状态类型为ExceptionCode.PERMISSION_EXCEPTION;

接着创建自己的ExceptionMapper:

@Provider
public class ApplicationExceptionMapper
        implements ExceptionMapper<ApplicationException> {

    @Override
    public Response toResponse(ApplicationException exception) {
        AjaxResult result = new AjaxResult(false, exception.getMessage(), null,
                exception.getCode().getCode());
        return Response.ok(result, MediaType.APPLICATION_JSON).build();
    }

}

在该方法中,我们使用拦截到的ApplicationException,构建了一个AjaxResult对象。AjaxResult对象是我们应用对于客户端统一的返回对象:

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class AjaxResult {
    private boolean success;//请求是否执行成功
    private String msg;//本次请求的消息
    private Object data;//本次请求可能携带的内容对象
    private int code;//如果出现异常,code代表异常类型码
}

最后,我们来写一个测试资源方法测试我们的异常处理:

@GET
@Path("resource")
@Produces(MediaType.APPLICATION_JSON)
public AjaxResult doSomething(@HeaderParam("token") String token) {
    if ("token".equals(token)) {
        return new AjaxResult(true, "正常访问资源", "some logic value", 0);
    } else {
        throw new PermissionException("没有权限访问该资源", null);
    }
}

在这里我们的演示资源方法是一个很简单的测试,我们直接判断请求头中是否存在token字段,如果没有,我们直接抛出一个PermissionException异常。

简单的测试(正常的访问):
image.png

错误的访问:
image.png

小结

在本节中,我们重点介绍了使用ExceptionMapper来做异常的统一处理,额外的,在Jersey中还提供了一个扩展异常处理接口ExtendedExceptionMapper,提供了更灵活的异常和异常处理器的匹配,关于这个接口,建议大家可以去看看API文档。

下一节,我们将介绍Jersey和Spring以及SpringBoot的集成开发。


  • 0
    点赞
  • 0
    评论
  • 4
    收藏
  • 扫一扫,分享海报

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值