随着业务逻辑变得越来越复杂,我们在编写代码时会遇到各种异常情况,这时就需要使用try-catch语句来捕获异常并进行处理。但是,大量的try-catch语句会让代码变得臃肿,不易维护,因此,我们需要一种优雅的方式来统一处理异常,减少代码中的try-catch语句。
比较下面两张图,看看您现在编写的代码属于哪一种风格?然后哪种编码风格您更喜欢?
丑陋的 try catch 代码块:
优雅的Controller:
那么问题来了,我们改如何实现第二种异常处理方式,如何优雅的处理各种异常?
在讲解如何减少try-catch语句之前,我们先来了解一下异常处理的基本原则。
首先,异常应该被及时捕获并进行处理。如果异常未被捕获,程序将崩溃并抛出未处理的异常,影响系统的稳定性。因此,我们需要在代码中合理地使用try-catch语句来捕获异常,并在catch块中进行处理。
异常应该被分类处理。不同类型的异常需要采取不同的处理方式,比如,对于业务逻辑异常,我们需要将异常信息返回给客户端,而对于系统异常,我们需要记录日志并通知管理员。
最后,异常处理应该是统一的。在一个应用程序中,我们可能会遇到很多不同的异常类型,如果每个异常都需要单独处理,会使代码变得很冗长。因此,我们需要将异常处理的逻辑抽象出来,实现统一的异常处理。
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注解用来定义环绕通知,表示在目标方法执行前后都要执行该通知。在该通知中,我们可以处理抛出的异常,然后返回处理结果。
通过上面的示例,我们实现了一个简单的统一异常处理。在实际开发中,我们可以根据需求进行一些优化,使代码更加简洁、优雅。异常处理是一个很重要的功能,需要我们在开发过程中认真对待,避免出现漏洞,保证系统的稳定性和安全性。