Как контролировать ошибки инициализации контекста Spring
Допустим, у нас есть Spring bean:
@Component
class PluginsProviderImpl implements PluginsProvider {
private final List<PluginInterface> plugins;
public PluginsProviderImpl(List<PluginInterface> plugins){
this.plugins = plugins;
}
//...
}
Реализации PluginInterface
иметь зависимость во время выполнения от основной системы и предоставляются извне. Возможно, что иногда некоторые из них ошибочны (например, их зависимости отсутствуют). Если при инициализации контекста Spring возникает такая ошибка - все приложение не запускается (даже если сломанный плагин не требуется для его правильной работы).
Можно ли контролировать загрузку контекста Spring таким образом, чтобы при возникновении ошибки в одном из PluginInterface
реализации, пропустить это и продолжить инициализацию?
ОБНОВЛЕНИЕ: Больше объяснения: мне не нужно добавлять бин условно. Я хочу пропустить ошибочный компонент, и проблема возникает во время инициализации контекста. Это плагин - предоставляется во время выполнения.
Еще одно объяснение: я хочу, чтобы приложение запускалось, даже если одна из представленных плагином реализаций PluginInterface не может быть инициализирована.
0 ответов
Я наконец нашел решение. Это не идеально, но работает в большинстве случаев.
Сначала я понял, что в моем случае ошибочный плагин означает плагин, у которого есть проблема со связыванием, например, кто-то предоставляет плагин без зависимостей времени выполнения или существует проблема с версией плагина в зависимости от версии приложения.
Во-вторых, я обнаружил ловушку в инициализации контекста Spring (точнее, фабрике бинов), которая позволяет внедрять код, когда: 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-компонентов. В общем, это может быть небезопасная операция (в конце концов, удаленный бин может понадобиться другому бину), но я использую его только для определения экземпляров плагинов, которые по умолчанию независимы и самодостаточны. Хорошо, достаточно поговорить о коде, давайте посмотрим код...;)
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
Подклассы LinkageError указывают, что класс имеет некоторую зависимость от другого класса; однако последний класс несовместимо изменился после компиляции первого класса
,
Как я выяснил, это также указывает на отсутствие зависимости. В начале я написал, что это решение не идеально. Код проверяет, имеет ли плагин беспараметрический конструктор для его создания. Если у плагина его нет - проверить невозможно. Поэтому мне нужно объявить дополнительные требования к плагинам - они должны иметь конструктор без параметров:).