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

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

统一异常处理是所有的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的集成开发。

WechatIMG7.jpeg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,605评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 7,092评论 0 62
  • 时间这个维度是可以感受到的,如果你愿相信智慧的话。当你达到另一精神境界时,这份思维乐趣所带来的美妙是不言而...
    7b6f343252a7阅读 165评论 0 0
  • 你说实在太闷 想要去过向往的生活 可背上了行囊 走过了一个又一个村落 却发现到处都唱着同样的歌 你看着落日 心情像...
    风过且住阅读 132评论 0 1