写点什么

抛弃丑陋的 try-catch,优雅处理异常

作者:Java你猿哥
  • 2023-04-20
    湖南
  • 本文字数:3169 字

    阅读完需:约 10 分钟

抛弃丑陋的try-catch,优雅处理异常

随着业务逻辑变得越来越复杂,我们在编写代码时会遇到各种异常情况,这时就需要使用 try-catch 语句来捕获异常并进行处理。但是,大量的 try-catch 语句会让代码变得臃肿,不易维护,因此,我们需要一种优雅的方式来统一处理异常,减少代码中的 try-catch 语句。

比较下面两张图,看看您现在编写的代码属于哪一种风格?然后哪种编码风格您更喜欢?

丑陋的 try catch 代码块:

丑陋的 try catch 代码块

优雅的 Controller:

优雅的Controller

那么问题来了,我们改如何实现第二种异常处理方式,如何优雅的处理各种异常?

异常处理的基本原则

在讲解如何减少 try-catch 语句之前,我们先来了解一下异常处理的基本原则。


首先,异常应该被及时捕获并进行处理。如果异常未被捕获,程序将崩溃并抛出未处理的异常,影响系统的稳定性。因此,我们需要在代码中合理地使用 try-catch 语句来捕获异常,并在 catch 块中进行处理。


异常应该被分类处理。不同类型的异常需要采取不同的处理方式,比如,对于业务逻辑异常,我们需要将异常信息返回给客户端,而对于系统异常,我们需要记录日志并通知管理员。


最后,异常处理应该是统一的。在一个应用程序中,我们可能会遇到很多不同的异常类型,如果每个异常都需要单独处理,会使代码变得很冗长。因此,我们需要将异常处理的逻辑抽象出来,实现统一的异常处理。

Spring Boot 中的异常处理

Spring Boot 提供了很多种方式来处理异常,比如使用 @ControllerAdvice 注解来定义一个全局的异常处理类,使用 @ExceptionHandler 注解来处理特定的异常类型等。下面,我们将介绍如何使用 @ControllerAdvice 注解来实现统一的异常处理。


定义异常处理类


我们可以使用 @ControllerAdvice 注解来定义一个全局的异常处理类,该类中的方法会被自动调用来处理异常。下面是一个示例:

@ControllerAdvicepublic class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class) @ResponseBody public ResponseData handleBusinessException(BusinessException e) { // 处理业务逻辑异常 return new ResponseData(e.getCode(), e.getMessage(), null); }
@ExceptionHandler(Exception.class) @ResponseBody public ResponseData handleException(Exception e) { // 处理系统异常 return new ResponseData(500, "系统异常", null); }}
复制代码

在上面的代码中,我们定义了一个全局的异常处理类 GlobalExceptionHandler,该类中定义了两个方法:handleBusinessException 和 handleException。其中,handleBusinessException 方法用来处理业务逻辑异常,handleException 方法用来处理系统异常。@ExceptionHandler 注解指定了处理的异常类型,@ResponseBody 注解将返回结果序列化成 JSON 格式,方便客户端进行处理。


自定义异常类


在上面的代码中,我们使用了 BusinessException 和 Exception 两个异常类来区分业务逻辑异常和系统异常。因此,我们需要定义这两个异常类。下面是一个示例:

public class BusinessException extends RuntimeException {
private int code;
public BusinessException(int code, String message) { super(message); this.code = code; }
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }}
复制代码


在上面的代码中,我们定义了一个 BusinessException 类,继承自 RuntimeException。该类中包含了一个 code 字段,用来标识业务逻辑异常的类型。


抛出异常


在我们的业务逻辑中,如果遇到了异常情况,我们需要抛出对应的异常。下面是一个示例:

javaCopy codepublic User getUserById(Long id) {    User user = userRepository.findById(id).orElse(null);    if (user == null) {        throw new BusinessException(1001, "用户不存在");    }    return user;}
复制代码

在上面的代码中,如果根据 id 未找到对应的用户,我们就会抛出 BusinessException 异常,该异常包含了错误码 1001 和错误信息"用户不存在"。

优雅的异常处理方式

通过上面的示例,我们已经实现了一个基本的统一异常处理,但是在实际开发中,我们还可以优化异常处理的方式,使代码更加优雅。


使用枚举类定义错误码


在上面的示例中,我们在 BusinessException 中定义了错误码,但是错误码的值可能会有重复,而且不太容易管理。因此,我们可以使用枚举类来定义错误码。下面是一个示例:


public enum ErrorCode {
USER_NOT_FOUND(1001, "用户不存在"), PASSWORD_ERROR(1002, "密码错误"), ;
private int code; private String message;
ErrorCode(int code, String message) { this.code = code; this.message = message; }
public int getCode() { return code; }
public String getMessage() { return message; }}
复制代码


在上面的代码中,我们定义了一个 ErrorCode 枚举类,包含了多个错误码及其对应的错误信息。

使用自定义注解来简化代码

在上面的示例中,我们需要在每个抛出异常的方法中手动创建 BusinessException 对象,并指定错误码和错误信息。这样做的代码量比较大,而且不太优雅。因此,我们可以使用自定义注解来简化代码。下面是一个示例:

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface CheckUser {
long id() default 0;
}
复制代码

在上面的代码中,我们定义了一个 CheckUser 注解,用来标注需要检查用户的方法。该注解包含了一个 id 属性,用来指定用户的 id。

下面是一个使用该注解的示例:

@GetMapping("/{id}")@CheckUser(id = 1)public User getUserById(@PathVariable("id") Long id) {    User user = userService.getUserById(id);    return user;}
复制代码

在上面的代码中,我们使用了 @CheckUser 注解,指定了 id 为 1,表示需要检查 id 为 1 的用户是否存在。在 CheckUserAspect 切面中,我们会根据该注解的值来进行业务逻辑的处理。

使用 AOP 实现统一异常处理

在上面的示例中,我们使用了 @ExceptionHandler 注解来实现异常的处理。但是,如果我们有很多的 Controller 方法,每个方法都需要加上该注解,这样就会使代码量变得很大,而且不太优雅。因此,我们可以使用 AOP 来实现统一异常处理。下面是一个示例:

javaCopy code@Aspect@Componentpublic class ExceptionAspect {
private static final Logger logger = LoggerFactory.getLogger(ExceptionAspect.class);
@Pointcut("execution(public * com.example.springbootdemo.controller..*.*(..))") public void pointcut() { }
@Around("pointcut()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object result; try { result = pjp.proceed(); } catch (BusinessException e) { result = new Response<>(e.getCode(), e.getMessage()); } catch (Exception e) { logger.error("系统异常", e); result = new Response<>(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage()); } return result; }
}
复制代码

在上面的代码中,我们定义了一个 ExceptionAspect 切面,用来处理所有 Controller 方法抛出的异常。@Pointcut 注解用来定义切入点,表示所有 public 方法。@Around 注解用来定义环绕通知,表示在目标方法执行前后都要执行该通知。在该通知中,我们可以处理抛出的异常,然后返回处理结果。

总结

通过上面的示例,我们实现了一个简单的统一异常处理。在实际开发中,我们可以根据需求进行一些优化,使代码更加简洁、优雅。异常处理是一个很重要的功能,需要我们在开发过程中认真对待,避免出现漏洞,保证系统的稳定性和安全性。


用户头像

Java你猿哥

关注

一只在编程路上渐行渐远的程序猿 2023-03-09 加入

关注我,了解更多Java、架构、Spring等知识

评论

发布
暂无评论
抛弃丑陋的try-catch,优雅处理异常_Java_Java你猿哥_InfoQ写作社区