当前位置: 首页 > news >正文

异常的统一处理

统一异常处理的必要性体现在保持代码的一致性、提供更清晰的错误信息、以及更容易排查问题。通过定义统一的异常处理方式,确保在整个应用中对异常的处理保持一致,减少了重复编写相似异常处理逻辑的工作,同时提供友好的错误信息帮助开发者和维护人员更快地定位和解决问题,最终提高了应用的可维护性和可读性。

Spring MVC 如何进行异常处理

当 Controller 里抛出异常时,请求会交给一串 HandlerExceptionResolver 依次处理:

  1. ExceptionHandlerExceptionResolver 异常处理器会找@ExceptionHandler 标注(包含 @RestControllerAdvice)的方法处理。

  2. ResponseStatusExceptionResolver 会识别 @ResponseStatusResponseStatusException 异常类,按注解/参数设定状态码与原因。

  3. DefaultHandlerExceptionResolver 会处理框架异常,将其翻译成 4xx/5xx(如 405/415)。

如果抛出的异常没有相应的处理器处理,就走默认的 /errorBasicErrorController)。

接下来就详细讲一下上面描述的三种异常处理方式

RestControllerAdvice@ExceptionHandler

@ControllerAdvice 或者 @RestControllerAdvice注解的作用是创建一个全局的异常处理器,该类会拦截所有控制器抛出的异常

@ExceptionHandler注解是用来定义异常处理类内部对应异常类型的处理逻辑。

定义一些状态码

/*** 统一业务码定义:同时绑定默认提示与 HTTP 状态*/
​
@Getter
public enum ResponseCode {
​SUCCESS(0, "Success", HttpStatus.OK),
​INTERNAL_ERROR(1, "服务器内部错误", HttpStatus.INTERNAL_SERVER_ERROR),
​USER_INPUT_ERROR(2, "用户输入错误", HttpStatus.BAD_REQUEST),
​AUTHENTICATION_NEEDED(3, "Token过期或无效", HttpStatus.UNAUTHORIZED),
​FORBIDDEN(4, "禁止访问", HttpStatus.FORBIDDEN),
​TOO_FREQUENT_VISIT(5, "访问太频繁,请休息一会儿", HttpStatus.TOO_MANY_REQUESTS),;
​private final int code; // 业务码(给前端判断逻辑)
​private final String message;  // 默认提示语
​private final HttpStatus httpStatus; // http 状态
​ResponseCode(int code, String message, HttpStatus httpStatus) {this.code = code;this.message = message;this.httpStatus = httpStatus;}
}

定义一个业务异常

/*** 统一业务异常*/
​
@Getter
public class AuroraRuntimeException extends RuntimeException {
​private final ResponseCode responseCode;
​public AuroraRuntimeException(ResponseCode responseCode) {super(responseCode.getMessage());this.responseCode = responseCode;}
​public AuroraRuntimeException(ResponseCode responseCode, String message) {super(message);this.responseCode = responseCode;}
}

定义全局异常处理类

@RestControllerAdvice
public class GlobalExceptionHandler {
​// 处理全局业务异常@ExceptionHandler(AuroraRuntimeException.class)public ResponseEntity<ErrorResponse> handleRuntimeException(AuroraRuntimeException e) {return buildErrorResponse(e.getResponseCode());}
​// 构建标准的错误响应private ResponseEntity<ErrorResponse> buildErrorResponse(ResponseCode responseCode) {ErrorResponse errorResponse = new ErrorResponse(LocalDateTime.now(),responseCode.getCode(),responseCode.getHttpStatus().value(),responseCode.getHttpStatus().getReasonPhrase(),responseCode.getMessage());return new ResponseEntity<>(errorResponse, responseCode.getHttpStatus());}
​// 定义错误响应的数据结构@Data@AllArgsConstructor@NoArgsConstructorpublic static class ErrorResponse {@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")private LocalDateTime timestamp;private int code; // 业务码private int status; // http 状态码private String error; // http 状态的简短描述private String message; // 详细的业务信息}
}

该类也可以继承 ResponseEntityExceptionHandler 类,其中定义了一些 mvc 常处理的异常

@ExceptionHandler注解可以用来处理局部异常。该注解在一个 @Controller 控制器类中使用只会在当前类中生效

@RestController
public class TestController {@RequestMapping("/test")public ResultResponse test() {throw new ServiceException();}@ExceptionHandler(ServiceException.class)public ResultResponse handleServiceException(ServiceException e) {}
}

@ResponseStatusResponseStatusException

可以自定义异常类

@ResponseStatus(HttpStatus.NOT_FOUND) // 直接 404
public class NotFoundException extends RuntimeException {public NotFoundException(String msg) { super(msg); }
}

定义了异常类之后,在相应的位置直接抛出该类即可

也可以在代码中直接抛出该类

throw new org.springframework.web.server.ResponseStatusException(HttpStatus.BAD_REQUEST, "参数不正确");

这两种方式会被 ResponseStatusExceptionResolver 自动识别捕获

RestControllerAdvice 中定义的异常处理类优先级最高,当其中定义的异常类被抛出之后, ExceptionHandlerExceptionResolver 会识别处理。它没有定义的异常被抛出,才会由 ResponseStatusExceptionResolver 处理 @ResponseStatus 标注的类

集成了 Spring Security 框架

