You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot自定义异常读取YML属性初始化问题求助

解决Spring Boot自定义异常从YML读取配置时父类构造器获取值为null的问题

说明:部分代码为西班牙语。

我为Spring Boot应用构建了一套自定义异常模型,抛出的每个自定义异常都会生成一个名为ErrorModel的个性化响应对象,可通过@RestControllerAdvice配合@ExceptionHandler捕获异常并返回对应的errorModel。每个异常生成的ErrorModel仅在code(对应codigo)和description(对应descripcion)字段上存在差异。

现有代码结构

全局异常处理器

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
  @ExceptionHandler(ClienteSinMorasException.class)
  @ResponseStatus(HttpStatus.NOT_FOUND)
  @ResponseBody
  public ErrorModel manejoDeClienteSinMoras(ClienteSinMorasException ex) {
      return ex.getErrorModel();
  }
}

ErrorModel定义

@Getter
@AllArgsConstructor
public class ErrorModel {
    private final String codigo;
    private final String descripcion;
    private final ArrayList<HashMap<String, String>> errores; //无需关注此字段
}

最初的异常实现(可正常运行)

抽象异常类:

public abstract class IErrorException extends RuntimeException {
    @Getter
    private final ErrorModel errorModel;

    public abstract String getCodigo();

    public abstract String getDescripcion();

    public IErrorException() {
        super();
        this.errorModel = new ErrorModel(this.getCodigo(), this.getDescripcion(), null);
    }

    public IErrorException(ArrayList<HashMap<String, String>> errores) {
        super();
        this.errorModel = new ErrorModel(this.getCodigo(), this.getDescripcion(), errores);
    }

    public IErrorException(String errorKey, String errorValue) {
        super();
        ArrayList<HashMap<String, String>> errores = new ArrayList<>();
        errores.add(new HashMap<>(Map.of(errorKey, errorValue)));
        this.errorModel = new ErrorModel(this.getCodigo(), this.getDescripcion(), errores);
    }

}

子类异常:

// 其中一个异常类
@Getter
public class ClienteSinMorasException extends IErrorException {

    private final String codigo = "code";
    private final String descripcion = "desc";

    public ClienteSinMorasException() {
        super();
    }

    public ClienteSinMorasException(ArrayList<HashMap<String, String>> errores) {
        super(errores);
    }

    public ClienteSinMorasException(String errorKey, String errorValue) {
        super(errorKey, errorValue);
    }

}

问题场景与原因

需求变更要求从.yml配置文件读取code和description的值,支持运行时修改无需重启应用。修改后子类异常代码如下,但原有逻辑失效:

// 修改后的异常类
@Getter
public class ClienteSinMorasException extends IErrorException {

    private final String codigo = Yml.get("cod");
    private final String descripcion = Yml.get("desc");

    public ClienteSinMorasException() {
        super();
    }

    public ClienteSinMorasException(ArrayList<HashMap<String, String>> errores) {
        super(errores);
    }

    public ClienteSinMorasException(String errorKey, String errorValue) {
        super(errorKey, errorValue);
    }

}

问题核心:原逻辑中,子类final常量字段在父类构造器调用getCodigo()时已完成初始化;但改为Yml.get()读取后,子类字段要等父类构造器执行完毕才会初始化,导致ErrorModel中的codigo和descripcion为null。同时Java规定super()必须是构造器第一语句,无法提前初始化子类变量。

解决方案

方案1:构造器传参规避初始化顺序问题

修改抽象类构造器,要求子类直接传入从YML读取的codigo和descripcion值,跳过子类字段初始化的依赖。

修改后的抽象异常类

public abstract class IErrorException extends RuntimeException {
    @Getter
    private final ErrorModel errorModel;

    public IErrorException(String codigo, String descripcion) {
        super();
        this.errorModel = new ErrorModel(codigo, descripcion, null);
    }

    public IErrorException(String codigo, String descripcion, ArrayList<HashMap<String, String>> errores) {
        super();
        this.errorModel = new ErrorModel(codigo, descripcion, errores);
    }

    public IErrorException(String codigo, String descripcion, String errorKey, String errorValue) {
        super();
        ArrayList<HashMap<String, String>> errores = new ArrayList<>();
        errores.add(new HashMap<>(Map.of(errorKey, errorValue)));
        this.errorModel = new ErrorModel(codigo, descripcion, errores);
    }

}

修改后的子类异常

