如何在Java中替换处理Throwable派生类型的if-else代码块?
优化Spring GlobalErrorAttributes的异常处理逻辑
问题背景
在Spring开发中,现有GlobalErrorAttributes类通过大量if-else分支判断异常类型,来填充错误属性并返回响应。随着业务扩展,异常类型逐渐增多,代码会愈发臃肿,且每次新增异常都需要修改核心类,违反开闭原则。需要实现无需修改原有核心代码即可扩展异常处理能力,同时利用Spring的自动装配机制,新增异常处理逻辑时自动生效。
现有核心代码
原GlobalErrorAttributes实现如下:
@Component @Slf4j public class GlobalErrorAttributes extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) { Map<String, Object> map = new LinkedHashMap<>(); Throwable error = super.getError(request); HttpStatus errorStatus; String messageDetail = null; if (error instanceof UserAlreadyExistsException) { Throwable cause = error.getCause(); if (cause != null) { messageDetail = error.getCause().getMessage(); } errorStatus = HttpStatus.NOT_ACCEPTABLE; log.error("error - {}, status - {}, detailMessage - {}", error.getMessage(), errorStatus, messageDetail); } if (error instanceof UserNotFoundException || error instanceof ResponseStatusException || error instanceof PasswordInHashNotFoundException) { Throwable cause = error.getCause(); if (cause != null) { messageDetail = error.getCause().getMessage(); } errorStatus = HttpStatus.NOT_FOUND; log.error("error - {}, status - {}, detailMessage - {}", error.getMessage(), errorStatus, messageDetail); } else if (error instanceof BadSqlGrammarException) { Throwable cause = error.getCause(); if (cause != null) { messageDetail = error.getCause().getMessage(); } errorStatus = HttpStatus.NOT_ACCEPTABLE; log.error("BadSqlGrammar error: error - {}, status - {}, detailMessage - {}", error.getMessage(), errorStatus, messageDetail); } else { Throwable cause = error.getCause(); if (cause != null) { messageDetail = error.getCause().getMessage(); } errorStatus = HttpStatus.INTERNAL_SERVER_ERROR; log.error("Not defined error: error - {}, status - {}, detailMessage - {}", error.getMessage(), errorStatus, messageDetail); } map.put("status", errorStatus.value()); map.put("message", error.getMessage()); map.put("detailMessage", messageDetail); return map; } }
尝试过的方案及问题
- 用Throwable实例作为Map键:无法通过
instanceof匹配,因为Map中存储的是预先创建的异常实例,和实际抛出的异常不是同一个对象,导致匹配失败。 - 用类名作为Map键:虽然能匹配具体异常类,但无法处理继承关系(子类异常无法匹配父类的处理逻辑),且硬编码类名容易出错,扩展性差。
可行解决方案
采用策略模式+Spring自动装配的方式,将每个异常的处理逻辑封装为独立策略类,由Spring自动扫描加载,核心类仅需遍历策略列表找到匹配项即可。
1. 定义异常处理策略接口
创建接口统一规范异常处理的核心行为:
public interface ExceptionHandlerStrategy { // 判断当前策略是否支持处理目标异常 boolean supports(Throwable throwable); // 返回对应HTTP状态码 HttpStatus getHttpStatus(); // 返回日志打印模板 String getLogTemplate(); // 自定义错误属性填充(默认实现处理通用逻辑) default void customizeErrorAttributes(Map<String, Object> attributes, Throwable throwable) { String detailMessage = throwable.getCause() != null ? throwable.getCause().getMessage() : null; attributes.put("detailMessage", detailMessage); } }
2. 实现具体异常处理策略
每个异常(或一组异常)对应一个策略类,标记@Component让Spring自动扫描装配:
用户已存在异常处理策略
@Component public class UserAlreadyExistsExceptionHandler implements ExceptionHandlerStrategy { @Override public boolean supports(Throwable throwable) { return throwable instanceof UserAlreadyExistsException; } @Override public HttpStatus getHttpStatus() { return HttpStatus.NOT_ACCEPTABLE; } @Override public String getLogTemplate() { return "error - {}, status - {}, detailMessage - {}"; } }
未找到类异常处理策略
@Component public class NotFoundExceptionHandler implements ExceptionHandlerStrategy { @Override public boolean supports(Throwable throwable) { return throwable instanceof UserNotFoundException || throwable instanceof ResponseStatusException || throwable instanceof PasswordInHashNotFoundException; } @Override public HttpStatus getHttpStatus() { return HttpStatus.NOT_FOUND; } @Override public String getLogTemplate() { return "error - {}, status - {}, detailMessage - {}"; } }
SQL语法错误异常处理策略
@Component public class BadSqlGrammarExceptionHandler implements ExceptionHandlerStrategy { @Override public boolean supports(Throwable throwable) { return throwable instanceof BadSqlGrammarException; } @Override public HttpStatus getHttpStatus() { return HttpStatus.NOT_ACCEPTABLE; } @Override public String getLogTemplate() { return "BadSqlGrammar error: error - {}, status - {}, detailMessage - {}"; } }
默认异常处理策略
@Component public class DefaultExceptionHandler implements ExceptionHandlerStrategy { @Override public boolean supports(Throwable throwable) { return true; // 匹配所有未被其他策略处理的异常 } @Override public HttpStatus getHttpStatus() { return HttpStatus.INTERNAL_SERVER_ERROR; } @Override public String getLogTemplate() { return "Not defined error: error - {}, status - {}, detailMessage - {}"; } }
3. 改造GlobalErrorAttributes核心类
通过构造函数注入所有策略实现,遍历找到匹配的策略后执行处理逻辑:
@Component @Slf4j public class GlobalErrorAttributes extends DefaultErrorAttributes { private final List<ExceptionHandlerStrategy> handlerStrategies; // Spring自动注入所有ExceptionHandlerStrategy实现类 public GlobalErrorAttributes(List<ExceptionHandlerStrategy> handlerStrategies) { this.handlerStrategies = handlerStrategies; } @Override public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) { Map<String, Object> map = new LinkedHashMap<>(); Throwable error = super.getError(request); // 查找匹配的处理策略 ExceptionHandlerStrategy strategy = handlerStrategies.stream() .filter(handler -> handler.supports(error)) .findFirst() .orElseThrow(() -> new IllegalStateException("No exception handler strategy found")); HttpStatus errorStatus = strategy.getHttpStatus(); String detailMessage = error.getCause() != null ? error.getCause().getMessage() : null; // 打印日志 log.error(strategy.getLogTemplate(), error.getMessage(), errorStatus, detailMessage); // 填充基础错误属性 map.put("status", errorStatus.value()); map.put("message", error.getMessage()); // 执行策略自定义属性逻辑 strategy.customizeErrorAttributes(map, error); return map; } }
扩展新异常的方式
新增异常类型时,仅需创建一个ExceptionHandlerStrategy实现类并标记@Component,Spring会自动扫描并将其加入策略列表,无需修改原有核心代码。
方案优势
- 符合开闭原则:新增异常处理无需修改核心代码,仅需添加策略类。
- Spring自动装配:利用Spring组件扫描能力,自动加载所有策略实现。
- 灵活性强:每个策略可自定义日志模板、状态码及错误属性填充逻辑。
- 可维护性高:每个异常的处理逻辑隔离在独立类中,代码结构清晰。
内容的提问来源于stack exchange,提问作者skyho
相关产品推荐
相关产品推荐

