Spring: Don't fail on bean initialization error - java

I know one of the purposes of Spring is to fail-fast if any bean initialization fails, but I'm curious if the following behavior is possible.
I have an application that depends on many external servers and DBs, and so there are a dozen beans that are interfaces for the different external services. Config values of the form 'db1.enabled=true' and 'db2.enabled=false' define whether these beans should even be initialized. What I want is a 'best effort' scenario where, if db1.enabled is set to true, but the db in question is not reachable for whatever reason and initialization of the bean that calls db1 fails, the whole system shouldn't crash. The effect should be as if db1.enabled was set to false, with ideally an error message somewhere indicating that this occurred. Doing this for all beans would defeat the purpose of Spring, but it would be useful for certain ones.

If you use Java Config you can run basically any code as bean initialization. You can, for example, put something like this in your config:
#Configuration
public class MyConfig{
#Bean
public DataSource mainDataSource(){
try{
// initialize your basic datasource here
} catch(Exception e) {
// try something else here
}
}
}

Related

Spring Initialisation Order with Configuration

I have a Spring project, and I need to configure Flyway.
Using the default FlywayAutoConfiguration, Flyway migration is immediatly executed before everything else (caching, PostConstruct annotations, services). This is the behavior I expected (in term or startup workflow)
Unfortunatly, I need to override the default FlywayAutoConfiguration because I use a custom Flyway implementation, but that's not the my main problem here, my issue is really related to Spring Priority for configuration and initialization sequence.
So do use my own flyway, I first copied FlywayAutoConfiguration to my maven module, name it CustomFlywayAutoConfiguration and just adapt imports. I also change the property "spring.flyway.enabled" to false and create another one "spring.flywaycustom.enabled" to be able to activate mine and not the default one.
Doing that fully change the sequence of startup. Now, flyway is executed at the end of the startup sequence (after caching and other #PostConstruct that are in my project)
The following bean defined in CustomFlywayAutoConfiguration is now created onyl at the end of the startup sequence. With the default FlywayAutoConfiguration , was well created on the beginning.
#Bean
#ConditionalOnMissingBean
public FlywayMigrationInitializer flywayInitializer(Flyway flyway,
ObjectProvider<FlywayMigrationStrategy> migrationStrategy) {
return new FlywayMigrationInitializer(flyway, migrationStrategy.getIfAvailable());
}
I tryied to play a lot with ordering (HIGHEST_PRECEDENCE, and LOWEST_PRECEDENCE)
#AutoConfigureOrder on configuration class
#Order on components
Try to Autowire FlywayMigrationInitializer to force the bean initialisation earlier
It's doesn't change anything, looks like Spring ignore #Order and #AutoConfigureOrder
Any idea why when Configuration is inside spring-boot-autoconfigure dependencies, it started as first priority, and when the same Configuration code is inside my project, I don't have the same ordering?
Thank you so much for your help.
Thanks to your answer, Focusin my attention FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor help me to solve the issue.
#Configuration(proxyBeanMethods = false)
#AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
#EnableConfigurationProperties({ DataSourceProperties.class, FlywayProperties.class })
#Import({ FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor.class }) // Very important to force flyway before
public class CustomFlywayConfiguration {
#Bean
public Flyway flyway(FlywayProperties properties, DataSourceProperties dataSourceProperties,
ResourceLoader resourceLoader, ObjectProvider<DataSource> dataSource,
#FlywayDataSource ObjectProvider<DataSource> flywayDataSource,
ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks) {
FluentConfiguration configuration = new FluentConfiguration(resourceLoader.getClassLoader());
DataSource dataSourceToMigrate = configureDataSource(configuration, properties, dataSourceProperties, flywayDataSource.getIfAvailable(), dataSource.getIfUnique());
checkLocationExists(dataSourceToMigrate, properties, resourceLoader);
configureProperties(configuration, properties);
List<Callback> orderedCallbacks = callbacks.orderedStream().collect(Collectors.toList());
configureCallbacks(configuration, orderedCallbacks);
fluentConfigurationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configuration));
configureFlywayCallbacks(configuration, orderedCallbacks);
List<JavaMigration> migrations = javaMigrations.stream().collect(Collectors.toList());
configureJavaMigrations(configuration, migrations);
return new CustomFlyway(configuration);
}
/**
* FlywayAutoConfiguration.FlywayConfiguration is conditioned to the missing flyway bean. #Import annotation are not executed in this case.
*
* So if we declare our own Flyway bean, we also need to create this bean to trigger the flyway migration.
*
* The main issue is now that bean is create after the #PostConstruct init method in MyInitComponent.
*
*/
#Bean
public FlywayMigrationInitializer flywayInitializer(Flyway flyway,
ObjectProvider<FlywayMigrationStrategy> migrationStrategy) {
return new FlywayMigrationInitializer(flyway, migrationStrategy.getIfAvailable());
}
/**
* Thanks to that, it's now working, because make sure it's required before to create EntityManager
*/
// #ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
// #ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
static class FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor
extends EntityManagerFactoryDependsOnPostProcessor {
FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor() {
super(FlywayMigrationInitializer.class);
}
}
...
So that confirm we can't easily override the Flyway bean without copy some logic from FlywayAutoConfiguration.
I created a small project to reproduce the bug, with the solution I found.
https://github.com/w3blogfr/flyway-issue-demo
I don't know if it's necessary to fix it or not in spring auto-configuration project?
I checked the history to find back the commit
https://github.com/spring-projects/spring-boot/commit/795303d6676c163af899e87364846d9763055cf8
And the ticket was this one.
https://github.com/spring-projects/spring-boot/issues/18382
The change looks technical and probably he missed that was an #ConditionalOnClass
None of the annotations that you've described have an impact on runtime semantics:
#AutoConfigureOrder works only on auto-configurations (so if you've copied the auto-configuration as a user config, it won't even be considered). This is used to order how auto-configurations are parsed (typical use case: make sure an auto-config contribute a bean definition of type X before another one check if a bean X is available).
#Order orders components of the same type. Doesn't have any effect as to "when" something occurs
Forcing initialisation using an injection point is a good idea but it'll only work if the component you're injecting it into is initialised itself at the right time.
The auto-configuration has a bunch of post-processors that create links between various components that use the DataSource and the FlywayMigrationInitializer. For instance, FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor makes sure that FlywayMigrationIntializer is a dependency of the entity manager so that the database is migrated before the EntityManager is made available to other components. That link is what's making sure Flyway is executed at the right time. I don't know why that's not working with your copy, a sample we can run ourselves shared on GitHub could help us figure out.
With all that said, please don't copy an auto-configuration in your own project. I'd advise to describe your use case in more details and open an issue so that we consider improving the auto-configuration.

