SpringBoot中全局异常处理的5种实现方式小结
目录
- 前言
- 为什么需要全局异常处理?
- 方式一:@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler
- 方式二:实现HandlerExceptionResolver接口
- 方式三:使用SimpleMappingExceptionResolver
- 方式四:自定义ErrorController
- 方式五:使用错误页面模板
- 实战示例:完整的异常处理体系
- 各方式对比与使用建议
- 最佳实践总结
- 结语
前言
在实际开发中,异常处理是一个非常重要的环节。合理的异常处理机制不仅能提高系统的健壮性,还能大大提升用户体验。本文将详细介绍SpringBootpython中全局异常处理的几种实现方式。
为什么需要全局异常处理?
如果没有统一的异常处理机制,当系统发生异常时,可能会导致以下问题
- 用户体验差:用户可能看到一些技术性的错误信息,如堆栈跟踪
- 安全隐患:暴露系统内部错误详情可能会被攻击者利用
- 维护困难:分散在各处的异常处理代码增加了维护难度
- 响应格式不一致:不同接口返回的错误格式不统一,增加前端处理难度
下面,来看几种在SpringBoot中实现全局异常处理的方式。
方式一:@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler
这是SpringBoot中最常用的全局异常处理方式。
首先,定义一个统一的返回结果类:
public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(Integer code, String message) { Result<T> result = new Result<>(); result.setCode(code); result.setMessage(message); return result; } // getter和setter方法省略 }
然后,定义自定义异常:
public class BusinessException extends RuntimeException { private Integer code; public BusinessException(String message) { super(message); this.code = 500; } public BusinessException(Integer code, String message) { super(message); this.code = code; } public Integer getCode() { return code; } }
创建全局异常处理类:
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.NoHandlerFoundException; @RestControllerAdvice public class GlobalExceptionHandler { /** * 处理自定义业务异常 */ @ExceptionHandler(BusinessException.class) public Result<Void> handleBusinessException(BusinessException e) { return Result.error(e.getCode(), e.getMessage()); } /** * 处理参数校验异常 */ @ExceptionHandler(ConstraintViolationException.class) public Result<Void> handleValidationException(ConstraintViolationException e) { return Result.error(400, "参数校验失败:" + e.getMessage()); } /** * 处理资源找不到异常 */ @ExceptionHandler(NoHandlerFoundException.class) public Result<Void> handleNotFoundException(NoHandlerFoundException e) { return Result.error(404, "请求的资源不存在"); } /** * 处理其他所有未捕获的异常 */ @ExceptionHandler(Exception.class) public Result<Void> handleException(Exception e) { return Result.error(500, "服务器内部错误:" + e.getMessage()); } }
优点
- 代码简洁清晰,易于维护
- 可以针对不同的异常类型定义不同的处理方法
- 与Spring MVC结合紧密,可以获取请求和响应上下文
方式二:实现HandlerExceptionResolver接口
HandlerExceptionResolver是Spring MVC中用于解析异常的接口,我们可以通过实现此接口来自定义异常处理逻辑。
import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import com.fasterXML.jackson.databind.ObjectMapper; import Javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @Component public class CustomExceptionResolver implements HandlerExceptionResolver { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { Result<?> result; python // 处理不同类型的异常 if (ex instanceof BusinessException) { BusinessException businessException = (BusinessException) ex; result = Result.error(businessException.getCode(), businessException.getMessage()); } else { result = Result.error(500, "服务器内部错误:" + ex.getMessage()); } // 设置响应类型和状态码 response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_OK); try { // 将错误信息写入响应 PrintWriter writer = response.getWriter(); writer.write(objectMapper.writeValueAsString(result)); writer.flush(); writer.close(); } catch (IOException e) { e.printStackTrace(); } // 返回空的ModelAndView表示异常已经处理完成 return new ModelAndView(); } }
优点
- 可以完全控制异常处理的过程
- 可以直接操作HttpServletRequest和HttpServletResponse
- 适合需要特殊处理的场景
缺点
- 代码相对复杂
- 无法利用Spring MVC的注解优势
方式三:使用SimpleMappingExceptionResolver
SimpleMappingExceptionResolver是HandlerExceptionResolver的一个简单实现,适用于返回错误视图的场景。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import java.util.Properties; @Configuration public class ExceptionConfig { @Bean public SimpleMappingExceptionResolver simpleMappingExceptionResolver() { SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver(); // 设置默认错误页面 resolver.setDefaultErrorView("error/default"); // 设置异常映射 Properties mappings = new Properties(); mappings.setProperty(BusinessException.class.getName(), "error/business"); mappings.setProperty(RuntimeException.class.getName(), "error/runtime"); resolver.setExceptionMappings(mappings); // 设置异常属性名,默认为"exception" resolver.setExceptionAttribute("ex"); return resolver; } }
对应的错误页面模板(使用Thymeleaf):
<!-- templates/error/business.html --> <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>业务错误</title> </head> <body> <h1>业务错误</h1> <p th:text="${ex.message}">错误信息</p> </body> </html>
优点
- 配置简单
- 适合返回错误页面的场景
缺点
- 主要适用于返回视图,不适合RESTful API
- 灵活性有限
方式四:自定义ErrorController
Spring Boot提供了BasicErrorController来处理应用中的错误,我们可以通过继承或替换它来自定义错误处理逻辑。
import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import java.util.Map; @Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class CustomErrorController implements ErrorController { @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public ResponseEntity<Result<Void>> handleError(HttpServletRequest request) { HttpStatus status = getStatus(request); Integer statusCode = status.value(); String message = "未知错误"; switch (statusCode) { case 404: message = "请求的资源不存在"; break; case 403: message = "没有权限访问该资源"; break; case 500: message = "服务器内部错误"; break; default: break; } return new ResponseEntity<>(Result.error(statusCode, message), status); } @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public String handleErrorHtml(HttpServletRequest request, Map<String, Object> model) { HttpStatus status = getStatus(request); // 添加错误信息到模型 model.put("status", status.value()); model.put("message", status.getReasonPhrase()); // 返回错误页面 return "error/error"; } private HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); if (statusCode == null) { return HttpStatus.INTERNAL_SERVER_ERROR; } try { return HttpStatus.valueOf(statusCode); } catch (Exception ex) { return HttpStatus.INTERNAL_SERVER_ERROR; } } }
优点
- 可以同时处理HTML和JSON响应
- 对所有未处理的错误提供统一的处理方式
- 可以获取错误的状态码和详细信息
缺点
- 只能处理已经发生的HTTP错误,无法拦截自定义异常
- 一般作为兜底方案使用
方式五:使用错误页面模板
Spring Boot支持通过静态HTML页面或模板来展示特定状态码的错误。只需要在templates/error/目录下创建对应状态码的页面即可。
目录结构:
src/
main/ resources/ templates/ error/ 404.html 500.html error.html # 默认错误页面
例如,404.html的内容:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>页面不存在</title> </head> <body> <h1>404 - 页面不存在</h1> <p>您请求的页面不存在,请检查URL是否正确。</p> <a href="/" rel="external nofollow" >返回首页</a> </body> </html>
优点
- 配置极其简单,只需创建对应的页面即可
- 适合简单的Web应用
缺点
- 灵活性有限
- 不适合RESTful API
- 无法处理自定义异常
实战示例:完整的异常处理体系
下面提供一个完整的异常处理体系示例,组合了多种方式:
首先,创建异常体系:
// 基础异常类 public abstract class BaseException extends RuntimeException { private final int code; public BaseException(int code, String message) { super(message); this.code = code; } public int getCode() { return code; } } // 业务异常 public class BusinessException extends BaseException { public BusinessException(String message) { super(400, message); } public BusinessException(int code, String message) { super(code, message); } } // 系统异常 puphpblic class SystemException extends BaseException { public SystemException(String message) { super(500, message); } public SystemException(int code, String message) { super(code, message); } } // 权限异常 public class PermissionException extends BaseException { public PermissionException(String message) { super(403, message); } }
创建全局异常处理器:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.servlet.NoHandlerFoundException; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @RestControllerAdvice public class GlobalExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** * 处理自定义基础异常 */ @ExceptionHandler(BaseException.class) public Result<?> handleBaseException(BaseException e) { logger.error("业务异常:{}", e.getMessage()); return Result.error(e.getCode(), e.getMessage()); } /** * 处理参数校验异常(@Valid) */ @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors(); String errorMsg = fieldErrors.stream() .map(error -> error.getField() + ": " + error.getDefaultMessage()) .collect(Collectors.joining(", ")); logger.error("参数校验错误:{}", errorMsg); return Result.error(400, "参数校验错误: " + errorMsg); } /** * 处理所有其他异常 */ @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Result<?> handleException(Exception e) { logger.error("系统异常:", e); return Result.error(500, "服务器内部错误,请联系管理员"); } }
使用示例:
@RestController @RequestMapping("/api") public class UserController { android @GetMapping("/{id}") public Result<User> getUser(@PathVariable Long id) { if (id <= 0) { throw new BusinessException("用户ID必须大于0"); } if (id > 100) { throw new SystemException("系统维护中"); } if (id == 10) { throw new PermissionException("没有权限查看此用户"); } // 模拟查询用户 User user = new User(id, "用户" + id, "user" + id + "@example.com"); return Result.success(user); } }
各方式对比与使用建议
实现方式 | 适用场景 | 灵活性 | 复杂度 |
---|---|---|---|
@ControllerAdvice + @ExceptionHandler | RESTful API、前后端分离项目 | 高 | 低 |
HandlerExceptionResolver | 需要精细控制异常处理过程的场景 | 高 | 中 |
SimpleMappingExceptionResolver | 传统Web应用,需要返回错误页面 | 中 | 低 |
自定义ErrorController | 需要自定义错误页面和错误响应的场景 | 中 | 中 |
错误页面模板 | 简单的Web应用,只需自定义错误页面 | 低 | 低 |
建议
对于RESTful API或前后端分离项目:优先选择@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler方式,它提供了良好的灵活性和简洁的代码结构。
对于需要返回错误页面的传统Web应用:可以使用SimpleMappingExceptionResolver或错误页面模板方式。
对于复杂系统:可以组合使用多种方式,例如
- 使用@ControllerAdvice处理业务异常
- 使用自定义ErrorController处理未捕获的HTTP错误
- 使用错误页面模板提供友好的错误页面
最佳实践总结
分层异常处理:根据业务需求,设计合理的异常继承体系,便于分类处理。
统一返回格式:无论成功还是失败,都使用统一的返回格式,便于前端处理。
合理记录日志:在异常处理中记录日志,可以帮助排查问题。不同级别的异常使用不同级别的日志。
区分开发和生产环境:在开发环境可以返回详细的错误信息,而在生产环境则应该隐藏敏感信息。
异常分类
- 业务异常:用户操作引起的可预期异常
- 系统异常:系统内部错误
- 第三方服务异常:调用外部服务失败等
不要忽略异常:即使是捕获异常后不需要处理,也应该至少记录日志。
结语
在Spring Boot应用中,全局异常处理是提高系统健壮性和用户体验的重要环http://www.devze.com节。通过本文介绍的几种实现方式,开发者可以根据实际需求选择合适的实现方案。在实际项目中,往往需要结合多种方式,构建一个完整的异常处理体系。
以上就是SpringBoot中全局异常处理的5种实现方式小结的详细内容,更多关于SpringBoot全局异常处理的资料请关注编程客栈(www.devze.com)其它相关文章!
精彩评论