一,沒有異常的情況,正常返回數據
希望接口統一返回的數據格式如下:
{
"status": 0,
"msg": "成功",
"data": null
}
和接口數據對應的bean
/**
* 統一返回結果的實體
* @param <T>
*/
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 錯誤碼
*/
private int status;
/**
* 提示消息
*/
private String msg;
/**
* 返回的數據體
*/
private T data;
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
操作Result實體的工具類
/**
* 生成result的工具類,避免重複代碼
*/
public class ResultUtils {
/**
* 成功時生成result的方法,有返回數據
*/
public static <T> Result<T> success(T t){
Result<T> result = new Result<>();
result.setStatus(ResultEnum.SUCCESS.getCode());
result.setMsg(ResultEnum.SUCCESS.getMsg());
result.setData(t);
return result;
}
/**
* 成功時生成result的方法,無返回數據
*/
public static <T> Result<T> success(){
return success(null);
}
/**
* 失敗時生成result的方法
*/
public static <T> Result<T> error(int status, String msg){
Result<T> result = new Result<>();
result.setStatus(status);
result.setMsg(msg);
return result;
}
}
封裝錯誤碼和錯誤消息的枚舉類
/**
* 所有返回結果的枚舉
*/
public enum ResultEnum {
UNKNOWN_ERROR(-1, "未知錯誤"),
SUCCESS(0, "成功"),
BASIC_INFO_ID_IS_EMPTY(600, "基本信息中BasicInfoId爲空"),
BASIC_INFO_ADD_TO_DATABASE_FAILURE(601, "向數據庫添加基本信息失敗"),
DETAILS_DATA_BASIC_INFO_ID_IS_EMPTY(602, "測試數據中BasicInfoId爲空"),
DETAILS_DATA_ADD_TO_DATABASE_FAILURE(603, "向數據庫添加測試數據失敗");
ResultEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
private int code;
private String msg;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public String toString() {
return "ResultEnum{" +
"code=" + code +
", msg='" + msg + '\'' +
'}';
}
}
統一封裝返回結果的切面
之所以需要這個切面,是爲了避免每個Controller方法中都要調用ResultUtils.success()。有了這個切面,Controller可以和原來一樣正常返回對象,字符串,void,在切面裏面將結果封裝成Result實體,而不需要每個Controller方法都返回Result實體。
/**
* 統一處理返回結果的切面,避免每個controller方法裏面都要調用ResultUtils.success()這句話
* 統一在這個切面裏面調用
*/
@ControllerAdvice
public class MyResponseAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private ObjectMapper objectMapper;
/**
* Whether this component supports the given controller method return type
* and the selected {@code HttpMessageConverter} type.
*
* @param returnType the return type
* @param converterType the selected converter type
* @return {@code true} if {@link #beforeBodyWrite} should be invoked;
* {@code false} otherwise
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
/**
* Invoked after an {@code HttpMessageConverter} is selected and just before
* its write method is invoked.
*
* @param body the body to be written
* @param returnType the return type of the controller method
* @param selectedContentType the content type selected through content negotiation
* @param selectedConverterType the converter type selected to write to the response
* @param request the current request
* @param response the current response
* @return the body that was passed in or a modified (possibly new) instance
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(body instanceof Result){ //發生異常之後,異常處理器裏面返回的已經是Result了
return body;
}else if(body instanceof String){ //String屬於特殊情況,需要單獨處理,否則會報錯
try {
return objectMapper.writeValueAsString(ResultUtils.success(body));
} catch (JsonProcessingException e) {
e.printStackTrace();
return ResultUtils.error(ResultEnum.UNKNOWN_ERROR.getCode(), e.getMessage());
}
}
return ResultUtils.success(body);
}
}
二,有異常的情況下
service層爲了自動回滾事務,會拋出一些自定義的RuntimeException。默認情況下,只有RuntimeException纔會回滾事務。如果Controller裏面直接處理service層拋出的異常,則Controller裏面到處都是try catch塊,代碼會很難看。將異常集中在一個地方處理會好很多。
springboot中是通過@ControllerAdvice和@ExceptionHandler來完成統一異常處理的。這2個註解只能處理Controller和攔截器中拋出的異常,其他地方拋出的異常(比如Filter中拋出的異常),無法捕獲。其他地方拋出的異常會轉到/error的Controller方法來處理,默認是BasicErrorController來處理,爲了能處理其他地方拋出的異常,我們會自定義ErrorController。
統一的異常處理類,處理Controller和攔截器拋出的異常
/**
* 統一的異常處理類
*/
@ControllerAdvice
public class MyExceptionHandler {
/**
* 轉發到/error,表示由BasicErrorController處理,
* BasicErrorController是由springboot自動裝配到容器中的
*/
/*@ExceptionHandler(BasicInfoException.class)
public String handleException(Exception ex, HttpServletRequest request){
request.setAttribute("javax.servlet.error.status_code", 401);
request.setAttribute("exMsg", ex.getMessage());
return "forward:/error";
}*/
/**
* 處理基本信息相關的異常
*/
@ExceptionHandler(BasicInfoException.class)
@ResponseBody
public Result handleBasicInfoException(BasicInfoException ex){
return ResultUtils.error(ex.getCode(), ex.getMessage());
}
/**
* 處理測試數據相關的異常
*/
@ExceptionHandler(DetailsDataException.class)
@ResponseBody
public Result handleDetailsDataException(DetailsDataException ex){
return ResultUtils.error(ex.getCode(), ex.getMessage());
}
/**
* 處理未知異常
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public Result handleUnKnowException(Exception ex){
return ResultUtils.error(ResultEnum.UNKNOWN_ERROR.getCode(), ex.getMessage());
}
}
自定義的異常類示例
public class BasicInfoException extends RuntimeException {
private int code;
public BasicInfoException(int code, String msg){
super(msg);
this.code = code;
}
public int getCode() {
return code;
}
}
處理其他地方拋出的異常(不是Controller和攔截器拋出的異常),自定義ErrorController
/**
* 自定義ErrorController,處理其他地方拋出的異常(不是Controller和攔截器拋出的異常)
*/
@Controller
public class MyBasicErrorController extends AbstractErrorController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 可以通過@Value獲取到
*/
@Value("${server.error.path}")
private String myPath;
private final ErrorProperties errorProperties;
private ErrorAttributes mErrorAttributes;
public MyBasicErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
super(errorAttributes);
this.errorProperties = serverProperties.getError();
this.mErrorAttributes = errorAttributes;
}
//@RequestMapping(value = "/error")
@RequestMapping("${server.error.path}") //從properties文件中獲取
@ResponseBody
public Result<Object> error(HttpServletRequest request) throws Throwable {
logger.debug("myPath = " + myPath);
//發生錯誤之後直接將異常拋出去,異常會到統一異常處理器中處理
WebRequest webRequest = new ServletWebRequest(request);
Throwable throwable = this.mErrorAttributes.getError(webRequest).getCause();
throw throwable;
/*UserException ex;
if(throwable instanceof UserException){
ex = (UserException) throwable;
throw ex;
}else{
throw throwable;
}*/
/*HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return ResultUtils.error(status.value(), status.name());
}
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
return ResultUtils.error((Integer) body.get("status"), (String)body.get("message"));*/
}
/**
* Determine if the stacktrace attribute should be included.
* @param request the source request
* @param produces the media type produced (or {@code MediaType.ALL})
* @return if the stacktrace attribute should be included
*/
private boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
ErrorProperties.IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
return true;
}
if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
return getTraceParameter(request);
}
return false;
}
/**
* Provide access to the error properties.
* @return the error properties
*/
private ErrorProperties getErrorProperties() {
return this.errorProperties;
}
/**
* Returns the path of the error page.
*
* @return the error path
*/
@Override
public String getErrorPath() {
return this.errorProperties.getPath();
}
}
自定義ErrorController中錯誤處理的方法中,也可以直接將異常拋出,這樣異常就會交給統一異常處理器進行處理。
//@RequestMapping(value = "/error")
@RequestMapping("${server.error.path}") //從properties文件中獲取
@ResponseBody
public Result<Object> error(HttpServletRequest request) throws Throwable {
logger.debug("myPath = " + myPath);
//發生錯誤之後直接將異常拋出去,異常會到統一異常處理器中處理
WebRequest webRequest = new ServletWebRequest(request);
Throwable throwable = this.mErrorAttributes.getError(webRequest).getCause();
UserException ex;
if(throwable instanceof UserException){
ex = (UserException) throwable;
throw ex;
}else{
throw throwable;
}
/*HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return ResultUtils.error(status.value(), status.name());
}
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
return ResultUtils.error((Integer) body.get("status"), (String)body.get("message"));*/
}