【原創(chuàng)文章傲醉,轉(zhuǎn)載請注明原文章地址,謝謝呻率!】
統(tǒng)一異常處理是所有的web應(yīng)用都需要考慮的硬毕。在Jersey中,也提供了很簡單的統(tǒng)一異常處理方式筷凤。
Jersey中的異常處理
Web應(yīng)用的異常處理基本思路昭殉,服務(wù)層隱藏底層的Checked Exception,服務(wù)層的異常統(tǒng)一包裝為RuntimeException拋出到Web層藐守,由Web層統(tǒng)一對服務(wù)層異常進(jìn)行處理挪丢。常見的兩種處理方式,針對Json格式的請求卢厂,返回包含異常代碼乾蓬,異常消息或者異常數(shù)據(jù)的對象返回,針對web頁面的請求慎恒,統(tǒng)一返回到異常結(jié)果展示頁面任内。
WebApplicationException
第一種方式撵渡,我們可以利用Jersey本身的異常對象,在資源方法中使用HTTP代碼來給客戶端返回指定錯誤死嗦。比如直接返回500錯誤等趋距。
在Jersey中,提供了一個javax.ws.rs.WebApplicationException類越除,在資源方法中节腐,可以簡單的通過返回該異常類,讓Jersey統(tǒng)一處理異常返回:
@Path("exception")
public class ExceptionResource {
@POST
@Path("register")
public Response register(@FormParam("name")String username) {
if ("admin".equals(username)) {
throw new WebApplicationException("用戶名已經(jīng)存在!",
Response.Status.CONFLICT);
} else {
return Response.ok("注冊成功!", MediaType.TEXT_PLAIN).build();
}
}
}
這里提供了一段代碼摘盆,exception/register資源方法返回一個Response翼雀,我們假設(shè)傳入一個用戶名username,在代碼中孩擂,我們最簡單的模擬了一個業(yè)務(wù):如果傳入的用戶名為admin狼渊,則判定為用戶名已經(jīng)存在(在實際的代碼中,可能是由業(yè)務(wù)層返回一個注冊成功后的用戶對象类垦,如果該用戶對象為空狈邑,則表明用戶名沖突),則直接拋出一個WebApplicationException护锤,在該類的構(gòu)造方法中官地,傳入了消息內(nèi)容和響應(yīng)狀態(tài)碼,這里我們設(shè)置的異常消息為:用戶名已經(jīng)存在烙懦,使用的狀態(tài)碼為Status.CONFLICT(409)驱入;
當(dāng)我們在POSTMAN中對代碼進(jìn)行測試
如果傳入的參數(shù)不是admin,則提示注冊成功氯析,如果傳入的參數(shù)為admin:
可以看到服務(wù)端正確返回409異常狀態(tài)碼亏较;
再簡單介紹一下WebApplicationException:
1,WebApplicationException可以在資源方法中掩缓,或者Provider中(攔截器雪情,過濾器,Entity Provider)拋出你辣;
2巡通,WebApplicationException有非常多的構(gòu)造方法,可以傳入不同的參數(shù)構(gòu)造Response舍哄;具體的可以看看對應(yīng)的API文檔宴凉;
3,使用WebApplicationException對于統(tǒng)一異常處理表悬,還是差距很遠(yuǎn)弥锄,最重要的原因在于針對實際的開發(fā),要求客戶端針對不同的狀態(tài)碼進(jìn)行處理,還是比較麻煩籽暇。
ExceptionMapper
第二種方式温治,Jersey提供了ExceptionMapper接口來更方便的根據(jù)異常類型來執(zhí)行對應(yīng)的異常處理方法。
我們先來看看ExceptionMapper接口聲明:
public interface ExceptionMapper<E extends Throwable> {
Response toResponse(E exception);
}
在該接口中戒悠,定義了Response toResponse(E exception);方法熬荆,很容易理解,針對匹配的exception類型救崔,怎么去生成一個對應(yīng)的Response對象惶看。
在我們具體構(gòu)建我們自己的異常處理之前,我們先來看看第三方框架中做好的示例代碼六孵。在Jackson框架中,如果在JSON->實體對象的映射過程中幅骄,出現(xiàn)解析錯誤劫窒,Jackson都會拋出一個com.fasterxml.jackson.databind.JsonMappingException異常,注意一下拆座,這個異常是一個checked exception主巍,因為這個異常屬于框架級別異常(集成IOException)。
接著挪凑,Jackson為我們提供了一個專門用于處理JsonMappingException的錯誤處理孕索,我們就從這個類開始學(xué)習(xí):
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實現(xiàn)了ExceptionMapper接口躏碳,在接口的泛型類型中只針對JsonMappingException進(jìn)行處理搞旭。在實現(xiàn)的toResponse方法中,使用Response構(gòu)造了Status.BAD_REQUEST(400狀態(tài)碼)菇绵,并傳入異常消息返回肄渗。
要使用ExceptionMapper,也只需要添加@Provider注解或者通過ResourceConfig.register方法注冊即可咬最。
我們簡單介紹一種統(tǒng)一處理方法翎嫡。首先確定目標(biāo)結(jié)果,我們就針對Json的數(shù)據(jù)格式永乌。其次惑申,針對整個應(yīng)用,我們構(gòu)建一個基礎(chǔ)的異常類翅雏,再針對不同的服務(wù)錯誤圈驼,創(chuàng)建不同的異常子類。在ExceptionMapper中枚荣,統(tǒng)一將錯誤包裝為響應(yīng)對象返回碗脊,下面簡單展示一些代碼:
首先我們創(chuàng)建一個異常類型枚舉類,用來表示不同的應(yīng)用異常類型和對應(yīng)的異常狀態(tài)碼:
@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;
}
接著,創(chuàng)建一個應(yīng)用的基礎(chǔ)異常類:
@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;
}
}
在該基礎(chǔ)異常類中衙伶,額外保存了一個異常類型祈坠;接著針對不同的服務(wù),提供不同的異常子類矢劲,比如針對權(quán)限訪問的異常:
public class PermissionException extends ApplicationException {
private static final long serialVersionUID = 1L;
public PermissionException(String msg, Throwable ex) {
super(msg, ExceptionCode.PERMISSION_EXCEPTION, ex);
}
}
代碼很簡單赦拘,僅僅只是額外規(guī)定了異常狀態(tài)類型為ExceptionCode.PERMISSION_EXCEPTION;
接著創(chuàng)建自己的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躺同,構(gòu)建了一個AjaxResult對象。AjaxResult對象是我們應(yīng)用對于客戶端統(tǒng)一的返回對象:
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class AjaxResult {
private boolean success;//請求是否執(zhí)行成功
private String msg;//本次請求的消息
private Object data;//本次請求可能攜帶的內(nèi)容對象
private int code;//如果出現(xiàn)異常丸逸,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("沒有權(quán)限訪問該資源", null);
}
}
在這里我們的演示資源方法是一個很簡單的測試,我們直接判斷請求頭中是否存在token字段黄刚,如果沒有捎谨,我們直接拋出一個PermissionException異常。
簡單的測試(正常的訪問):
錯誤的訪問:
小結(jié)
在本節(jié)中憔维,我們重點介紹了使用ExceptionMapper來做異常的統(tǒng)一處理涛救,額外的,在Jersey中還提供了一個擴展異常處理接口ExtendedExceptionMapper业扒,提供了更靈活的異常和異常處理器的匹配检吆,關(guān)于這個接口,建議大家可以去看看API文檔程储。
下一節(jié)蹭沛,我們將介紹Jersey和Spring以及SpringBoot的集成開發(fā)。