本文主要结合实现的restfuljava 后端,总结Exception 的处理架构。
1. exception 的分类和处理方法
异常是面向对象语言非常重要的一个特性,良好的异常设计对程序的可扩展性、可维护性、健壮性都起到至关重要。
JAVA根据用处的不同,定义两类异常
Checked Exception: Exception的子类,方法签名上需要显示的声明throws,编译器迫使调用者处理这类异常或者声明throws继续往上抛。
Unchecked Exception: RuntimeException的子类,方法签名不需要声明throws,编译器也不会强制调用者处理该类异常。
我们要谈的是 CheckedException:
应用级的exception: 由应用在运行过程中抛出的exception,主要跟应用的业务逻辑有关, 并且这些exception实在应用中定义的, 比如:输入的登陆账号找不到,登录失败。
系统级的exception:应用在运行过程中系统抛出的异常,如IO错误,运算错误等等。
我要谈的只要就是这两类exception的处理。
2. 集中的Exception handler 如何定义及使用
System Exception: Exception 和 SQL exception
Business Exception: BadRequestException, NotFoundException,ServerRejectException, SystemException
在restful 后端系统的任何地方都可以抛出这些Exception,通过Spring的Exception handler来集中处理所有抛出的 Exception
@ControllerAdvice
public class GlobalExceptionController {
private static final Log logger = LogFactory
.getLog(GlobalExceptionController.class);
@ExceptionHandler(SQLException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ModelAndView handleSQLException(HttpServletRequest request,
SQLException ex) {
handleLog(request, ex);
Map errorMap = new HashMap();
errorMap.put("code", HttpStatus.INTERNAL_SERVER_ERROR);
errorMap.put("Requested", request.getRequestURL());
errorMap.put("message", ex.toString());
return new ModelAndView(JSONUtils.VIEW_NAME,
JSONStringView.JSON_MODEL_DATA, errorMap);
}
@ExceptionHandler(BadRequestException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public ModelAndView handleBadRequestException(
HttpServletRequest request, BadRequestException ex) {
handleLog(request, ex);
ExceptionModel exceptionModel = getExceptionModel(
HttpStatus.BAD_REQUEST, ex);
return new ModelAndView(JSONUtils.VIEW_NAME,
JSONStringView.JSON_MODEL_DATA, exceptionModel);
}
@ExceptionHandler(ServerRejectException.class)
@ResponseStatus(value = HttpStatus.FORBIDDEN)
public ModelAndView handleServerRejectException(
HttpServletRequest request, ServerRejectException ex) {
handleLog(request, ex);
ExceptionModel exceptionModel = getExceptionModel(HttpStatus.FORBIDDEN,
ex);
return new ModelAndView(JSONUtils.VIEW_NAME,
JSONStringView.JSON_MODEL_DATA, exceptionModel);
}
@ExceptionHandler(NotFoundException.class)
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public ModelAndView handleNotFoundException(HttpServletRequest request,
NotFoundException ex) {
handleLog(request, ex);
ExceptionModel exceptionModel = getExceptionModel(HttpStatus.NOT_FOUND,
ex);
return new ModelAndView(JSONUtils.VIEW_NAME,
JSONStringView.JSON_MODEL_DATA, exceptionModel);
}
@ExceptionHandler(SystemException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ModelAndView handleSystemException(HttpServletRequest request,
SystemException ex) {
handleLog(request, ex);
ExceptionModel exceptionModel = getExceptionModel(
HttpStatus.INTERNAL_SERVER_ERROR, ex);
return new ModelAndView(JSONUtils.VIEW_NAME,
JSONStringView.JSON_MODEL_DATA, exceptionModel);
}
@ExceptionHandler(Exception.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ModelAndView handleAllException(HttpServletRequest request,
Exception ex) {
handleLog(request, ex);
Map errorMap = new HashMap();
errorMap.put("code",
Integer.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()));
errorMap.put("message", ex.toString());
return new ModelAndView(JSONUtils.VIEW_NAME,
JSONStringView.JSON_MODEL_DATA, errorMap);
}
private ExceptionModel getExceptionModel(HttpStatus httpStatus,
CommonException ex) {
ExceptionModel exceptionModel = new ExceptionModel();
ErrorENUM errorEnum = ex.getErrorEnum();
exceptionModel.setStatus(httpStatus.value());
exceptionModel.setMoreInfo(ex.getMoreInfo());
if (errorEnum != null) {
exceptionModel.setErrorCode(errorEnum.getCode());
exceptionModel.setMessage(errorEnum.toString());
}
return exceptionModel;
}
private void handleLog(HttpServletRequest request, Exception ex) {
Map parameter = request.getParameterMap();
StringBuffer logBuffer = new StringBuffer();
if (request != null) {
logBuffer.append(" request method=" + request.getMethod());
logBuffer.append(" url=" + request.getRequestURL());
}
if (ex instanceof CommonException) {
logBuffer.append(" moreInfo="
+ ((CommonException) ex).getMoreInfo());
}
if (ex != null) {
logBuffer.append(" exception:" + ex);
}
logger.error(logBuffer.toString());
}
}
想要深入了解的 可以参考这里
http://www.journaldev.com/2651/spring-mvc-exception-handling-exceptionhandler-controlleradvice-handlerexceptionresolver-json-response-example
3. HTTP request 的 exception 返回设计
在每个应用Exception 中设计不同的返回消息和errorcode:
并且返回的status 也不同。
针对RESTFUL interface 返回错误主要有以下两种不同的方式:
第一种 Facebook,无论正常返回还是错误返回,HTTPstatus 都是 200 OK, 返回信息需要check 返回的Object
第二种 Twilio 和 SimpleGeo ,HTTP status 正常返回 200, 出错 返回 400,404, 403 等等。
笔者采用的是第二种方式。
Http status 的返回值选择:
根据restful应用的具体情况笔者主要选择了:
· 200 – OK
· 400 - Bad Request
· 403 – Forbidden
· 404 – Not Found
· 503 – Internal servererror
Restful 返回error 信息的model class定义:
public classExceptionModel {
private Integer status;
private Integer errorCode;
private String message;
private String moreInfo;
public Integer getErrorCode(){
return errorCode;
}
public void setErrorCode(Integer errorCode) {
this.errorCode = errorCode;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getMoreInfo() {
return moreInfo;
}
public void setMoreInfo(String moreInfo) {
this.moreInfo = moreInfo;
}
}
{
"message":"400202: The specific parameter is exist in system. ",
"status":400,
"errorCode":400202,
"moreInfo":"06d690e7-fb4d-45a8-91e0-7d82b0c60ca5 and 09f5edd6-1be3-4949-a302-9f487f82d723' is exist in system "
}
详情可参考:
https://blog.apigee.com/detail/restful_api_design_what_about_errors
4. 使用Spring AOP 进行输入和返回验证
@Aspect
public class InfoObjectInterceptor {
@Autowired
private ValidatorBean validatorBean;
private static final Log logger =LogFactory
.getLog(InfoObjectInterceptor.class);
@Pointcut("execution(* com.json.BeanLang.createInfoObject(..))")
private voidcreateInfoObjectMethod() {
}//
@Before("createInfoObjectMethod()&& args(infoObjectModel)")
public voiddoAccessCreateCheck(InfoObjectModel infoObjectModel)
throws Exception {
if(validatorBean.checkInfoObjectExist(infoObjecId)) {
String moreInfo = newStringBuffer()
.append(infoObjecId)
.append(" isexist in system").toString();
throw newBadRequestException(
ErrorENUM.PARAMETER_EXIST_IN_SYSTEM,moreInfo);
}
}
}
5. 异常处理的原则和技巧
参考:http://lavasoft.blog.51cto.com/62575/18920/
1、避免过大的try块,不要把不会出现异常的代码放到try块里面,尽量保持一个try块对应一个或多个异常。
2、细化异常的类型,不要不管什么类型的异常都写成Excetpion。
3、catch块尽量保持一个块捕获一类异常,不要忽略捕获的异常,捕获到后要么处理,要么转译,要么重新抛出新类型的异常。
4、不要把自己能处理的异常抛给别人。
5、不要用try...catch参与控制程序流程,异常控制的根本目的是处理程序的非正常情况
6. 优点
a) Business 方面的 信息反馈给client, 方便系统集成和调试
b) 系统级的Exception 有利于 function 测试 发现隐藏的bug
参考:
http://mariuszprzydatek.com/2013/07/29/spring-localized-exception-handling-in-rest-api/