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

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

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

丑陋的 try catch 代码块:

抛弃丑陋的try-catch,优雅处理异常_第1张图片

 

优雅的Controller:

抛弃丑陋的try-catch,优雅处理异常_第2张图片

 

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

异常处理的基本原则

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

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

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

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

Spring Boot中的异常处理

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

定义异常处理类

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

@ControllerAdvice
public 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
@Component
public 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,spring,spring,boot)