问题
Let say we have a Spring bean:
@Component
class PluginsProviderImpl implements PluginsProvider {
private final List<PluginInterface> plugins;
public PluginsProviderImpl(List<PluginInterface> plugins){
this.plugins = plugins;
}
//...
}
The implementations of PluginInterface
have runtime dependency with the core system and are provided externally. It is possible that sometimes, some of them are erroneous (e.g. their dependencies are missing). If there is a such an error while Spring context initialization - the whole application does not start (even thou the broken plugin is not required to its proper operation).
Is it possible to control Spring context loading in such a way that, if error occurs in one of the PluginInterface
implementations, skip it and proceed with initialization?
UPDATE: More explanation: I Do not need to add a bean conditionally. I want to skip a bean that is erroneous and the problem appears during context initialization. This is a plugin - provided at runtime.
Even more explanation: I want to application start even if one of the plugin introduced implementations of PluginInterface cannot be initialized.
回答1:
I finally found a solution. It is not perfect but it works in most of cases.
At first I realized that in my case erroneous plugin means the plugin that have linkage problem, e.g. someone provide the plugin without runtime dependencies or there is a problem with plugin version against the application version.
Secondly I found a hook in Spring context initialization (bean factory to be precise) that allows to inject code when: 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.
- regardless of Spring documentation info, it also allows for removing bean definitions from bean factory. In general it may not be safe operation (in the end, the removed bean my be needed by other bean) but I use it only for plugins instances definition, that are by default independent and self contained. Ok, enough talking about the code, let us see the code... ;)
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());
}
}
}
The key fragment is:
catch (LinkageError e) {
((BeanDefinitionRegistry) beanFactory).removeBeanDefinition(beanName);
}
We catch LinkageError (not an Exception!) because we search for broken implemenations and as Java documentation says
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
.
As I found out, it also indicates the lack of the dependency. A the begining I wrote that this solution is not perfect. The code checks if the plugin has parameteless constructor to instantiatiate it. If plugin do not have one - cannot be checked. So I need to ad additional requirement for plugins - they must have parameterless constructor :).
来源:https://stackoverflow.com/questions/53961633/how-to-control-spring-context-initialization-errors