问题:为什么要在数据处理层controller层做全局异常捕获?
分析:如果一个异常,在service层没有被catch到,那么,这个异常就传递到了controller层,如果controller不做处理,页面看到的就是500了。全局异常处理,针对的是这种情况:当异常不能被catch的时候,避免给用户展示500界面,在controller层做的统一处理,跳转到我们自定义的错误页面,或者返回自定义的error信息。
方案:@ControllerAdvice+@ExceptionHandler
package com.powersi.handler;
import cn.hsa.hsaf.core.framework.web.exception.AppException;
import com.alibaba.fastjson.JSON;
import com.powersi.common.exception.BaseExcCodesEnum;
import com.powersi.common.exception.BizRtException;
import com.powersi.common.utils.ResultBuilder;
import com.powersi.common.utils.ResultDto;
import com.powersi.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.rpc.RpcException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName HSAFExceptionHandler
* @Description HSAF异常处理器
* @Author ****
* @Date 2021/1/12 11:09
* @Version 1.0
**/
@ControllerAdvice
@Slf4j
public class WebExceptionHandler {
/**
* 处理自定义运行时的业务异常
* @param request
* @param ex
* @return
*/
@ExceptionHandler(BizRtException.class)
public Object exceptionHandler(BizRtException ex, HttpServletRequest request, HttpServletResponse response) {
log.error("BizRtException:" + ex.getLocalizedMessage(), ex);
return getObject(response, ResultBuilder.genExpResult(ex));
}
/**
* 处理空指针的异常
* @param request
* @param ex
* @return
*/
@ExceptionHandler(value =NullPointerException.class)
public Object exceptionHandler(NullPointerException ex, HttpServletRequest request, HttpServletResponse response) {
log.error("NullPointerException:" + ex.getLocalizedMessage(), ex);
return getObject(response, ResultBuilder.genExpResult(ex));
}
/**
* Handle exceptions thrown by handlers.
*/
@ExceptionHandler(AppException.class)
public Object exceptionHandler(Exception ex, HttpServletRequest request, HttpServletResponse response) {
log.error("exception:" + ex.getLocalizedMessage(), ex);
return getObject(response, ResultBuilder.genExpResult(ex));
}
@ExceptionHandler({RpcException.class})
public Object RpcException(Exception ex,HttpServletRequest request, HttpServletResponse response) {
log.error("rpc接口参数校验异常。{}", ex.getMessage());
log.error("exception:" + ex.getLocalizedMessage(), ex);
String errMsg = ((ConstraintViolationException) ex.getCause()).getConstraintViolations()
.iterator().next().getMessage();
//ResponseEntity 为自定义的返回类,非springframework.http.ResponseEntity,当然此处可以自己定义返回类,只需将 异常信息 errMsg 返回即可
return getObject(response, ResultBuilder.buildResult(BaseExcCodesEnum.PARAM_EXCEPTION.getCode(), null,errMsg));
}
/**
* 自定义验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object validExceptionHandler(MethodArgumentNotValidException e, HttpServletResponse response) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
StringBuffer stringBuffer = new StringBuffer(16);
if (!CollectionUtils.isEmpty(fieldErrors)) {
List<String> fieldList = new ArrayList<>();
// 遍历所有错误信息,如果一个字段有多个校验注解,只取第一个错误信息
fieldErrors.forEach(fieldError -> {
if(fieldList.contains(fieldError.getField())){
return;
}
fieldList.add(fieldError.getField());
stringBuffer.append(fieldError.getDefaultMessage()).append(",");
});
}
String message = "";
if(StringUtils.isNotEmpty(stringBuffer.toString())){
message = stringBuffer.toString().substring(0,stringBuffer.toString().length()-1);
}
return getObject(response, ResultBuilder.buildResult(BaseExcCodesEnum.PARAM_EXCEPTION.getCode(), null, message));
}
private Object getObject(HttpServletResponse response, ResultDto resultDto) {
response.setCharacterEncoding("utf-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setHeader("Access-control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setStatus(HttpStatus.OK.value());
try {
response.getWriter().write(JSON.toJSONString((resultDto)));
} catch (IOException e) {
log.error("异常返回出错" + e.getMessage(),e);
}
return new ModelAndView();
}
}
异常都可以自己定义,基本就是错误信息+错误码
public class AppException extends RuntimeException {
private static final long serialVersionUID = -7611643172712984323L;
int code = -1;
public AppException() {
}
public AppException(int code) {
this.code = code;
}
public AppException(String message) {
super(message);
}
public AppException(int code, String message) {
super(message);
this.code = code;
}
public AppException(Throwable cause) {
super(cause);
}
public AppException(String message, Throwable cause) {
super(message, cause);
}
public AppException(int code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public int getCode() {
return this.code;
}
public void setCode(int code) {
this.code = code;
}
}
这样我们service层throw或者throws抛出到controller层的异常,在controller层都不需要另外自己写代码处理了。