【前言】
前几天晚上收到了API网关我接口异常报警。我就去看了看我们的接口日志发现没有任何异常就感觉很神奇,然后找API网关的相关人员查了下日志(如下),发现这个日志是爬虫导致的并且我们系统并没有显示,发现这个异常并不是接口里的异常而且在Controller层之前的异常。所以要将全局异常处理下方便以后排查定位问题
【全局异常处理】
SpringBoot中有在个注解@ControllerAdvice这个注解是spring在3.2新增加的controller的增强器,其作用
-
全局异常处理(通常和@ExceptionHandler 一起使用)
-
全局数据绑定(通常和@ModelAttribute一起使用)
-
全局数据预处理(通常和@InitBinder一起使用)
这次主要讲解@ControllerAdvice和@ExceptionHandler来处理一些异常问题和一些我的个人小见解
【示例一】
-
【1.1查询接口示例代码】
来咱们先演示下咱们springBoot中的controller的一些异常,来先上代码
package com.example.demo.controller;
import com.example.demo.common.JsonResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 测试
* @author shuaiTuTu
*/
@RestController
@RequestMapping("/test")
public
class TestController {
@RequestMapping(value = "/info", method = RequestMethod.GET)
public Object getAgentInfo(
@RequestParam(value = "id",required = true) Integer id,
@RequestParam(value = "name") String name
) {
return new JsonResponse(id);
}
}
正常查询结果
如此加入错误id=a,查询结果
错误日志
2022-04-10 17:30:58.055 WARN 104916 --- [nio-8080-exec-9] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'; nested exception is java.lang.NumberFormatException: For input string: "a"]
-
【1.2异常处理示例代码】
异常处理就需要咱们出捕获处理了,来先上代码,@ExceptionHandler可以处理各种指定异常以及自定义异常,大家可以根据自己需要处理
package com.example.demo.common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 处理异常
* @author shuaiTuTu
*/
@ControllerAdvice
public class CustomControllerAdvice {
protected static final Logger logger = LoggerFactory.getLogger(CustomControllerAdvice.class);
@ExceptionHandler(value = MissingServletRequestParameterException.class)
@ResponseBody
public String missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
logger.error("缺少参数异常");
return "缺少参数异常"+ex.getMessage();
}
@ExceptionHandler(value = Exception.class)
@ResponseBody
public String exceptionHandler(Exception ex) {
logger.error("发送异常");
return "异常"+ex.getMessage();
}
}
这次异常处理结果
虽然问题咱们处理了,但是给人的感觉很不好,也就是不够人性化。
【处理方式二】
-
【2.1定义基础返回结果】
由于之前返回结果不统一,报错的处理不友好咱们,增加统一的处理
package com.example.demo.common;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* 返回结果
* @author shuaiTuTu
* @date 2018-10-30
*
*/
public class JsonResponse {
private JsonResponseHeader header;
private Object body;
public
JsonResponse() {
this(ResponseStatusEnum.OK.status, ResponseStatusEnum.OK.msg, null);
}
public
JsonResponse(int status) {
this(status, ResponseStatusEnum.getEnum(status).msg, null);
}
public
JsonResponse(int status, String msg) {
this(status, msg,null);
}
public
JsonResponse(Object body) {
this(ResponseStatusEnum.OK.status, ResponseStatusEnum.OK.msg, body);
}
public
JsonResponse(int status, String msg, Object body) {
this.header = new JsonResponseHeader(status, msg);
if (body == null) {
body = new HashMap();
}
this.body = body;
}
public
JsonResponse(String key, Object value) {
Map<String, Object> bodyObj = new HashMap<>();
bodyObj.put(key, value);
this.header = new JsonResponseHeader(ResponseStatusEnum.OK.status, ResponseStatusEnum.OK.msg);
this.body = bodyObj;
}
public JsonResponseHeader getHeader() {
return header;
}
public void setHeader(JsonResponseHeader header) {
this.header = header;
}
public Object getBody() {
return body;
}
public void setBody(Object body) {
this.body = body;
}
/**
* 业务层 响应头对象
*/
public class JsonResponseHeader {
private int status;
private String msg;
public JsonResponseHeader(int status, String msg) {
this.status = status;
this.msg = msg;
}
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 enum ResponseStatusEnum {
NULL(0, ""),
OK(200, "OK"),
INVALID_SESSION(400, "Request fail: invalid session"),
UNAUTHORIZED(401, "Request fail: unauthorized"),
ERROR(500, "Request fail: unexpected error"),
ACCOUNT_EXPIRED_FORMAL(501, "Request fail: formal account expired"),
ACCOUNT_EXPIRED_TRIAL(502, "Request fail: trial account expired"),
ACCOUNT_IS_STOP(503, "Request fail: account is stop"),
SESSION_EXPIRED(504, "Request fail: session expired"),
INVALID_PARAMETER(600, "Request fail: invalid parameter"),
INVALID_SIGN(601, "Request fail: invalid sign"),
REQUEST_EXPIRED(602, "Request expired"),
REQUEST_MISS(603, "Request missing"),
INVALID_PARAMETER_COUNT(603, "Request fail: 无效的请求数量值"),
INVALID_PARAMETER_TIME(604, "Request fail: 无效的时间范围参数"),
PARAMETER_IS_NOT_PRESENT(605, "Request fail: parameter is not present"),
SMS_SEND_ERROR(606, "sms send failed"),
SMS_SENDER_IS_EMPTY(607, "sms receivers is empty"),
ADD_EVENT_FAILED(608, "add or update or delete event failed");
private int status;
private String msg;
ResponseStatusEnum(int status, String msg) {
this.status = status;
this.msg = msg;
}
public int getStatus() {
return status;
}
public String getMsg() {
return msg;
}
public static ResponseStatusEnum getEnum(int status) {
Optional<ResponseStatusEnum> optional = Arrays.stream(ResponseStatusEnum.values())
.filter(t -> t.getStatus() == status)
.findFirst();
return optional.orElse(NULL);
}
}
}
-
【2.2统一处理异常和特殊处理】
这次在全局处理上增加了入参:HttpServletRequest这个可以获取请求uri方法做些监控处理
package com.example.demo.common; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpRequest; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import java.net.URI; /** * 处理异常 * @author shuaiTuTu */ @ControllerAdvice public class CustomControllerAdvice { protected static final Logger logger = LoggerFactory.getLogger(CustomControllerAdvice.class); @ExceptionHandler(value = MissingServletRequestParameterException.class) @ResponseBody public JsonResponse missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) { logger.error("缺少参数异常"); return new JsonResponse(JsonResponse.ResponseStatusEnum.REQUEST_MISS.getStatus(), ex.getMessage()); } @ExceptionHandler(value = Exception.class) @ResponseBody public JsonResponse exceptionHandler(HttpServletRequest request, Exception ex) { String uri = request.getRequestURI(); logger.error(uri+"发送异常"); return new JsonResponse(JsonResponse.ResponseStatusEnum.ERROR.getStatus(),"uri:" +uri+" EX: "+ ex.getMessage()); } }
-
【2.4结果展示】
总结
-
统一的异常处理,可以减少代码的重复和复杂度,有利于代码维护,快速定位问题
-
利用异常处理作出合理的监控方法提前发现问题