如果集成了 Spring Security 的框架,在他的 Filter Chain 中如果抛出了 401/403 的异常,这些异常是不会由 Spring MVC 处理的,因为这些异常 Web Filter 级的,发生在 Spring MVC 之前

这里我们主要讲一下在 Spring Security 中,未认证和已认证但权限不够时异常是如和处理的。

其中主要涉及三个核心类 AuthenticationEntryPointAccessDeniedHandlerExceptionTranslationFilter

AuthenticationEntryPoint未认证 时怎么响应(通常返回 401 或跳登录页)。

AccessDeniedHandler已认证,但权限不够 时怎么响应(通常返回 403)。

ExceptionTranslationFilter:Security 过滤器链里的 翻译器捕获 鉴权阶段抛出的异常,然后 转给 上述两个处理器生成响应。

异常处理流程:

请求 →(自定义过滤器/JWT)→ … → FilterSecurityInterceptor(做鉴权)

  • 如果当前没有有效的 Authentication(匿名/未登录)且资源需要登录,鉴权会 AuthenticationException,然后会被 ExceptionTranslationFilter 捕获,接着 调用 AuthenticationEntryPoint.commence() 抛出 401/跳转登录

  • 如果当前已登录,但不满足访问所需权限/角色鉴权会 AccessDeniedException,然后被 ExceptionTranslationFilter 捕获 调用 AccessDeniedHandler.handle() 抛出 403

在 security 加入相应的配置

http.csrf(csrf -> csrf.disable()).sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeHttpRequests(reg -> reg.requestMatchers("/auth/**", "/public/**").permitAll().anyRequest().authenticated()).exceptionHandling(ex -> ex.authenticationEntryPoint(authenticationEntryPoint()) // 401.accessDeniedHandler(accessDeniedHandler())           // 403).addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

通过 Bean 注入 AuthenticationEntryPointAccessDeniedHandler 类,并通过 lamba 实现处理方法

// 401 - 未认证 / Token 无效或过期
@Bean
AuthenticationEntryPoint authenticationEntryPoint() {return (req, res, ex) -> {res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);res.setContentType("application/json;charset=UTF-8");res.getWriter().write("{\"code\":\"AUTHENTICATION_NEEDED\",\"message\":\"未登录或令牌无效\"}");};
}
​
// 403 - 已认证但无权限
@Bean
AccessDeniedHandler accessDeniedHandler() {return (req, res, ex) -> {res.setStatus(HttpServletResponse.SC_FORBIDDEN);res.setContentType("application/json;charset=UTF-8");res.getWriter().write("{\"code\":\"FORBIDDEN\",\"message\":\"没有访问权限\"}");};
}

这也可以通过继承实现

简单看一下 ExceptionTranslationFilter 内部的处理逻辑

try {chain.doFilter(request, response);  // 继续往后交给 FilterSecurityInterceptor 等
} catch (AuthenticationException ae) {// 未认证:清上下文 + 可能保存原始请求 + 调用 EntryPointSecurityContextHolder.clearContext();// 如果用了 SavedRequest(表单站点),会保存请求以便登录后重定向回来authenticationEntryPoint.commence(request, response, ae);
} catch (AccessDeniedException ade) {if (isAnonymousOrRememberMe()) {// 某些情况下把它当成未认证来走 EntryPoint(如 RememberMe 失效)authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(..., ade));} else {// 已认证但权限不足 → 403accessDeniedHandler.handle(request, response, ade);}
}

因此,在 security 中,一般不需要自己手动抛出处理登录逻辑的异常,MVC 层的业务异常由上述的业务处理方法

http://www.dtcms.com/a/400443.html

相关文章:

  • 代码共享网站wordpress mnews
  • Win11在哪设置禁用网络连接?Win11禁用网络连接步骤介绍
  • 旅游网站建设的规模设想建筑公司会计账务处理
  • 十一、Linux中RCU的实现以及内核抢占控制函数
  • 政务网站建设目的_意义做营销怎样才能吸引客户
  • 安徽宿州住房与建设网站济南市建设工程招标投标协会网站
  • 做外贸网络推广网站河南seo推广平台
  • 怀化网站优化哪个好php做网站示例
  • 用tornado做网站qq空间网站是多少
  • mvc电子商务网站开发微信app
  • 网站设计师主要做什么wordpress积分查看
  • 厘清大小端:分清数值轴、地址轴与书写轴
  • 保定做网站电话北京市住房建设投资中心网站
  • 如何优化唯品会库存API性能?
  • 四川住房和城乡建设部官方网站WordPress文章不置顶
  • 建设图片展示网站网站服务器指的是什么
  • 卖东西的网站怎么建设app开发需要哪些软件
  • 分析可口可乐网站建设的目的男女做爰视频网站在线
  • 网站模板平台资源电子商务网站功能模块
  • 外贸网站响应式伊春住房和城乡建设局网站
  • 网站制作费用需要多少钱网站主机服务器
  • 河南工会考试真题分享
  • 有需要做网站的吗网站建设设置分享功能
  • 社交网站开发项目计划报告最好最值得做的调查网站
  • 处理视频抽帧并转换成json
  • 东大桥做网站的公司中国建设银行 网站登录
  • 成都科技网站建设联免费书画网站怎么做的
  • [SQL] 给定起止日期,计算出日期行
  • 育苗盘补苗路径规划研究_2.5
  • 建设网站的网站叫什么男网站案例展示分类