Spring bean creation lifecycle : Why having multiple interaction points?

I'm learning spring and I'm stuck with the bean lifecycle !
When creating a bean, spring gives us several maners to interact with it. We can use one or all of :
BeanFactoryPostProcessor
BeanPostProcessor#postProcessBeforeInitialization
#PostConstruct
InitializingBean#afterPropertiesSet
#Bean#initMethod
BeanPostProcessor#postProcessAfterInitialization
Spring calls them in the order above
My question is : why all these points of interaction ? and when to use each of them (the use case) ?
A BeanFactoryPostProcessor and a BeanPostProcessor are quite different beast and also apply to different things.
A BeanFactoryPostProcessor will operate on the metadata for a bean (I like to call it the recipe) and will post process that. A well know example is the PropertySourcesPlaceholderConfigurer which will inject/replace all #Value in configuration with the value. A BeanFactoryPostProcessor operates on the metadata and thus before any bean has been created.
The BeanPostProcessor can be applied to a bean and can even replace a bean. Spring AOP uses this quite a lot. An example is the PersistenceExceptionTranslationPostProcessor, when a bean has been created it will pass through this BeanPostProcessor and when it is annotated with #Persistence the bean will be replaced by a proxy. This proxy adds exception translation (i.e. it will convert JpaException and SQLException to the Spring DataAccessException). This is done in a BeanPostProcessor. And can be be done before the init-callbacks are called (the postProcessBeforeInitializationor after they have been called thepostProcessAfterInitialization). The PersistenceExceptionTranslationPostProcessorwill run in thepostProcessAfterInitialization` as it needs to be certain the bean has been initialized.
The ServletContextAwareProcessor will run right after the object has been created to inject the ServletContext as early as possible as the initializing of a bean might depend on it.
The 3 callbacks for initializing a bean are more or less the same but are called in sequence because they have been included in later versions. It starter with only an interface InitializingBean and init-method in xml (later also added to #Bean and the annotation support was added when annotations became a thing.
You need init methods to initialize a bean, you might want to check if all properties have been set (like a required datasource) or you might want to start something. A DataSource (especially a connection pool) is a good example to initialize. After all dependencies have been injected you want to start the pool so it will open the connections. Now as you probably cannot modify the code you want to use the init-method if you control the code you probably want to add #PostConstruct. If you are writing an framework that depends on Spring I would use the InitializingBean.
Next to those 3 init methods you also have the destruction counter-parts. The DisposableBean interface (and destroy-method) and the #PreDestroy annotation. Again when you stop your application you also want to close the connections in your connection pool and you want to probably call the close method on the DataSource. A perfect sample of a destruction callback.

Registering Hystrix Concurrency Strategy fails after migrating to spring-boot-2.0

Trying to register hystrix concurrency strategy fails after migrating to Spring-boot-2.0 with spring actuator enabled with java.lang.IllegalStateException stating that "Another strategy was already registered" . This is although I have not used registerConcurrencyStrategy anywhere else in my code.
I'd like to register concurrency strategy to carry-forward Log MDC context so that I'm able to log both within and outside the Hystrix wrapped method equally well, which includes thread-locals. And this used to work perfectly in spring-boot-1.5
After having migrated to spring-boot 2.0 (from 1.5), the HystrixPlugins.getInstance().registerConcurrencyStrategy(this); fails with IllegalStateException
As per https://github.com/Netflix/Hystrix/issues/1057, this issue can come if either (a) Any other code flow would have registered its own or default ConcurrencyStrategy before this is invoked (b) any call would have come via Hystrix before this is invoked
Since the above invocation is within the constructor of a class which is annotated with #Component, this should get invoked ideally before any method call happens (except initialization of other beans, including their constructors).
We even tried moving this code inside the SpringBoot Application Class's main method before invoking the SpringApplication.run(MyApplication.class, args); but that also didn't work
#Component
public class ContextCopyHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
private static final String EVENT = "HystrixConcurrencyStrategy";
private static final String ACTION = "ContextCopy";
public ContextCopyHystrixConcurrencyStrategy(Logger logger, LoggerUtil defaultLoggerUtil) {
try {
HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
} catch (IllegalStateException e) {
defaultLoggerUtil.logEvents(logger, Level.WARN, e.getMessage(), EVENT, ACTION, "", "Race condition! Could not register strategy. HystrixConcurrencyStrategy is already initialized.");
}
Expected: My registering should have happened before any other code and registering should have been successful
Actual: My registering fails with IllegalStateException
How do I make sure that my registering happens well before any other registering (which is not present in my code, but may be inside some of the libraries that I may be transitively using)
By default, Spring boot 2 accuator registers Hystrix Metric Binder beans which reset already set HystrixConcurrencyStrategy and sets HystrixConcurrencyStrategyDefault.
So, disabling that bean by
management.metrics.binders.hystrix.enabled=false
would help not resetting your custom HystrixConcurrencyStrategy
We took a close look at my maven .m2 directory classes and looked for registerConcurrencyStrategy inside all the classes in all the jars. And we found that
io.micrometer.core.instrument.binder.hystrix
was internally registering the HystrixConcurrencyStrategy with the default one.
And upon further research we found that setting the following property in application.properties:
management.metrics.binders.hystrix.enabled=false disabled the Hystrix Metrics Binder (I'm actually not sure what it does though!) and then things worked
I was using spring-boot-starter-parent v2.5.3 with Spring Cloud version 2020.0.3.
I had to manually include version for spring-cloud-starter-netflix-hystrix. I was getting "Another strategy was already registered" exception when starting my microservice. I included the
management.metrics.binders.hystrix.enabled=false
in the application.properties file and this issue got resolved.

Can I ignore BeanCreationException and inject null instead?

We have a situation where our Spring wires up some beans that include ActiveMQ classes built with Java 6. Our application runs on customer's servers, so we can't guarantee that they have Java 6 or later installed. If they happen to have Java 5, the application can't start because of BeanCreationExceptions with the classes that depend on ActiveMQ (root cause being a UnsupportedClassVersionError).
So my question is, is there any way to ignore a BeanCreationException and still start the application? I want to be able to display an error message saying that they need to install Java 6 or later, but since the application can't even start, I never have a chance to do this.
My hunch is that there is no way to do this, because Spring needs to be able to guarantee that my application is built properly after initialization, but I thought I would ask anyway. Any other suggestions for how to accomplish my end goal would also be helpful and appreciated.
We are using Spring 3.0.6
Thanks!
If you can upgrade to Spring 3.1 (stable), take advantage of Java configuration:
#Bean
public SomeBean public someBean() {
if(isEverythingOkWithJavaVersion()) {
return new CorrectBean();
} else {
return null;
}
}
or:
#Bean
public SomeBean public someBean() {
try {
return new CorrectBean();
} catch(UnsupportedClassVersionError e) {
log.warn("", e);
return null;
}
}
In older versions of Spring FactoryBean might be used to implement the same logic. Instead of returning null you might also return some fake implementation that you can discover later and warn the user when the application tries to use it.
First off, any throwables that are a subclass of java.lang.Error are generally considered to be non-recoverable. So while it's possible to catch them, it's strongly discouraged:
An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions.
However, if all you're going to do is display an error message, then you should be able to get away with it.
SO to get back to your question, I suggest creating an implementation of Spring's FactoryBean interface, which would try to load the ActiveMQ classes. If it works, then it can return the appropriate object from FactoryBean.getObject. If it fails (via a caught UnsupportedClassVersionError), then it can return either a null, or some other object representing that condition.
You could create a factory bean and let it create the actual ActiveMQ bean. If it can't be initialized the factory could return a dummy/mock implementation so things don't break. Later you could ask the factory if everything went alright.
http://static.springsource.org/spring/docs/2.5.x/reference/beans.html#beans-factory-extension-factorybean

Tracking down cause of Spring's "not eligible for auto-proxying"

When you start messing around with Spring's auto-proxy stuff, you often run into this behaviour as documented:
Classes that implement the
BeanPostProcessor interface are
special, and so they are treated
differently by the container. All
BeanPostProcessors and their directly
referenced beans will be instantiated
on startup, as part of the special
startup phase of the
ApplicationContext, then all those
BeanPostProcessors will be registered
in a sorted fashion - and applied to
all further beans. Since AOP
auto-proxying is implemented as a
BeanPostProcessor itself, no
BeanPostProcessors or directly
referenced beans are eligible for
auto-proxying (and thus will not have
aspects 'woven' into them.
For any such bean, you should see an
info log message: “Bean 'foo' is not
eligible for getting processed by all
BeanPostProcessors (for example: not
eligible for auto-proxying)”.
In other words, if I write my own BeanPostProcessor, and that class directly references other beans in the context, then those referenced beans will not be eligible for auto-proxying, and a message is logged to that effect.
My problem is that tracking down where that direct reference is can be very difficult, since the "direct reference" can in fact be a chain of transitive dependencies that ends up taking in half the beans in the application context. All Spring gives you is that single info message, and it's not really much help, beyond telling you when a bean has been caught in this web of references.
The BeanPostProcessor I'm developing does have direct references to other beans, but it's a very limited set of references. Despite this, pretty much every bean in my context is then being excluded from being auto-proxied, according to the log messages, but I can't see where that dependency is happening.
Has anyone found a better way of tracking this down?
Follow this recipe:
Open BeanPostProcessorChecker in your IDE (it's an inner class of AbstractApplicationContext)
Set a breakpoint on if (logger.isInfoEnabled()) { in the method postProcessAfterInitialization
Run your code
When you hit the breakpoint, look for calls to getBean(String,Class<T>) in your stack trace.
One of these calls will try to create a BeanPostProcessor. That bean should be the culprit.
Background
Imagine this situation:
public class FooPP implements BeanPostProcessor {
#Autowire
private Config config;
}
When Spring has to create config (since it's a dependency of FooPP), it has a problem: The contract says that all BeanPostProcessor must be applied to every bean that is being created. But when Spring needs config, there is at least one PP (namely FooPP) which isn't ready for service!
This gets worse when you use an #Configuration class to define this bean:
#Configuration
public class BadSpringConfig {
#Lazy #Bean public Config config() { return new Config(); }
#Lazy #Bean public FooPP fooPP() { return new FooPP(); }
}
Every configuration class is a bean. That means to build a bean factory from BadSpringConfig, Spring needs to apply the post-processor fooPP but in order to do that, it first needs the bean factory ...
In this example, it's possible to break one of the cyclic dependencies. You can make FooPP implement BeanFactoryAware to get Spring inject the BeanFactory into the post processor. That way, you don't need autowiring.
Later in the code, you can lazily ask for the bean:
private LazyInit<Config> helper = new LazyInit<Config>() {
#Override
protected InjectionHelper computeValue() {
return beanFactory.getBean( Config.class );
}
};
#Override
public Object postProcessBeforeInitialization( Object bean, String beanName ) throws BeansException {
String value = helper.get().getConfig(...);
}
(source for LazyInit)
To break the cycle between the bean factory and the post processor, you need to configure the post processor in an XML config file. Spring can read that and build all the structures without getting confused.
Just to bring some closure to this question, the collapse of the uninitialized object graph was caused by the BeanPostProcessor using #Autowired to get its dependencies, and the autowire mechanism effectively caused every other bean definition to be initialized before my BeanPostProcessor got a chance to have a say in the matter. The solution is not to use autowiring for your BPPs.
Not sure if it's of any help, but the Eclipse Spring IDE's
graph view looks like it could be helpful in sorting out bean references..

Categories

Resources