i have issue with disabling specific DataSources in Spring Actuator. I currently have task in my application to implement Spring Actuator, but need Actuator to ignore/disable for some features in app(Health Indicator mainly). Application is built from other mini apps. Any suggestions or instructions how to start it ?
Disable the default datasources health indicator
management.health.db.enabled=false
and customise the your required data source with DataSourceHealthIndicator
example:
#Autowired
private DataSource requiredDataSource;
#Bean
public DataSourceHealthIndicator requiredDataSourceHealthIndicator() {
return new DataSourceHealthIndicator(requiredDataSource);
}
An important point to prevent the Health check system becomes fully none-functional due to infinite loop for waiting for database connection is to configure the connectionTimeout and validationTimeout in datasource config.
In the case that we using HikariCP as connection pool provider, with respect to the case in hand the implementation will look like below,
HikariConfig config = new HikariConfig();
...
config.setInitializationFailTimeout(1000);
config.setConnectionTimeout(1500);
config.setValidationTimeout(1500);
...
return new HikariDataSource(config).unwrap(DataSource.class)
#Bean
public DataSourceHealthIndicator dataSourceHealthIndicator(){
...
return new DataSourceHealthIndicator(dataSource, "SELECT 1");
}
#Component
#RequiredArgsConstructor
public class CustomHealth implements HealthIndicator {
#Override
public Health health() {
...
return Health.status(healthIndicator.health().getStatus()).build();
}
private final DataSourceHealthIndicator healthIndicator;
}
And as Bhushan Uniyal stated you can disable the actuator default DataSourceHealthIndicator in your property files
YML
management:
health:
db:
enabled: false
Properties
management.health.db.enabled: false
Related
Use Case
I want to implement Spring Security ACL in Spring Boot multi tenancy system.
Approach
In order to achieve this, I need to change datasource bean which is created on application bootstrap. I need to connect to datasource after the tenant user logs in.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
[...]
// data source
#Bean
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
Prerequisites:
ACL is working fine in single tenant environment.
Multi tenancy is working also.
Question:
How can I refresh datasource in MethodSecurityConfiguration after system startup?
Thanks for your help!
Use case
Our spring-boot-backend (v 2.3.1) using a postgres-database with HikariCP as connection pool.
The application is online and a admin accidentally kills the database.
Expected behavior
Spring notice that the connection to DB was lost. The application will gracefully shutdown.
Actual behavior
Spring is still running. All methods, which needs the db, ending up in exceptions.
To recover a complete good state, we need manually to restart spring.
Notes
We have a lot of async workers and they cannot recover correctly, when the database is going back online.
And docker/kubernetes will notice when the application shutdown and can automatically restart it.
Question
How can I reach the expected behavior?
Sadly I found nothing similar in the web.
With the hints from #Zubair I build a new small solution.
Iam using the Spring-Actuator-package, because they have some ready classes for this use-case.
All what I need was a HealthIndicator-Bean, like this
#Bean
public HealthIndicator dataSourceHealthIndicator(final DataSource dataSource) {
return new DataSourceHealthIndicator(dataSource, "SELECT 1;");
}
and scheduled watcher (because HikariCP nor the HealthIndicators has any events).
#Scheduled(fixedRate = 10000L)
public void checkDBHealth() {
final Status status = this.dataSourceHealthIndicator.health().getStatus();
if (!Status.UP.equals(status)) {
log.error("DATABASE IS OFFLINE! SHUTTING DOWN!");
System.exit(1);
}
}
I hope that will be useful for others.
Edit
I had to change the config of HikariCP. Otherwise the health-checker waiting almost endless for database-connection.
#Bean
public DataSource dataSource() {
final HikariConfig config = new HikariConfig();
// default settings with user, password, jdbc, ...
config.setInitializationFailTimeout(1000);
config.setConnectionTimeout(1000);
config.setValidationTimeout(1000);
return new HikariDataSource(config);
}
If its spring 2.0, you may call the shut-down actuator from some monitoring service.
You can disable the actuator default provided health indicators in your property files and replace your custom DatasourceHealthIndicator by register it as bean.
#Bean
public DataSourceHealthIndicator dataSourceHealthIndicator(){
return new DataSourceHealthIndicator(dataSource, "SELECT 1");
}
#Component
#RequiredArgsConstructor
public class CustomHealth implements HealthIndicator {
private final DataSourceHealthIndicator healthIndicator;
#Override
public Health health() {
// ...
return Health.status(healthIndicator.health().getStatus()).build();
}
}
And set your properties like this.
application.yaml
management:
health:
db:
enabled: false
application.properties
management.health.db.enabled: false
I have a spring boot app, which uses resilience4j AOP-based #CircuitBreakers.
Now I would like to make the circuit breakers' information available in the /actuator/health endpoint, but I'm not seeing the details.circuitBtreakers objects described in the docs in the JSON output.
What am I doing wrong?
By comparison, getting dynamic cache information to appear in the /actuator/metrics endpoint required a small amount of custom wiring, but this is well documented. I wonder if there is a similar trick that I can apply for dynamically defined #CircuitBreakers to be registerd with the /actuator/health endpoint.
MyService.java:
#Service
public class MyService {
#Autowired
private CacheManager cacheManager;
#Autowired
private CacheMetricsRegistrar cacheMetricsRegistrar;
#PostConstruct
public void postConstruct() {
// On-the-fly defined (annotation-based) caches are not auto-registered with micrometer metrics.
final Cache cache = cacheManager.getCache("myCache");
cacheMetricsRegistrar.bindCacheToRegistry(cache);
}
#CircuitBreaker(name = "myCB", fallbackMethod = "fallbackCallAnApi")
public String callAnApi() throws RestClientException {
// ...
}
#Cacheable("myCache")
public String getSomethingCacheable() {
// ...
}
}
application.properties:
resilience4j.circuitbreaker.configs.default.registerHealthIndicator=true
management.endpoints.web.expose=health,metrics
management.endpoints.web.exposure.include=health,metrics
management.endpoint.health.enabled=true
management.endpoint.metrics.enabled=true
management.metrics.enable.resilience4j.circuitbreaker.calls=true
management.health.circuitbreakers.enabled=true
Dynamically registering CircuitBreakers for the HealthIndicator endpoint doesn't work at the moment.
Unfortunately you have to configure them:
resilience4j.circuitbreaker:
configs:
default:
registerHealthIndicator: true
instances:
myCB:
baseConfig: default
You could say it's a bug.
https://github.com/resilience4j/resilience4j/blob/master/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/monitoring/health/CircuitBreakersHealthIndicator.java#L99-L102
I am facing an issue with my custom spring boot starter and a spring boot app consumer that uses as a dependency. I have in both an application.yml but it seems that the configuration I am looking for it is only pressent if it is defined in the consumer.
My config in the starter is like this:
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = "security")
public class StarterSecurityConfig {
private boolean jwtEnabled;
private String[] unsecuredPaths;
private String[] securedPaths;
}
And I have this bean defined in the AutoConfiguration class:
#Bean
public StarterSecurityConfig starterSecurityConfig() {
return new StarterSecurityConfig();
}
It is perfectly retrieved by the consumer which has this application.yml and another variables:
security:
jwt-enabled: true
secured-paths:
- /user/**
unsecured-paths:
- /**
But if I remove that from the consumer and I put it in the application.yml of the starter, the starter beans does not have these properties when creating them.
Maybe am I missing something?
If I understood properly your issue, I have faced such problem just last week ...
I was inspecting this issue and I have some findings (they are not supported by official documentation): if you add dependency and want to use its resources, you have a situation when both application.yml files have the same location - classpath:application.yml, and or they cannot be loaded together, or one of them is overridden by other. In any case, in my application, it did not work.
The straight and simple solution if you just need to load configuration from dependent config file - rename it and load in a possible way (manual loading from YAML, property source's initializer, etc.)
But if this config file should be used anywhere, we can load properties manually in the context. In a dependency (consumer in your case) create another configuration file, e.g. consumer-application.yml and next bean in #configuration class:
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
var propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
var yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
yamlPropertiesFactoryBean.setResources(new ClassPathResource("consumer-application.yaml"));
propertySourcesPlaceholderConfigurer.setProperties(yamlPropertiesFactoryBean.getObject());
return propertySourcesPlaceholderConfigurer;
}
And you can use properties from YAML-file in both applications with #Value.
But the simplest way - to use properties configs. In that case, you can just set #PropertySource("classpath:consumer-application.properties") in consumer and #PropertySource(value = {"classpath:application.properties", "classpath:consumer-application.properties"})
In my case both variants work correctly.
You can try initializing the member variables on the starter itself. If consumer wants to override the values they can do it with they're application configuration.
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = "security")
public class StarterSecurityConfig {
private boolean jwtEnabled = true;
private String[] unsecuredPaths = { "/user/**" };
private String[] securedPaths = { "/**" };
}
Fews more ideas:
I would make jwtEnabled as false and would remove the #Configuration and #ConfigurationProperties from the above Class and create an SecurityAutoConfiguration Class with other beans.
#Configuration
public class SecurityAutoConfiguration{
#Bean
#ConfigurationProperties(prefix = "security")
public StarterSecurityConfig starterSecurityConfig(){
return new StarterSecurityConfig();
}
#Bean
#ConditionalOnProperty(value="security.jwtEnabled", havingValue = "true")
public JwtService jwtService(StarterSecurityConfig starterSecurityConfig) {
return new JwtService(starterSecurityConfig);
}
}
the consumers will be able to enable or disable the security-starter with their application configuration using security.jwtEnabled flag.
I was reading through this Git issue:
https://github.com/spring-projects/spring-boot/issues/7589
with regards to Java Spring boot and am trying to figure out a way to bypass the crash upon startup.
The short version is that If you include the code for creating the mongo client:
#Bean
public MongoOperations mongoOperations() {
try {
//This runs an operation which uses my credentials to login to the db
return new MongoTemplate(mongoDbFactory());
} catch (Exception e){
e.printStackTrace();
return null;
}
}
and the MongoDB is running, it will connect and not have any problems, but if the MongoDB is not running, Spring will retry and after failing again, will crash and stop all startup sequences.
My question is this: is there a way to bypass this initial crash / check against the DB being up and running other than commenting out all code referencing it? Can I catch an exception somewhere low-level and let it pass through?
If your application behaves in such a way that MongoDB is optional, you have several options.
If you are migrating an existing application, the easiest from a start would be to exclude the auto-configuration and create the infrastructure yourself. Not in the way you've indicated as returning null from a #Bean method is quite nasty. Rather you could have some service that could lazily create the client and you could update your optional usages of MongoDB to go through that service. The service would be created regardless but would only create the underlying infrastructure if necessary.
The other option is to use a profile. If the main use case is that MongoDB is available then create a application-nomongo.properties (something like that) where you would exclude the auto-configuration using the spring.autoconfigure.exclude property. When the application starts without mongo, you can enable the nomongo profile and the auto-configuration will backoff. When it's not enabled, the Mongo bean will be created by Spring Boot.
I had the same issue. I want my application to be able to run offline as well, queue stuff and from time to time flush to database. This means it needs to be able to launch offline and connect whenever internet is available. Ofc, Spring crashed my app when it did not succeed in connecting.
What worked for me was this:
#Configuration
#EnableJpaRepositories(bootstrapMode = BootstrapMode.LAZY)
public class DataSourceConfig {
#Bean(destroyMethod = "close")
public DataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName("xxx");
hikariConfig.setJdbcUrl("xxx");
hikariConfig.setUsername("xxx");
hikariConfig.setPassword("xxx");
hikariConfig.setConnectionTestQuery("SELECT 1");
hikariConfig.setPoolName("springHikariCP");
hikariConfig.setInitializationFailTimeout(-1);
hikariConfig.addDataSourceProperty("dataSource.cachePrepStmts", "true");
hikariConfig.addDataSourceProperty("dataSource.prepStmtCacheSize", "250");
hikariConfig.addDataSourceProperty("dataSource.prepStmtCacheSqlLimit", "2048");
hikariConfig.addDataSourceProperty("dataSource.useServerPrepStmts", "true");
HikariDataSource dataSource = new HikariDataSource(hikariConfig);
return dataSource;
}
#Bean
public EntityManager entityManager() {
return entityManagerFactory().getObject().createEntityManager();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("blabla");
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
em.setJpaProperties(additionalProperties());
return em;
}
private Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL55Dialect");
return properties;
}
}
Also, place this in your App class
#SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class)
application.yml looks like this:
#Spring properties
spring:
jpa:
hibernate:
ddl-auto: update
datasource:
url: "xxx"
username: xxx
password: xxx
driver-class-name: com.mysql.cj.jdbc.Driver
continue-on-error: true
hikari:
connection-timeout: 2000
initialization-fail-timeout: -1
auto-commit: true
This works for me, hopefully it will work for you guys as well, I saw many posts around here searching for a solution to this. Good luck!