如何控制 Spring 上下文初始化错误
How to control Spring context initialization errors
假设我们有一个 Spring bean:
@Component
class PluginsProviderImpl implements PluginsProvider {
private final List<PluginInterface> plugins;
public PluginsProviderImpl(List<PluginInterface> plugins){
this.plugins = plugins;
}
//...
}
PluginInterface
的实现与核心系统有运行时依赖性,由外部提供。有时,其中一些可能是错误的(例如,缺少它们的依赖项)。如果在 Spring 上下文初始化时出现这样的错误 - 整个应用程序不会启动(即使损坏的插件不需要其正常运行)。
是否可以控制 Spring 上下文加载,如果 PluginInterface
实现之一发生错误,则跳过它并继续初始化?
更新:更多说明:我不需要有条件地添加 bean。我想跳过一个错误的 bean,问题出现在上下文初始化期间。这是一个插件 - 在运行时提供。
更多解释:
即使其中一个插件引入的 PluginInterface 实现无法初始化,我也想启动应用程序。
我终于找到了解决办法。它并不完美,但在大多数情况下都有效。
起初我意识到,在我的例子中,错误的插件意味着插件有 linkage 问题,例如有人提供了没有运行时依赖的插件,或者插件版本与应用程序版本有问题。
其次,我在 Spring 上下文初始化(准确地说是 bean 工厂)中找到了一个钩子,它允许在以下情况下注入代码:All bean definitions will have been loaded, but no beans will have been instantiated yet. This allows for overriding or adding properties even to eager-initializing beans.
- 无论 Spring 文档信息如何,它还允许 从 bean 工厂中删除 bean 定义。一般来说,它可能不是安全操作(最后,删除的 bean 可能被其他 bean 需要)但我只将它用于插件实例定义,默认情况下它们是独立的和自包含的。好了,代码说得够多了,让我们看看代码...;)
public class PluginQualifierProcessor implements BeanFactoryPostProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(PluginQualifierProcessor.class);
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
String[] beanNamesForType = beanFactory.getBeanNamesForType(PluginInterface.class);
List<String> beans = Arrays.asList(beanNamesForType)
.stream()
.collect(Collectors.toList());
for (String beanName : beans) {
BeanDefinition bean = beanFactory.getBeanDefinition(beanName);
if (!bean.hasConstructorArgumentValues()) {
String className = bean.getBeanClassName();
try {
tryToInstatiate(className);
// we are interested only in runtime linkage errors that can happen if plugin is erroneous
} catch (LinkageError e) {
LOGGER.error("plugin {} is erroneous. It will be discarded from context. {}", className, e);
((BeanDefinitionRegistry) beanFactory).removeBeanDefinition(beanName);
}
}
}
}
private void tryToInstatiate(String className) {
try {
Class<?> beanClass = Class.forName(className);
beanClass.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
LOGGER.debug("skip exception while creating instance of {}. {}", className, e.getMessage());
}
}
}
关键片段是:
catch (LinkageError e) {
((BeanDefinitionRegistry) beanFactory).removeBeanDefinition(beanName);
}
我们捕获了 LinkageError(不是异常!),因为我们搜索了损坏的实现,正如 Java 文档所说
Subclasses of LinkageError indicate that a class has some dependency on another class; however, the latter class has incompatibly changed after the compilation of the former class
.
正如我发现的那样,这也表明缺乏依赖性。
一开始我写道这个解决方案并不完美。代码检查插件是否有无参数构造函数来实例化它。如果插件没有 - 无法检查。所以我需要对插件添加额外的要求——它们必须有无参数的构造函数:)。
假设我们有一个 Spring bean:
@Component
class PluginsProviderImpl implements PluginsProvider {
private final List<PluginInterface> plugins;
public PluginsProviderImpl(List<PluginInterface> plugins){
this.plugins = plugins;
}
//...
}
PluginInterface
的实现与核心系统有运行时依赖性,由外部提供。有时,其中一些可能是错误的(例如,缺少它们的依赖项)。如果在 Spring 上下文初始化时出现这样的错误 - 整个应用程序不会启动(即使损坏的插件不需要其正常运行)。
是否可以控制 Spring 上下文加载,如果 PluginInterface
实现之一发生错误,则跳过它并继续初始化?
更新:更多说明:我不需要有条件地添加 bean。我想跳过一个错误的 bean,问题出现在上下文初始化期间。这是一个插件 - 在运行时提供。
更多解释: 即使其中一个插件引入的 PluginInterface 实现无法初始化,我也想启动应用程序。
我终于找到了解决办法。它并不完美,但在大多数情况下都有效。
起初我意识到,在我的例子中,错误的插件意味着插件有 linkage 问题,例如有人提供了没有运行时依赖的插件,或者插件版本与应用程序版本有问题。
其次,我在 Spring 上下文初始化(准确地说是 bean 工厂)中找到了一个钩子,它允许在以下情况下注入代码:All bean definitions will have been loaded, but no beans will have been instantiated yet. This allows for overriding or adding properties even to eager-initializing beans.
- 无论 Spring 文档信息如何,它还允许 从 bean 工厂中删除 bean 定义。一般来说,它可能不是安全操作(最后,删除的 bean 可能被其他 bean 需要)但我只将它用于插件实例定义,默认情况下它们是独立的和自包含的。好了,代码说得够多了,让我们看看代码...;)
public class PluginQualifierProcessor implements BeanFactoryPostProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(PluginQualifierProcessor.class);
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
String[] beanNamesForType = beanFactory.getBeanNamesForType(PluginInterface.class);
List<String> beans = Arrays.asList(beanNamesForType)
.stream()
.collect(Collectors.toList());
for (String beanName : beans) {
BeanDefinition bean = beanFactory.getBeanDefinition(beanName);
if (!bean.hasConstructorArgumentValues()) {
String className = bean.getBeanClassName();
try {
tryToInstatiate(className);
// we are interested only in runtime linkage errors that can happen if plugin is erroneous
} catch (LinkageError e) {
LOGGER.error("plugin {} is erroneous. It will be discarded from context. {}", className, e);
((BeanDefinitionRegistry) beanFactory).removeBeanDefinition(beanName);
}
}
}
}
private void tryToInstatiate(String className) {
try {
Class<?> beanClass = Class.forName(className);
beanClass.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
LOGGER.debug("skip exception while creating instance of {}. {}", className, e.getMessage());
}
}
}
关键片段是:
catch (LinkageError e) {
((BeanDefinitionRegistry) beanFactory).removeBeanDefinition(beanName);
}
我们捕获了 LinkageError(不是异常!),因为我们搜索了损坏的实现,正如 Java 文档所说
Subclasses of LinkageError indicate that a class has some dependency on another class; however, the latter class has incompatibly changed after the compilation of the former class
.
正如我发现的那样,这也表明缺乏依赖性。 一开始我写道这个解决方案并不完美。代码检查插件是否有无参数构造函数来实例化它。如果插件没有 - 无法检查。所以我需要对插件添加额外的要求——它们必须有无参数的构造函数:)。