@Getter
public class ClienteSinMorasException extends IErrorException {

    private final String codigo = Yml.get("cod");
    private final String descripcion = Yml.get("desc");

    public ClienteSinMorasException() {
        super(Yml.get("cod"), Yml.get("desc"));
    }

    public ClienteSinMorasException(ArrayList<HashMap<String, String>> errores) {
        super(Yml.get("cod"), Yml.get("desc"), errores);
    }

    public ClienteSinMorasException(String errorKey, String errorValue) {
        super(Yml.get("cod"), Yml.get("desc"), errorKey, errorValue);
    }

}

优点:逻辑简单直接,彻底解决初始化顺序问题;缺点:每个子类构造器需重复写YML读取代码,配置键变更时需修改所有子类。


方案2:懒加载ErrorModel延迟初始化

将ErrorModel的创建延迟到第一次调用getErrorModel()时,此时子类字段已完成初始化。

修改后的抽象异常类(手动实现懒加载)

public abstract class IErrorException extends RuntimeException {
    private ErrorModel errorModel;
    private final ArrayList<HashMap<String, String>> initErrores;
    private final String initErrorKey;
    private final String initErrorValue;

    public abstract String getCodigo();

    public abstract String getDescripcion();

    public IErrorException() {
        super();
        this.initErrores = null;
        this.initErrorKey = null;
        this.initErrorValue = null;
    }

    public IErrorException(ArrayList<HashMap<String, String>> errores) {
        super();
        this.initErrores = errores;
        this.initErrorKey = null;
        this.initErrorValue = null;
    }

    public IErrorException(String errorKey, String errorValue) {
        super();
        this.initErrores = null;
        this.initErrorKey = errorKey;
        this.initErrorValue = errorValue;
    }

    public ErrorModel getErrorModel() {
        if (errorModel == null) {
            ArrayList<HashMap<String, String>> actualErrores = initErrores;
            if (initErrorKey != null && initErrorValue != null) {
                actualErrores = new ArrayList<>();
                actualErrores.add(new HashMap<>(Map.of(initErrorKey, initErrorValue)));
            }
            errorModel = new ErrorModel(getCodigo(), getDescripcion(), actualErrores);
        }
        return errorModel;
    }
}

子类异常保持修改后的代码不变

@Getter
public class ClienteSinMorasException extends IErrorException {

    private final String codigo = Yml.get("cod");
    private final String descripcion = Yml.get("desc");

    public ClienteSinMorasException() {
        super();
    }

    public ClienteSinMorasException(ArrayList<HashMap<String, String>> errores) {
        super(errores);
    }

    public ClienteSinMorasException(String errorKey, String errorValue) {
        super(errorKey, errorValue);
    }

}

优点:子类无需修改构造器逻辑,保持原有结构;缺点:需注意线程安全(异常对象通常为线程局部,风险极低)。


方案3:Spring依赖注入管理配置(推荐)

利用Spring的配置注入能力,将配置封装为Bean,避免手动读取YML的初始化顺序问题,还支持运行时刷新配置。

首先创建配置类:

@Component
@RefreshScope // 支持运行时刷新配置
public class ErrorConfig {
    @Value("${error.clienteSinMoras.cod}")
    private String clienteSinMorasCod;

    @Value("${error.clienteSinMoras.desc}")
    private String clienteSinMorasDesc;

    private static ErrorConfig instance;

    @PostConstruct
    public void init() {
        instance = this;
    }

    public static String getClienteSinMorasCod() {
        return instance.clienteSinMorasCod;
    }

    public static String getClienteSinMorasDesc() {
        return instance.clienteSinMorasDesc;
    }
}

然后修改子类异常,并结合方案2的懒加载逻辑:

@Getter
public class ClienteSinMorasException extends IErrorException {

    private final String codigo = ErrorConfig.getClienteSinMorasCod();
    private final String descripcion = ErrorConfig.getClienteSinMorasDesc();

    public ClienteSinMorasException() {
        super();
    }

    public ClienteSinMorasException(ArrayList<HashMap<String, String>> errores) {
        super(errores);
    }

    public ClienteSinMorasException(String errorKey, String errorValue) {
        super(errorKey, errorValue);
    }

}

优点:符合Spring最佳实践,配置管理规范,支持运行时刷新;缺点:需依赖Spring上下文,增加配置类代码。

内容的提问来源于stack exchange,提问作者Gonzalo Martinez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 01:41:02