统一处理某一类异常,能够减少代码的重复度和复杂度,有利于代码的维护。
Spring 统一异常处理有 4 种方式,分别为:
-
使用 @ ExceptionHandler 注解
-
实现 HandlerExceptionResolver 接口
-
使用 @controlleradvice 注解
-
使用 @Restcontrolleradvice注解
1.1 使用 @ ExceptionHandler 注解
使用@ExceptionHandler注解作用在方法上面,参数是具体的异常类型。
一旦系统抛出这种类型的异常时,会引导到该方法来处理。
但是它的缺陷很明显,处理异常的方法和出错的方法(或者异常最终抛出来的地方)必须在同一个controller,不能全局控制。
@GetMapping("/gooderr")
public Good test3(Good good){
int i=5/0;
return good;
}
//如果不加这个 就会报500页面错误
@ExceptionHandler(Exception.class)
public ModelAndView myException(Exception e) {
ModelAndView error = new ModelAndView("error");
error.addObject("error", e.getMessage());
return error;
}
编写一个error.jsp文件
<%@page pageEncoding="UTF-8" language="java" contentType="text/html;UTF-8" %>
<html>
<body>
<h1>这是错误页面</h1>
<p>${error}</p>
</body>
</html>
1.2 实现 HandlerExceptionResolver 接口
springmvc提供一个HandlerExceptionResolver接口,自定义全局异常处理器必须要实现这个接口,如下:
创建一个包:
handlerexr包下创建
@Component
public class HandlerResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, Exception e) {
ModelAndView mv=new ModelAndView();
mv.addObject("msg",e.getMessage());
mv.setViewName("error");
return mv;
}
}
测试运行:http://localhost:8080/gooderr
分类异常处理:创建三个异常页面 复制三个 分别是空指针,Arithmetic计算异常,全局异常
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h2>测试成功,欢迎来到我的世界</h2>
<p>空指针异常:${msg}</p>
<img src="../img/q1.jpg">
</body>
</html>
先验证是否正确:http://localhost:8080/gooderr
异常验证controller:
@GetMapping("/gooderr")
@ResponseBody
public Good test2(Good good) throws BizException {
if(good.getNum()==1){
int i=5/0;
}else if (good.getNum()==2){
String name=null;
name.equals("abc");
}
return good;
}
访问地址:http://localhost:8080/gooderr?num=1 计算异常
http://localhost:8080/gooderr?num=2 空指针异常
内容输出:
@Component
public class HandlerResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, Exception e) {
ModelAndView mv=new ModelAndView();
if(e instanceof ArithmeticException){
mv.setViewName("Arithmetic");
mv.addObject("msg",e.getMessage());
}
if(e instanceof NullPointerException){
mv.setViewName("nullpoint");
}
mv.addObject("msg",e.toString());
/*
mv.addObject("msg",e.getMessage());
mv.setViewName("error");*/
return mv;
}
}
直接测试
1.3 使用 @ControllerAdvice+ @ ExceptionHandler 注解 (重点)
@ExceptionHandler 可以返回 ModelAndView 定制异常视图。
@ControllerAdvice 是一个增强的 Controller,@ExceptionHandler 可以拦截特定的异常,因此可以更精确的配置异常处理逻辑。
创建一个类advice中: MyExceptionAdvice
@ControllerAdvice
public class MyExceptionAdvice {
@ExceptionHandler(NullPointerException.class)
public ModelAndView processException(NullPointerException ex){
ModelAndView mv = new ModelAndView("nullpoint");
mv.addObject("msg",ex.toString());
return mv;
}
@ExceptionHandler(ArithmeticException.class)
public ModelAndView processException2(ArithmeticException ex){
ModelAndView mv = new ModelAndView("arith");
mv.addObject("msg",ex.toString());
return mv;
}
}
测试运行:
简写方式:
@ExceptionHandler(Exception.class)
public ModelAndView processException(Exception e){
ModelAndView mv=new ModelAndView();
if(e instanceof ArithmeticException){
mv.setViewName("arith");
mv.addObject("msg",e.getMessage());
}
if(e instanceof NullPointerException){
mv.setViewName("nullpoint");
mv.addObject("msg",e.toString());
}
// mv.addObject("msg",e.toString());
return mv;
}
但是使用第一种方式还是比较清晰的:
自定义异常类:自己编写一个异常类:
public class BizException extends Exception{
private int code;
public BizException(String msg){
super(msg);
}
public BizException(int code,String msg){
super(msg);
this.code=code;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
在MyExceptionAdvice中加一个方法:
@ExceptionHandler(BizException.class)
public ModelAndView processException2(BizException e){
ModelAndView mv=new ModelAndView();
mv.addObject("msg",e.getCode()+"-->"+e.toString());
mv.setViewName("biz");
return mv;
}
创建一个Biz的异常jsp页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h2>测试成功,欢迎来到我的世界</h2>
<p>自己定义的异常:${msg}</p>
</body>
</html>
controller中修改:
@GetMapping("/gooderr2")
@ResponseBody
public Good test3(Good good) throws BizException {
if(good.getNum()==1){
int i=5/0;
}else if (good.getNum()==2){
String name=null;
name.equals("abc");
}else if(good.getNum()==3){
throw new BizException(301,"我看错你了");
}else if(good.getNum()==4){
throw new BizException(401,"我看错你了");
}
return good;
}
1.4 针对RestController 统一处理
@RestControllerAdvice
public class MyRestContollerAvice {
@ExceptionHandler( Exception.class)
public Object error(Exception ex){
Map map = new HashMap();
map.put("code",1);
map.put("message",ex.getMessage());
return map;
}
}
1.5 扩展 统一返回值
创建统一返回值类:
@Data
public class ResponseDTO {
private int code;
private String message;
private Object data;
public static ResponseDTO success(Object data){
ResponseDTO dto=new ResponseDTO();
dto.setData(data);
return dto;
}
public static ResponseDTO error(int code,String msg){
ResponseDTO dto=new ResponseDTO();
dto.setCode(code);
dto.setMessage(msg);
return dto;
}
}
统一异常处理
@RestControllerAdvice
@Slf4j
public class MyResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@ExceptionHandler
public Object processException(Exception ex){
ex.printStackTrace();
if(ex instanceof BizException){
return ResponseDTO.error(((BizException)ex).getCode(),ex.getMessage());
}
//其它异常
return ResponseDTO.error(999,ex.getMessage());
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
Object responseDTO = null;
responseDTO = ResponseDTO.success(body);
//针对String返回值,我们做了下特殊处理,因为我们硬生生的给人家套了个外壳,StringHttpMessageConverter无能为力,所以我们自己把它转换成string就可以了
if (selectedConverterType == StringHttpMessageConverter.class){
return JSONUtil.toJsonStr(responseDTO);
}
return responseDTO;
}
}
整体代码:
package com.by.advice;
@RestControllerAdvice
public class MyRestControllerAdvice implements ResponseBodyAdvice<Object>{
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
/**
* beforeBodyWrite 在controller返回之前
* 执行MethodParameter 获取方法参数中的类型
* @param body 响应体内容
* @param methodParameter 获取方法参数中的类型
* @param mediaType 媒体类型:决定浏览器将以什么形式、什么编码对资源进行解析
* @param selectedConverter 报文信息转换器。
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> selectedConverter,
ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
if(body instanceof ResponseDTO){
return body;
}
ResponseDTO success = ResponseDTO.success(body);
//针对String返回值 我们做特殊的处理
if(selectedConverter== StringHttpMessageConverter.class){
return JSONUtil.toJsonStr(success);
}else {
return success;
}
}
/* @ExceptionHandler(Exception.class)
public Object error( Exception e){
ResponseDTO dto=new ResponseDTO();
if(e instanceof BizException){
BizException biz= (BizException) e;
dto.setCode(biz.getCode());
}else{
dto.setCode(666);
}
dto.setMessage(e.getMessage());
return dto;
}*/
/* @ExceptionHandler(BindException.class)
public String processException(BindException e){
BindingResult result = e.getBindingResult();
StringBuilder sb=new StringBuilder();
List<ObjectError> allError=result.getAllErrors();
for (ObjectError error : allError) {
sb.append(error.getDefaultMessage());
}
return sb.toString();
}*/
@ExceptionHandler(Exception.class)
public Object error( Exception e){
int code=666;
if(e instanceof BizException){
BizException biz= (BizException) e;
code=biz.getCode();
}
if(e instanceof MethodArgumentNotValidException){
StringBuilder sb = new StringBuilder();
List<ObjectError> allErrors = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors();
for(ObjectError error : allErrors){
sb.append(error.getDefaultMessage());
}
return ResponseDTO.error(888,sb.toString());
}
return ResponseDTO.error(code,e.getMessage());
}
}