我们在实际开发中,会因为各种问题而导致无法正常访问网址,网站的对象是群众,如果出现各种的报错信息,对于用户的体验是非常的不好的,所以我们需要对项目的内部进行异常处理,
保证用户的体验舒爽
目录
完整目录结构
1、异常处理一:默认异常处理机制
如果访问到了不存在的页面,或服务器出现异常springboot会返回默认的错误页面
可以将自定义的错误页面放在springboot默认规则下的路径
默认规则:
默认情况下boot提供了/error处理所有错误的映射
也就是在/error目录下放置的页面可以处理对应的错误映射,4xx.html处理4xx错误
浏览器客户端会响应一个 “whitelabel” 错误视图
资源文件夹目录
1)导入前端模板坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.25</version>
</dependency>
2)创建templates.error文件夹
resource资源文件夹下,目录结构
3)自定义错误视图文件
把异常页面统一放在templates的error文件夹下
404、500
4)配置模板
加上这段配置
spring.thymeleaf.prefix=classpath:/templates/
5)controller层模拟错误
//请求异常
@RequestMapping("/index01")
public String testError(){
//这里报错
int i=10/0;
return "redirect:/login.html";
}
//请求正确
@RequestMapping("/index02")
public String testError02(){
return "redirect:/login.html";
}
这段代码必须加上,不然异常无法处理,会出现找不到localhost页面,而不会报出异常
@RequestMapping("/error")
public String handleError(HttpServletRequest request) {
// 发生异常后的处理逻辑
return "error.html";
}
这样无论出现什么形式的报错,异常信息都会被捕获,然后抛出对应的异常页面信息
比如:我输入一个不存在的路径Title
它会获取异常抛出异常页面
2、异常处理二:自定义全局异常处理
实际项目处理中会出现各种各样的异常,如果以上面的方式处理异常,我们就需要写大量的异常处理页面,太过麻烦,可以定义中一个全部异常处理页面
使用了全局异常处理后原本的/error页面就会失效
1)添加异常页面error
thymeleaf会自动查找名为“error”的视图来呈现任何错误
该页面路径:src/main/resources/templates
<body>
<div th:text="${exception}"></div>
<div th:text="${url}"></div>
<h1>页面状态异常!!!</h1>
</body>
2)自定义全局异常处理类
共有三种方法:这里举例两种
一、注解
自定义一个全局异常处理类,只需上面加上 @ControllerAdvice 注解
同时要在执行异常处理的方法上标记@ExceptionHandler
@ControllerAdvice注解的原理是SpringAOP提供的,将Controller层的方法作为切面,从而对Controller层方法进行拦截处理
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler02 {
@ExceptionHandler({ArithmeticException.class})
public String handleArithmeticException(){
//可输出日志信息
log.info("算术异常处理");
return "error";
}
@ExceptionHandler({NullPointerException.class})
public String handleNullPointerException(Exception e){
log.info("空指针异常处理");
return "error";
}
}
运行结果:
控制台不会出现原来的报错信息,报错异常以日志的形式打印输出
这样可以保证无论出现何种异常都会跳转到这个异常页面
不足:无法获取异常信息
二、优化
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler implements ErrorController {
@ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest request, Exception e) throws Exception {
String url=request.getRequestURL().toString();
log.error("URL:"+url);
log.error("Exception:"+e);
ModelAndView mav = new ModelAndView("error");
// 添加错误信息到模型中
mav.addObject("exception", e);
mav.addObject("url", url);
return mav;
}
}
页面效果:
控制台效果:
三、HandlerExceptionResolver接口处理异常
@Configuration
public class HandlerExceptionResolverImpl implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
String url=request.getRequestURL().toString();
ModelAndView modelAndView=new ModelAndView("error");
modelAndView.addObject("exception", ex);
modelAndView.addObject("url", url);
modelAndView.addObject("msg","实现HandlerExceptionResolver接口处理异常");
return modelAndView;
}
}
3、数据传输协议实现
由于我们返回值的类型多样,不利于前端处理,因此需要用统一格式封装结果集,并补充一些提示信息,实现表现层和前端数据传输
1)定义统一返回格式
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
//是否成功
private Boolean flag;
//返回码
private Integer code;
//返回消息
private String message;
//返回数据
private Object data;
public Result(Integer code,Object data){
this.data=data;
this.code=code;
}
public Result(Integer code,Object data,Boolean flag){
this.data=data;
this.code=code;
this.flag=flag;
}
}
2)定义标记码类
public class StatusCode {
//成功
public static final int OK=20000;
//失败
public static final int ERROR=20001;
//用户名或密码错误
public static final int LOGINERROR=20002;
//权限不足
public static final int ACCESSERROR=20003;
//远程调用失败
public static final int REMOTEERROR=20004;
//重复操作
public static final int REPERROR=20005;
//没有对应的抢购数据
public static final int NOTFOUNDERROR=20006;
/**
* 业务操作结果反馈
*/
public static final Integer SAVE_OK = 20011;
public static final Integer DELETE_OK = 20021;
public static final Integer UPDATE_OK = 20031;
public static final Integer GET_OK = 20041;
public static final Integer SAVE_ERR = 20010;
public static final Integer DELETE_ERR = 20020;
public static final Integer UPDATE_ERR = 20030;
public static final Integer GET_ERR = 20040;
/**
* 异常类型标识
*/
public static final Integer SYSTEM_ERR = 50001;
public static final Integer BUSINESS_ERR = 50002;
public static final Integer SYSTEM_UNKNOW_ERR = 59999;
}
4、异常处理三:状态码处理异常
前面所写的类统统先注掉
1)统一处理程序抛出异常
@RestControllerAdvice
public class ProjectExceptionAdvice {
//其他异常或者自定义异常可以自行拦截,修改 @ExceptionHandler即可
//异常1
//异常2
//拦截所有的异常信息——如果以上异常都拦截不住这就是最后的防线
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
//记录日志
//通知运维
//通知开发
ex.printStackTrace();
return new Result(false,StatusCode.ERROR, ex.getMessage());
}
}
注意:注解@ExceptionHandler不可以在一个类中同时存在
2)统一处理业务异常
public class BusinessException extends RuntimeException{
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
public BusinessException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
}
3)统一处理系统异常
public class SystemException extends RuntimeException{
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public SystemException(Integer code, String message) {
super(message);
this.code = code;
}
public SystemException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
}
异常处理的效果
页面返回结果
控制台响应结果
控制台响应结果和页面显示的一致
后端控制台正常报错
5、异常处理四:状态码统一异常、返回处理
异常处理三中我返回了异常信息到页面,但后端却没有数据信息的返回
并且异常处理类太多了,这样并不好
注释掉原先的类
1)接口数据统一返回类状态码
①结果集Result02
@Data
public class Result02<T> implements Serializable {
//是否成功
private Boolean flag;
//返回码
private Integer code;
//返回消息
private String message;
//返回数据
private T data;
public Result02(Boolean flag, Integer code, String message, T data) {
this.flag = flag;
this.code = code;
this.message = message;
this.data = data;
}
public Result02(Boolean flag, Integer code, String message) {
this.flag = flag;
this.code = code;
this.message = message;
}
public Result02() {
this.flag=true;
this.code=StatusCode.OK;
this.message="操作成功!!!";
}
}
②返回码StatusCode
public class StatusCode {
//成功
public static final int OK=20000;
//失败
public static final int ERROR=20001;
//用户名或密码错误
public static final int LOGINERROR=20002;
//权限不足
public static final int ACCESSERROR=20003;
//远程调用失败
public static final int REMOTEERROR=20004;
//重复操作
public static final int REPERROR=20005;
//没有对应的抢购数据
public static final int NOTFOUNDERROR=20006;
/**
* 业务操作结果反馈
*/
public static final Integer SAVE_OK = 20011;
public static final Integer DELETE_OK = 20021;
public static final Integer UPDATE_OK = 20031;
public static final Integer GET_OK = 20041;
public static final Integer SAVE_ERR = 20010;
public static final Integer DELETE_ERR = 20020;
public static final Integer UPDATE_ERR = 20030;
public static final Integer GET_ERR = 20040;
/**
* 异常类型标识
*/
public static final Integer SYSTEM_ERR = 50001;
public static final Integer BUSINESS_ERR = 50002;
public static final Integer SYSTEM_UNKNOW_ERR = 59999;
}
2)controller层异常拦截处理
这里的@ExceptionHandler不能在一个类中同时存在
@RestControllerAdvice
public class ProjectExceptionAdvice {
//拦截所有的异常信息
//@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
//记录日志
//通知运维
//通知开发
ex.printStackTrace();
return new Result(StatusCode.ERROR, ex.getMessage(), false);
}
//这里我再定义了一个结果类Reslut02,和上一个方法基本相同
//拦截所有的异常信息
@ExceptionHandler(Exception.class)
public Result02 doException02(Exception ex){
//记录日志
//通知运维
//通知开发
ex.printStackTrace();
return new Result02(false, StatusCode.ERROR, ex.getMessage());
}
}
3)controller层返回信息拦截
@ControllerAdvice
public class ResponseBodyHandle implements ResponseBodyAdvice<Result02> {
private Logger logger= (Logger) LoggerFactory.getLogger(ResponseBodyHandle.class);
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
//支持所有方法
return true;
}
public Result02 beforeBodyWrite(Result02 body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
String methodName= returnType.getMethod().getName();
logger.info("{}方法返回值={}",methodName,JSON.toJSONString(body));
return body;
}
}
结果:
前端页面显示结果
后端结果显示
这样就实现了前端和后端的异常拦截和信息返回处理