找到了三种错误处理的方法:
- 使用
@ControllerAdvice
注解,这种可以处理所有controller层抛出的异常,但是如果是在controller层try/catch了,那么就无法进行处理了,而且也无法处理不经过controller层的异常,比如404;但是这个也有好处,那就是在处理异常的时候可以根据不同的异常来分开进行处理,而且处理方法也比较简单。 - 实现ErrorController接口或者是继承他的子类,这个类是Spring Boot给我们写好的对错误处理的类,处理的结果就是我们看到的那个白色的界面,另外他也可以返回json数据,这个具体的返回值的类型会根据请求的containType变化。
- 使用AOP拦截各种请求,在进行增强的时候一旦抛出了异常我们就可以根据抛出的异常的不同类型来进行处理。
使用@ControllerAdvice注解
在这个里面,我为了方便自定义了一个Exception类,定义了错误的信息和错误代码等,在错误处理类里面处理这个类的异常就行了。
另外就是,这个小demo支持根据请求的不同返回页面和json数据。这个只是对ContentType做了一个判断,很容易出问题,最好的解决办法应该就是直接在请求分开,根据不同的请求返回不同的错误信息。
package com.example.demo.config;
import com.example.demo.bean.MyException;
import com.example.demo.bean.User;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
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 java.io.IOException;
import java.io.PrintWriter;
@ControllerAdvice
public class MyErrorHandler {
@ExceptionHandler(value = MyException.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, HttpServletResponse rep, MyException e) throws IOException {
ModelAndView modelAndView = new ModelAndView();
if ("application/json".equals(req.getContentType())) {
String msg = e.toJson();
rep.setContentType("application/json");
rep.setCharacterEncoding("UTF-8");
PrintWriter writer = rep.getWriter();
writer.write(msg);
writer.flush();
writer.close();
return null;
}else {
//设置错误界面的地址
modelAndView.setViewName("error");
modelAndView.getModel().put("path", e.getPath());
modelAndView.getModel().put("date", e.getDate());
modelAndView.getModel().put("msg", e.getMsg());
modelAndView.getModel().put("code", e.getCode());
}
return modelAndView;
}
}
对应的错误处理类,我为了省事,直接在类里添加了一个方法,拼接成json字符串。
package com.example.demo.bean;
import java.util.Date;
public class MyException extends Exception{
private Integer code;
private String msg;
private String path;
private Date date;
public String toJson() {
return "{\"code\": "+ code + " , \"msg\":\""+msg+"\", \"path\":\""+
path+"\", \"date\":\""+date+"\"}";
}
实现ErrorController或继承其子类
我试了一下,这个可以根据不同的状态码以及请求类型的不同返回不同的错误信息,但是仍然存在不足:异常信息可以取出来,但是不容易处理;另外就行不能够根据抛出异常的不同来个性化处理。
另外就是,如果是为了方便的话,继承AbstractErrorController
这个类会比较好一点,因为在这个里面提供了一些获取各种信息的辅助方法,可以直接拿过来用,可以省去一些精力。
下面是一个小demo:
package com.example.demo.config;
import com.example.demo.bean.MyException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/error")
public class MyErrorController extends AbstractErrorController {
@Autowired
public MyErrorController(ErrorAttributes errorAttributes) {
this(errorAttributes, null);
}
public MyErrorController(ErrorAttributes errorAttributes, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorViewResolvers);
}
/**
* 处理404问题,这个返回的是页面
* @param req
* @param rep
* @return
*/
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE,value = "404")
public ModelAndView error404Html(HttpServletRequest req, HttpServletResponse rep) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("/error");
modelAndView.getModel().put("code", 404);
modelAndView.getModel().put("msg", "页面找不到了");
modelAndView.getModel().put("path", "");
modelAndView.getModel().put("date", new Date());
return modelAndView;
}
/**
* 同样处理404,这个返回的是json
* @param req
* @param rep
* @return
*/
@RequestMapping(value = "404")
@ResponseBody
public String error404(HttpServletRequest req, HttpServletResponse rep) {
MyException exception = new MyException(404, "页面找不到了", "");
return exception.toJson();
}
/**
* 处理其他的各种问题,这个返回的结果是页面
* @param req
* @param rep
* @return
*/
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest req, HttpServletResponse rep) {
ModelAndView modelAndView = new ModelAndView();
//获取各种状态信息和异常信息
Map<String, Object> errorAttributes = super.getErrorAttributes(req, true);
Object msg = errorAttributes.getOrDefault("trace", "");
modelAndView.setViewName("/error");
modelAndView.getModel().put("code", errorAttributes.get("status"));
modelAndView.getModel().put("msg", msg);
return modelAndView;
}
/**
* 处理其他问题,返回的结果是json
* @param req
* @param rep
* @return
*/
@RequestMapping
@ResponseBody
public String error(HttpServletRequest req, HttpServletResponse rep) {
Map<String, Object> errorAttributes = super.getErrorAttributes(req, true);
String msg = (String) errorAttributes.getOrDefault("trace", "");
return msg;
}
@Override
public String getErrorPath() {
return null;
}
}
上面的两个混用
上面的两个可以同时使用,遇到问题的时候会先通过@ControllerAdvice
进行处理,如果能够成功处理的话,那就没事了,如果不能处理的话会交给ErrorController
的子类进行处理,这样的话刚刚好。
可以使用@ControllerAdvice
标记的类处理各种自己定义好的异常,然后使用ErrorController
的子类处理404或者是其他的问题。另外就是混用的话,我发现404异常并不会进入之前定义好的专门处理404异常的方法,但是状态码的确是404。
使用AOP
需要额外规定Controller层的返回结果,不然做统一处理的话不方便,当然也可以根据类型不同分别处理,不过定义一个统一返回结构体会更加的方便。
我这里写了一个简单的异常统一捕获的类,我的思路是:不管是啥异常,统统抛出,到这个类里面做统一的处理。
如果是业务的异常,那就返回到错误界面,提示相关信息;其余的错误统统返回成系统错误。
@Aspect
@Slf4j
@Component
public class ErrorAspect {
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void controller() {
}
@Around("controller()")
public Object errorHandler(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
//只用于抛出异常的情况
ModelAndView modelAndView = null;
try {
return joinPoint.proceed(args);
}catch (BizException e) {
//处理业务的异常
log.error("errorHandler BizException msg : {}", e.getMsg());
modelAndView = new ModelAndView();
modelAndView.setViewName("/error");
modelAndView.getModel().put("code", e.getCode());
modelAndView.getModel().put("msg", e.getMsg());
} catch (Throwable throwable) {
//处理其他的系统异常
log.error("errorHandler throwable msg : {}", throwable.getMessage());
modelAndView = new ModelAndView();
modelAndView.setViewName("/error");
modelAndView.getModel().put("code", BaseError.SYSTEM_ERROR.getCode());
modelAndView.getModel().put("msg", BaseError.SYSTEM_ERROR.getMsg());
}
return modelAndView;
}
}
参考: