Spring Boot程序内重启后出现NoClassDefFoundError问题求解
问题描述
正常启动Spring Boot应用时,如下定义的ComboBoxHelper类可正常运行:
public class ComboBoxHelper { private static final Logger logger = LoggerFactory.getLogger(ComboBoxHelper.class); private static final I18NProvider i18nProvider = Application.getAppContext().getBean(I18NProvider.class); public ComboBoxHelper() { // To do something } public ItemLabelGenerator<DocumentTypes> startItemLabelGenerator() { return new ItemLabelGenerator<DocumentTypes>() { private static final long serialVersionUID = -338940206315118937L; @Override public String apply(DocumentTypes item) { logger.debug("====> ItemLabelGenerator - {} <====", item); String itemValue = ""; if (item.getTypeName().contains("common.string")) { itemValue = i18nProvider.getTranslation(item.getTypeName(), UI.getCurrent().getLocale()); } else { itemValue = item.getTypeName(); } return itemValue; } }; }
但通过按钮调用如下restart方法重启应用后,初始化ComboBoxHelper类时会抛出java.lang.NoClassDefFoundError: Could not initialize class错误:
public static void restart() { ApplicationArguments args = context.getBean(ApplicationArguments.class); Thread thread = new Thread(() -> { context.close(); context = SpringApplication.run(Application.class, args.getSourceArgs()); }); thread.setDaemon(false); thread.start(); }
原因分析
- 静态初始化绑定失效上下文:
ComboBoxHelper的i18nProvider是静态常量,类第一次加载时就会从Spring上下文获取Bean并完成初始化。重启时旧上下文被关闭,若ComboBoxHelper已被旧类加载器加载,其静态成员会绑定到已失效的旧上下文。后续代码再尝试使用该类时,类初始化阶段的未捕获异常会被JVM标记为不可初始化,最终触发NoClassDefFoundError。 - 类加载器隔离问题:自定义重启逻辑中,新应用可能复用旧类加载器或旧类实例未被回收。旧类的静态成员关联旧上下文,新上下文无法正常使用这些成员,导致类初始化失败。
解决方案
方案1:改用实例注入(推荐)
将i18nProvider改为实例成员,通过Spring构造注入获取,让Spring管理ComboBoxHelper的生命周期,确保每次重启后都能拿到新上下文的Bean:
@Component public class ComboBoxHelper { private static final Logger logger = LoggerFactory.getLogger(ComboBoxHelper.class); private final I18NProvider i18nProvider; @Autowired public ComboBoxHelper(I18NProvider i18nProvider) { this.i18nProvider = i18nProvider; // 原构造逻辑 } public ItemLabelGenerator<DocumentTypes> startItemLabelGenerator() { return new ItemLabelGenerator<DocumentTypes>() { private static final long serialVersionUID = -338940206315118937L; @Override public String apply(DocumentTypes item) { logger.debug("====> ItemLabelGenerator - {} <====", item); String itemValue = ""; if (item.getTypeName().contains("common.string")) { itemValue = i18nProvider.getTranslation(item.getTypeName(), UI.getCurrent().getLocale()); } else { itemValue = item.getTypeName(); } return itemValue; } }; }
方案2:延迟获取Bean
若必须保留静态结构,不要在类初始化时获取Bean,而是在每次使用时从当前上下文获取:
public class ComboBoxHelper { private static final Logger logger = LoggerFactory.getLogger(ComboBoxHelper.class); public ComboBoxHelper() { // To do something } public ItemLabelGenerator<DocumentTypes> startItemLabelGenerator() { return new ItemLabelGenerator<DocumentTypes>() { private static final long serialVersionUID = -338940206315118937L; @Override public String apply(DocumentTypes item) { logger.debug("====> ItemLabelGenerator - {} <====", item); String itemValue = ""; // 每次调用时获取当前有效的I18NProvider I18NProvider i18nProvider = Application.getAppContext().getBean(I18NProvider.class); if (item.getTypeName().contains("common.string")) { itemValue = i18nProvider.getTranslation(item.getTypeName(), UI.getCurrent().getLocale()); } else { itemValue = item.getTypeName(); } return itemValue; } }; }
方案3:使用官方重启机制
自定义重启逻辑容易引发类加载器泄漏问题,建议改用Spring Boot DevTools的重启功能,它通过类加载器隔离实现可靠的应用重启,避免手动重启的各种隐患。
内容的提问来源于stack exchange,提问作者Rolf Warnecke
相关产品推荐
相关产品推荐

