Usage of RequestContextHolder in a WebClient's ExchangeFilterFunction - spring

Background: I have traditional Spring MVC application (Spring Boot) that utilizes Spring Cloud OpenFeign (REST service consumption) and Apache CXF (SOAP service consumption). I'm trying to see if I can consolidate these two HTTP clients to just Spring's WebClient.
I'm trying to figure out if there is a better way to reference attributes placed in RequestContextHolder and then consume/use them in an ExchangeFilterFunction (replaces various Feign/CXF request interceptors).
For example, the following works:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.RouterFunctions;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
import java.util.HashMap;
import java.util.Map;
import static java.util.Objects.requireNonNull;
#SpringBootApplication
public class DemoApplication {
private static Log logger = LogFactory.getLog(DemoApplication.class);
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
RouterFunction<ServerResponse> router(WebClient.Builder webClientBuilder) {
WebClient webClient = webClientBuilder.baseUrl("https://jsonplaceholder.typicode.com").filter((req, next) -> {
Object foo = requireNonNull(RequestContextHolder.getRequestAttributes()).getAttribute("foo", WebRequest.SCOPE_REQUEST);
logger.info("Foo=" + foo);
return next.exchange(ClientRequest.from(req).header("foo", foo.toString()).build());
}).build();
return RouterFunctions.route()
.before((request) -> {
logger.info("Setting foo from " + Thread.currentThread().getName());
RequestContextHolder.getRequestAttributes().setAttribute("foo", "bar", WebRequest.SCOPE_REQUEST);
return ServerRequest.from(request).build();
})
.GET("/test", (request) -> {
ClientResponse response = webClient.get().uri("/todos/1").exchange().block();
Map<String, Object> body = new HashMap<>();
body.put("attributes", RequestContextHolder.getRequestAttributes().getAttributeNames(WebRequest.SCOPE_REQUEST));
body.put("todo", response.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {}).block());
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(body);
})
.build();
}
}
Please note the above is still a servlet/blocking application example. The only reactive part is WebClient and the ExchangeFilterFunction. RestTemplate is in maintenance mode and the recommendation is to use WebClient as per the javadoc
When I spam the endpoint with multiple requests, it seemingly works:
2020-08-24 21:26:22.829 INFO 15212 --- [nio-8080-exec-1] com.example.demo.DemoApplication : Setting foo from http-nio-8080-exec-1
2020-08-24 21:26:22.829 INFO 15212 --- [nio-8080-exec-1] com.example.demo.DemoApplication : Foo=bar
2020-08-24 21:26:22.915 INFO 15212 --- [nio-8080-exec-2] com.example.demo.DemoApplication : Setting foo from http-nio-8080-exec-2
2020-08-24 21:26:22.916 INFO 15212 --- [nio-8080-exec-2] com.example.demo.DemoApplication : Foo=bar
2020-08-24 21:26:23.011 INFO 15212 --- [nio-8080-exec-3] com.example.demo.DemoApplication : Setting foo from http-nio-8080-exec-3
2020-08-24 21:26:23.011 INFO 15212 --- [nio-8080-exec-3] com.example.demo.DemoApplication : Foo=bar
2020-08-24 21:26:23.118 INFO 15212 --- [nio-8080-exec-4] com.example.demo.DemoApplication : Setting foo from http-nio-8080-exec-4
2020-08-24 21:26:23.119 INFO 15212 --- [nio-8080-exec-4] com.example.demo.DemoApplication : Foo=bar
However this feels wrong to do since Reactor offers Context and even more so since this Reactor FAQ explicitly calls out the following (emphasis mine):
Now that we’ve established that MDC "just working" is not the best assumption to make in a declarative API
Any help/guidance is appreciated.

Related

How to configure Spring Boot 2 SASL OAUTHBEARER to connect to KAFKA AZURE EVENT HUB?

I am trying to connect a simple Spring Boot kafka Consumer to Azure Event Hub Kafka
I must authenticate to Azure Event Hub via Azure AD before polling or consuming message from topic
In my application.yaml
server:
port: 9000
spring:
kafka:
bootstrap-servers: xxxx.servicebus.windows.net:9093
properties:
sasl.jaas.config: org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required;
sasl.mechanism: OAUTHBEARER
security.protocol: SASL_SSL
request.timeout.ms: 60000
**sasl.login.callback.handler.class**: com.xxx.config.CustomAuthenticateCallbackHandler
consumer:
group-id: compass
properties:
spring.json:
use.type.headers: false
value.default.type: com.xxx.consumerMessage
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
topic:
name: global.xxx
I added this class to implement Kafka custom OAUTHBEARER to get and validate Token from Azure AD :
package com.xxxx..consumer.config;
//Copyright (c) Microsoft Corporation. All rights reserved.
//Licensed under the MIT License.
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeoutException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.AppConfigurationEntry;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.KafkaException;
import org.apache.kafka.common.security.auth.AuthenticateCallbackHandler;
import org.apache.kafka.common.security.oauthbearer.OAuthBearerToken;
import org.apache.kafka.common.security.oauthbearer.OAuthBearerTokenCallback;
import com.microsoft.aad.msal4j.ClientCredentialFactory;
import com.microsoft.aad.msal4j.ClientCredentialParameters;
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
import com.microsoft.aad.msal4j.IAuthenticationResult;
import com.microsoft.aad.msal4j.IClientCredential;
public class CustomAuthenticateCallbackHandler implements AuthenticateCallbackHandler {
final static ScheduledExecutorService EXECUTOR_SERVICE = Executors.newScheduledThreadPool(1);
private String authority;
private String appId;
private String appSecret;
private ConfidentialClientApplication aadClient;
private ClientCredentialParameters aadParameters;
#Override
public void configure(Map<String, ?> configs, String mechanism, List<AppConfigurationEntry> jaasConfigEntries) {
String bootstrapServer = Arrays.asList(configs.get(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG)).get(0).toString();
bootstrapServer = bootstrapServer.replaceAll("\\[|\\]", "");
URI uri = URI.create("https://" + bootstrapServer);
String sbUri = uri.getScheme() + "://" + uri.getHost();
this.aadParameters =
ClientCredentialParameters.builder(Collections.singleton(sbUri + "/.default"))
.build();
this.authority = "https://login.microsoftonline.com/xxxxxx/"; // replace <tenant-id> with your tenant id
this.appId = "xxxxxx"; // also called client id
this.appSecret = "xxxxx"; // also called client secret
}
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback: callbacks) {
if (callback instanceof OAuthBearerTokenCallback) {
try {
OAuthBearerToken token = getOAuthBearerToken();
OAuthBearerTokenCallback oauthCallback = (OAuthBearerTokenCallback) callback;
oauthCallback.token(token);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
e.printStackTrace();
}
} else {
throw new UnsupportedCallbackException(callback);
}
}
}
OAuthBearerToken getOAuthBearerToken() throws MalformedURLException, InterruptedException, ExecutionException, TimeoutException
{
if (this.aadClient == null) {
synchronized(this) {
if (this.aadClient == null) {
IClientCredential credential = ClientCredentialFactory.createFromSecret(this.appSecret);
this.aadClient = ConfidentialClientApplication.builder(this.appId, credential)
.authority(this.authority)
.build();
}
}
}
IAuthenticationResult authResult = this.aadClient.acquireToken(this.aadParameters).get();
System.out.println("TOKEN ACQUIRED");
return new OAuthBearerTokenImp(authResult.accessToken(), authResult.expiresOnDate());
}
public void close() throws KafkaException {
// NOOP
}
}
My doubts are :
Is this the best solution to do that with Spring , should I write by myself the class which implements AuthenticateCallbackHandler
If I change the **client Id ** constant to wrong value in CustomAuthenticateCallbackHandler.Java , in the console an exception is added :
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: com.microsoft.aad.msal4j.MsalServiceException: AADSTS700016: Application with identifier 'xxxxxx00f832e67A' was not found in the directory 'e4e1abd9-eac7-4a71-ab52-da5c998aa7ba'. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You may have sent your authentication request to the wrong tenant.
But after that, the console shows: Successfully logged in, is it normal to not authenticate and get Polling?
2021-07-07 15:00:07.924 INFO 16108 --- [ restartedMain] .o.i.e.ExpiringCredentialRefreshingLogin : ***Successfully logged in.***
2021-07-07 15:00:07.974 WARN 16108 --- [ restartedMain] o.a.k.clients.consumer.ConsumerConfig : The configuration 'spring.json.value.default.type' was supplied but isn't a known config.
2021-07-07 15:00:07.975 WARN 16108 --- [ restartedMain] o.a.k.clients.consumer.ConsumerConfig : The configuration 'spring.json.use.type.headers' was supplied but isn't a known config.
2021-07-07 15:00:07.976 INFO 16108 --- [ restartedMain] o.a.kafka.common.utils.AppInfoParser : Kafka version: 2.7.1
2021-07-07 15:00:07.976 INFO 16108 --- [ restartedMain] o.a.kafka.common.utils.AppInfoParser : Kafka commitId: xxx
2021-07-07 15:00:07.977 INFO 16108 --- [ restartedMain] o.a.kafka.common.utils.AppInfoParser : Kafka startTimeMs: 1625662807975
2021-07-07 15:00:07.978 INFO 16108 --- [ restartedMain] o.a.k.clients.consumer.KafkaConsumer : [Consumer clientId=consumer-xxxx-1, groupId=xxx] Subscribed to topic(s): global.finance.copa
2021-07-07 15:00:07.996 INFO 16108 --- [ restartedMain] c.l.c.consumer.KafkaOnAzureApplication : Started KafkaOnAzureApplication in 1.703 seconds (JVM running for 2.555)
2021-07-07 15:00:08.178 WARN 16108 --- [ntainer#0-0-C-1] org.apache.kafka.clients.NetworkClient : [Consumer clientId=consumer-xxx-1, groupId=xxxx] Connection to node -1 (xxx.servicebus.windows.net/xxxxx:9093) could not be established.
The SASL OUATHBEARER:

How can i setup Spring Boot with two datasources (MapRepository and H2 JPARepository)?

I'm trying to set up a spring boot project with two datasources.
First datasource would be a H2 Database and second a MapRepository.
Both repositories would share the same entity.
I could manage to setup a project with two H2 databases, but when I try to setup a MapRepository instead of the second H2 datasource I get the following error:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.0)
2021-01-12 10:57:16.610 INFO 26672 --- [ main] ch.getonline.springtestapp.App : Starting App using Java 15.0.1 on nbbetina1 with PID 26672 (C:\Users\BetinaHiestand\eclipse20-workspace\spring-test-app\target\classes started by BetinaHiestand in C:\Users\BetinaHiestand\eclipse20-workspace\spring-test-app)
2021-01-12 10:57:16.612 INFO 26672 --- [ main] ch.getonline.springtestapp.App : The following profiles are active: dev
2021-01-12 10:57:17.070 INFO 26672 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2021-01-12 10:57:17.070 INFO 26672 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Map repositories in DEFAULT mode.
2021-01-12 10:57:17.092 INFO 26672 --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data Map - Could not safely identify store assignment for repository candidate interface ch.getonline.springtestapp.storage.repositories.map.MapRepository. If you want this repository to be a Map repository, consider extending one of the following types with your repository: org.springframework.data.keyvalue.repository.KeyValueRepository.
2021-01-12 10:57:17.092 INFO 26672 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 15 ms. Found 0 Map repository interfaces.
2021-01-12 10:57:17.094 INFO 26672 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2021-01-12 10:57:17.094 INFO 26672 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2021-01-12 10:57:17.111 INFO 26672 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 14 ms. Found 1 JPA repository interfaces.
2021-01-12 10:57:17.654 INFO 26672 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-01-12 10:57:17.661 INFO 26672 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-01-12 10:57:17.661 INFO 26672 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.39]
2021-01-12 10:57:17.758 INFO 26672 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-01-12 10:57:17.758 INFO 26672 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1105 ms
2021-01-12 10:57:17.976 INFO 26672 --- [ main] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/dbadmin'. Database available at 'jdbc:h2:mem:db1dev'
2021-01-12 10:57:18.058 INFO 26672 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2021-01-12 10:57:18.099 INFO 26672 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.4.23.Final
2021-01-12 10:57:18.198 INFO 26672 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2021-01-12 10:57:18.324 INFO 26672 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
Hibernate:
drop table if exists "BasicEntity" CASCADE
Hibernate:
create table "BasicEntity" (
"DNA" binary not null,
"id" varchar(255),
"type" varchar(255),
primary key ("DNA")
)
2021-01-12 10:57:18.759 INFO 26672 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-01-12 10:57:18.765 INFO 26672 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-01-12 10:57:18.787 INFO 26672 --- [ main] ch.getonline.springtestapp.App : SpringTestApplication is starting...
2021-01-12 10:57:18.931 WARN 26672 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'appContext': Unsatisfied dependency expressed through field 'entityStorage'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'entityStorageHandler' defined in file [C:\Users\BetinaHiestand\eclipse20-workspace\spring-test-app\target\classes\ch\getonline\springtestapp\storage\handlers\EntityStorageHandler.class]: Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'ch.getonline.springtestapp.storage.repositories.map.MapRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
2021-01-12 10:57:18.931 INFO 26672 --- [ main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2021-01-12 10:57:18.933 INFO 26672 --- [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2021-01-12 10:57:18.944 INFO 26672 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-01-12 10:57:18.956 ERROR 26672 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 2 of constructor in ch.getonline.springtestapp.storage.handlers.EntityStorageHandler required a bean of type 'ch.getonline.springtestapp.storage.repositories.map.MapRepository' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'ch.getonline.springtestapp.storage.repositories.map.MapRepository' in your configuration.
I already tried to add the ComponentScan and add a repository annotation to the MapRepository, but couldn't figure out why no bean was created for it. Both repositories are in seperate packages which are set as basePackages for the EnableMapRepositories/EnableJpaRepositories annotation. For the SQLRepository I created a configuration class with the driver properties etc. I am not sure if something like this would also be needed for the MapRepositories and couldn't find helpful documentation about it.
I am not really experienced with Spring Boot therefore the first question would be if its possible to have a setup like this? And if yes how am I supposed to configure it?
Application start:
#SpringBootApplication
#ComponentScan (basePackages = {"ch.getonline.springtestapp"})
#EntityScan("ch.getonline.springtestapp.entity.types")
#EnableMapRepositories(basePackages = "ch.getonline.springtestapp.storage.repositories.map")
#EnableJpaRepositories(basePackages = "ch.getonline.springtestapp.storage.repositories.sql", entityManagerFactoryRef = "sqlDatabaseEntityManager", transactionManagerRef = "sqlDatabaseTransactionManager")
public class App {
// Logger setup (Per class)
private static final Logger log = LoggerFactory.getLogger(App.class);
/*
* Application start
*/
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(App.class, args);
System.out.println("App context in main: " + ctx.getDisplayName());
}
MapRepository:
package ch.getonline.springtestapp.storage.repositories.map;
import org.springframework.stereotype.Repository;
import ch.getonline.springtestapp.storage.repositories.EntityRepository;
#Repository("mapRepository")
public interface MapRepository extends EntityRepository {
}
EntityRepository:
package ch.getonline.springtestapp.storage.repositories;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.NoRepositoryBean;
import ch.getonline.springtestapp.entity.types.BasicEntity;
#NoRepositoryBean
public interface EntityRepository extends CrudRepository<BasicEntity, Long>{
//Entity findByUuid(UUID id);
}
StorageHandler in which I tried to access both repositories:
package ch.getonline.springtestapp.storage.handlers;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import ch.getonline.springtestapp.AppContext;
import ch.getonline.springtestapp.entity.attribute.Attribute;
import ch.getonline.springtestapp.entity.types.BasicEntity;
import ch.getonline.springtestapp.storage.StorageHandler;
import ch.getonline.springtestapp.storage.repositories.EntityRepository;
import ch.getonline.springtestapp.storage.repositories.map.MapRepository;
import ch.getonline.springtestapp.storage.repositories.sql.SQLRepository;
/** Entity Storage
* <br>
*
* - Coordinates saving, loading, updating of Entities over different Repositories
*
*
* #author sigi
*
*/
#Component
public class EntityStorageHandler implements StorageHandler<BasicEntity, Long> {
// Logger
private static final Logger log = LoggerFactory.getLogger(EntityStorageHandler.class);
private final AppContext app;
private final Map<String, EntityRepository> repos;
EntityStorageHandler(AppContext app, SQLRepository sqlRepo, MapRepository mapRepo) {
this.app = app;
this.repos = new HashMap<String, EntityRepository>();
this.repos.put("sql", sqlRepo);
this.repos.put("map", mapRepo);
}
//StorageHandler start hook
public void run(String... args) throws Exception {
//Print all configs for the key app in the config
StringBuilder appConfig = new StringBuilder();
for(Entry<String, Object> entry : this.app.getConfig().entrySet()) {
appConfig.append("\nkey: " + entry.getKey() + " value: " + entry.getValue());
}
log.info(appConfig.toString());
//Write demo Entity into db
BasicEntity e1 = new BasicEntity();
e1.setId("1");
e1.setType("Type1");
this.repos.get("sql").save(e1);
BasicEntity e2 = new BasicEntity();
e2.setId("2");
e2.setType("Type2");
this.repos.get("sql").save(e2);
BasicEntity e3 = new BasicEntity();
e3.setId("3");
e3.setType("Type3");
this.repos.get("map").save(e2);
}
}
BasicEntity:
package ch.getonline.springtestapp.entity.types;
import java.util.UUID;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.springframework.data.keyvalue.annotation.KeySpace;
import ch.getonline.springtestapp.entity.GenericEntity;
/**
* Basic Entity Implementation
*
* #author sigi
*
*/
#Entity
#KeySpace("basicEntities")
public class BasicEntity extends GenericEntity {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
UUID DNA;
}
SQLConfiguration:
package ch.getonline.springtestapp.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
#Configuration
public class SQLConfiguration {
#Autowired
private Environment env;
public SQLConfiguration() {
super();
}
#Primary
#Bean
public LocalContainerEntityManagerFactoryBean sqlDatabaseEntityManager() {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(sqlDataSource());
em.setPackagesToScan("ch.getonline.springtestapp.entity.types");
final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
final HashMap<String, Object> properties = new HashMap<String, Object>();
properties.put("hibernate.hbm2ddl.auto", env.getProperty("spring.jpa.hibernate.ddl-auto"));
properties.put("hibernate.dialect", env.getProperty("spring.jpa.database-platform"));
properties.put("hibernate.jdbc.batch_size", env.getProperty("spring.jpa.properties.hibernate.jdbc.batch_size"));
properties.put("hibernate.order_inserts", env.getProperty("spring.jpa.properties.hibernate.order_inserts"));
properties.put("hibernate.order_updates", env.getProperty("spring.jpa.properties.hibernate.order_updates"));
properties.put("hibernate.jdbc.batch_versioned_data", env.getProperty("spring.jpa.properties.hibernate.jdbc.batch_versioned_data"));
properties.put("hibernate.generate_statistics", env.getProperty("spring.jpa.properties.hibernate.generate_statistics"));
properties.put("hibernate.id.new_generator_mappings", env.getProperty("spring.jpa.properties.hibernate.id.new_generator_mappings"));
properties.put("hhibernate.cache.use_second_level_cache", env.getProperty("spring.jpa.properties.hibernate.cache.use_second_level_cache"));
properties.put("hibernate.globally_quoted_identifiers", env.getProperty("spring.jpa.properties.hibernate.globally_quoted_identifiers"));
properties.put("hibernate.format_sql", env.getProperty("spring.jpa.properties.hibernate.format_sql"));
properties.put("hibernate.show_sql", env.getProperty("spring.jpa.properties.hibernate.show_sql"));
properties.put("hibernate.use_sql_comments", env.getProperty("spring.jpa.properties.hibernate.use_sql_comments"));
properties.put("hibernate.type", env.getProperty("spring.jpa.properties.hibernate.type"));
properties.put("hibernate.naming.physical-strategy", env.getProperty("spring.jpa.hibernate.naming.physical-strategy"));
em.setJpaPropertyMap(properties);
return em;
}
#Primary
#Bean
public DataSource sqlDataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("spring.datasource.driverClassName"));
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
#Primary
#Bean
public PlatformTransactionManager sqlDatabaseTransactionManager() {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(sqlDatabaseEntityManager().getObject());
return transactionManager;
}
}
SQLRepository
package ch.getonline.springtestapp.storage.repositories.sql;
import ch.getonline.springtestapp.storage.repositories.EntityRepository;
public interface SQLRepository extends EntityRepository {
}
application.yml
#Debug mode
debug: false
#External config
spring:
#Basic setup
profiles.active: dev
config:
import: optional:classpath:config/app.properties, optional:classpath:config/config.yml
#Localization
messages:
basename: config.i18n.messages
#db
h2:
console:
path: /dbadmin
enabled: true
settings:
web-allow-others: true
datasource:
username: inmemory
password: inmemory
driverClassName: org.h2.Driver
port: 8080
#jpa
jpa:
hibernate:
ddl-auto: create
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
properties:
hibernate:
id:
new_generator_mappings: true
cache:
use_second_level_cache: true
order_inserts: true
order_updates: true
globally_quoted_identifiers: true
generate_statistics: false
show_sql: true
format_sql: true
use_sql_comments: true
type: trace
jdbc:
batch_size: 500
batch_versioned_data: false
tmp:
use_jdbc_metadata_defaults: false
database-platform: org.hibernate.dialect.H2Dialect
As written in my comment, it's quite unusual to use two different datasources with one spring boot service. But here are cases where this might be necessary, and here is how to achieve it in a clean way:
Keep in mind, my answer is mostly taken from that Baeldung tutorial
Consider having a datasource like this:
spring.datasource.jdbcUrl = [url]
spring.datasource.username = [username]
spring.datasource.password = [password]
Now, we want to add a second one, preferably with the same syntax:
spring.second-datasource.jdbcUrl = [url]
spring.second-datasource.username = [username]
spring.second-datasource.password = [password]
To use both configurations simoultanously, we just create two configuration classes with a Datasource Bean - pay attention to the prefix annotation:
#Configuration
#PropertySource({"classpath:persistence-multiple-db-boot.properties"})
#EnableJpaRepositories(
basePackages = "com.baeldung.multipledb.dao.user",
entityManagerFactoryRef = "userEntityManager",
transactionManagerRef = "userTransactionManager")
public class PersistenceUserAutoConfiguration {
#Primary
#Bean
#ConfigurationProperties(prefix="spring.datasource")
public DataSource userDataSource() {
return DataSourceBuilder.create().build();
}
// userEntityManager bean
// userTransactionManager bean
}
#Configuration
#PropertySource({"classpath:persistence-multiple-db-boot.properties"})
#EnableJpaRepositories(
basePackages = "com.baeldung.multipledb.dao.product",
entityManagerFactoryRef = "productEntityManager",
transactionManagerRef = "productTransactionManager")
public class PersistenceProductAutoConfiguration {
#Bean
#ConfigurationProperties(prefix="spring.second-datasource")
public DataSource productDataSource() {
return DataSourceBuilder.create().build();
}
// productEntityManager bean
// productTransactionManager bean
}
You could imo just create one configuration class and provide both beans in that one.
For more information see Spring JPA – Multiple Databases

Springboot : failed while scanning for beans

I am trying to understand a concept in springboot.
I have project structure as mentioned below. There are two packages and I have one class in each package.
src/main/java
> com.emerald.paymentengine
ApplicationRun.java
> com.emerald.paymentengine.config
DataSourceDbConfig.java
When I am trying to run the ApplicationRun.java following above structure, I am getting below error :
Error:
2020-08-19 01:33:08.673 INFO 30128 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-08-19 01:33:08.673 INFO 30128 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.37]
2020-08-19 01:33:08.749 INFO 30128 --- [ restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-08-19 01:33:08.750 INFO 30128 --- [ restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 657 ms
2020-08-19 01:33:08.779 WARN 30128 --- [ restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'applicationRun': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.emerlad.paymentengine.config.DataSourceDbConfig' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
2020-08-19 01:33:08.781 INFO 30128 --- [ restartedMain] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2020-08-19 01:33:08.791 INFO 30128 --- [ restartedMain] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-08-19 01:33:08.883 ERROR 30128 --- [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.emerald.paymentengine.ApplicationRun required a bean of type 'com.emerlad.paymentengine.config.DataSourceDbConfig' that could not be found.
Action:
Consider defining a bean of type 'com.emerlad.paymentengine.config.DataSourceDbConfig' in your configuration.
But when I moved DataSourceDbConfig.java in the same package as mentioned below, it's running and I am getting below output :
src/main/java
> com.emerald.paymentengine
ApplicationRun.java
DataSourceDbConfig.java
Output :
2020-08-19 01:37:24.726 INFO 34364 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-08-19 01:37:24.726 INFO 34364 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.37]
2020-08-19 01:37:24.810 INFO 34364 --- [ restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-08-19 01:37:24.810 INFO 34364 --- [ restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 722 ms
2020-08-19 01:37:24.849 INFO 34364 --- [ restartedMain] f.a.AutowiredAnnotationBeanPostProcessor : Autowired annotation is not supported on static fields: static com.emerald.paymentengine.DataSourceDbConfig com.emerald.paymentengine.ApplicationRun.dbConfig
Connection established !!
2020-08-19 01:37:24.981 INFO 34364 --- [ restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-08-19 01:37:25.116 INFO 34364 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2020-08-19 01:37:25.140 INFO 34364 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8082 (http) with context path ''
2020-08-19 01:37:25.148 INFO 34364 --- [ restartedMain] c.emerald.paymentengine.ApplicationRun : Started ApplicationRun in 1.335 seconds (JVM running for 2.406)
I was thinking #SpringBootApplication will automatically scan the main and subpackages and will be able to pick the required bean. How can I make it work by placing Config.java file in different package as mentioned in the very first scenario?
Code :
ApplicationRun.java
package com.emerald.paymentengine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class ApplicationRun {
#Autowired
static
DataSourceDbConfig dbConfig;
public ApplicationRun(DataSourceDbConfig dbConfig){
ApplicationRun.dbConfig = dbConfig ;
}
public static void main(String[] args) {
SpringApplication.run(ApplicationRun.class, args);
dbConfig.dataSource();
}
}
DataSourceDbConfig.java
package com.emerlad.paymentengine.config;;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import oracle.jdbc.pool.OracleDataSource;
import java.sql.SQLException;
import javax.sql.DataSource;
#Component
#Configuration
#ConfigurationProperties("spring.datasource")
public class DataSourceDbConfig {
#Value("${spring.datasource.url}")
private String dbUrl;
#Value("${spring.datasource.username}")
private String dbUser;
#Value("${spring.datasource.secure}")
private String dbPasswrd;
#Bean
public DataSource dataSource() {
OracleDataSource dataSource = null;
try {
dataSource = new OracleDataSource();
dataSource.setUser(dbUser);
dataSource.setPassword(dbPasswrd);
dataSource.setURL(dbUrl);
System.out.println("Connection established !!");
} catch (SQLException e) {
System.out.println("An issue occured while establishing connection !!");
e.printStackTrace();
}
return dataSource;
}
}
I'll be honest, I do not understand what it is that you want to accomplish, but this is how to get the DataSource bean in your main method:
ApplicationRun.java:
package com.emerald.paymentengine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.emerlad.paymentengine.config.DataSourceDbConfig;
import org.springframework.context.annotation.Import;
import javax.sql.DataSource;
#SpringBootApplication
#Import(DataSourceDbConfig.class)
public class ApplicationRun {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(ApplicationRun.class, args);
DataSource dataSource = context.getBean(DataSource.class)
}
}
DataSourceDbConfig.java:
package com.emerlad.paymentengine.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import oracle.jdbc.pool.OracleDataSource;
import java.sql.SQLException;
import javax.sql.DataSource;
#Configuration
#ConfigurationProperties("spring.datasource")
public class DataSourceDbConfig {
#Value("${spring.datasource.url}")
private String dbUrl;
#Value("${spring.datasource.username}")
private String dbUser;
#Value("${spring.datasource.secure}")
private String dbPasswrd;
#Bean
public DataSource dataSource() {
OracleDataSource dataSource = null;
try {
dataSource = new OracleDataSource();
dataSource.setUser(dbUser);
dataSource.setPassword(dbPasswrd);
dataSource.setURL(dbUrl);
System.out.println("Connection established !!");
} catch (SQLException e) {
System.out.println("An issue occured while establishing connection !!");
e.printStackTrace();
}
return dataSource;
}
}
class DataSourceDbConfig - using #Configuration includes #Component, so you can get rid of it
class ApplicationRun - I would keep it simple and remove the DataSourceDbConfig
#SpringBootApplication
public class ApplicationRun {
public static void main(String[] args) {
ApplicationContext appContext = SpringApplication.run(ApplicationRun.class, args);
// Getting datasource from application context.
DataSource dataSource = appContext.getBean(DataSource.class);
}
}

Java Spring Boot Rest API 400 Bad Request

I am trying to make a async Rest API with Spring Boot.
The Rest API receives http requests that have a json body with one value "zahl", then it extracts that value, opens a socket and sends a tcp request with that number to a loadbalacer, that divides the Requests on different servers.
They all make the same and calculate wheter the given number is a prime or not.
If it is, it returns a String for example: "(89) is prime: (true)" or "(10) is prime: false".
The result goes back over the loadbalancer to the Rest API and finally to the client.
That's how it should works.
But for most requests I get Bad Request 400. Sometimes it works fine.
400 Bad Request
Therefore I tried to do the calculation directly instead of opening a socket and it works perfectly.
So the problem is the Socket right?
Here is my Code:
package javaRestApi;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
#RestController
public class MyRestController extends SpringBootServletInitializer {
private static Logger log = LoggerFactory.getLogger(MyRestController.class);
public static void main(String[] args) {
SpringApplication.run(MyRestController.class, args);
}
#Autowired
private AsyncService service;
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(MyRestController.class);
}
#PostMapping(value = "/api/request/json", consumes = MediaType.APPLICATION_JSON_VALUE)
public String jsonRequest(#RequestBody RequestModel body) throws IOException, Exception {
log.info("new Request");
CompletableFuture<String> result = service.isPrime(body.getZahl());
log.info("Result: " + result.get());
return result.get();
}
}
package javaRestApi;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown=true)
public class RequestModel {
private String zahl;
public void setZahl(String zahl) {
this.zahl=zahl;
}
public String getZahl() {
return zahl;
}
}
package javaRestApi;
import java.util.concurrent.Executor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
#Configuration
#EnableAsync
public class AsyncConfiguration {
#Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("AsyncThread-");
executor.initialize();
return executor;
}
}
And finally the class where I create the Socket...
package javaRestApi;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.concurrent.CompletableFuture;
import javax.net.SocketFactory;
import java.net.Socket;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
#Service
public class AsyncService {
#Async("asyncExecutor")
public CompletableFuture<String> isPrime(String zahl) throws IOException, Exception {
//direct calcualtion
int z = Integer.parseInt(zahl);
if(prime(z))
return CompletableFuture.completedFuture("" + z + " is prime");
else
return CompletableFuture.completedFuture("" + z + " is no prime");
//with Tcp Connection
//return CompletableFuture.completedFuture("3"/*tcpClient(zahl)*/);
}
//local prime calculation
private static boolean prime(int zahl) {
for(int i = 2; i<zahl; i++) {
if(zahl % i == 0) {
return false;
}
}
return true;
}
// TCP connection to load balancer
private static String tcpClient(String zahl) throws IOException, Exception {
java.net.Socket socket = SocketFactory.getDefault().createSocket("localhost", 12345);
senden(socket, zahl);
return lesen(socket);
}
// send the value to Socket
static void senden(java.net.Socket socket, String zahl) throws IOException {
PrintWriter prwr = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
prwr.print(zahl);
prwr.flush();
}
// get the result from the socket
static String lesen(java.net.Socket socket) throws IOException {
int Buflength = 40;
BufferedReader bufR = new BufferedReader(new InputStreamReader(socket.getInputStream()));
char[] buf = new char[Buflength];
int anzZeichen = bufR.read(buf, 0, Buflength);
String retString = new String(buf, 0, anzZeichen);
return retString;
}
}
And here the Stacktrace.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.1.RELEASE)
2020-06-26 20:22:36.106 INFO 71152 --- [ main] javaRestApi.MyRestController : Starting MyRestController on DESKTOP-FVOQ69M with PID 71152 (C:\Users\Fill07\Desktop\javaRestApi\target\classes started by Fill07 in C:\Users\Fill07\Desktop\javaRestApi)
2020-06-26 20:22:36.110 INFO 71152 --- [ main] javaRestApi.MyRestController : No active profile set, falling back to default profiles: default
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.springframework.cglib.core.ReflectUtils (file:/C:/Users/Fill07/.m2/repository/org/springframework/spring-core/5.2.7.RELEASE/spring-core-5.2.7.RELEASE.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of org.springframework.cglib.core.ReflectUtils
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
2020-06-26 20:22:37.870 INFO 71152 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-06-26 20:22:37.884 INFO 71152 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-06-26 20:22:37.884 INFO 71152 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.36]
2020-06-26 20:22:38.213 INFO 71152 --- [ main] org.apache.jasper.servlet.TldScanner : At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
2020-06-26 20:22:38.225 INFO 71152 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-06-26 20:22:38.225 INFO 71152 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2041 ms
2020-06-26 20:22:38.356 INFO 71152 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService
2020-06-26 20:22:38.358 INFO 71152 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'asyncExecutor'
2020-06-26 20:22:38.845 INFO 71152 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-06-26 20:22:38.860 INFO 71152 --- [ main] javaRestApi.MyRestController : Started MyRestController in 3.385 seconds (JVM running for 3.941)
2020-06-26 20:22:40.830 INFO 71152 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-06-26 20:22:40.830 INFO 71152 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2020-06-26 20:22:40.841 INFO 71152 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 11 ms
2020-06-26 20:22:40.977 INFO 71152 --- [nio-8080-exec-2] javaRestApi.MyRestController : new Request
2020-06-26 20:22:41.013 INFO 71152 --- [nio-8080-exec-2] javaRestApi.MyRestController : Result: HTTP/1.1 400 Bad Request
Connection: cl
I don't understand what is wrong, i mean sometimes it works.
How can I fix it?
Is it maybe a problem with Tomcat?
Thanks for any advice.

Spring Actuator /Health Logger set to OFF, yet still logging

I want to set the Logger for just the /health actuator endpoint to OFF in the application.properties file. Application is on Spring Boot 1.5.
The /health would be our new monitoring url for the F5. I don't want to flood the logs.
I have this.
logging.level.org.springframework.web=DEBUG
org.springframework.boot.actuate.health.Logger=OFF
logging.level.org.springframework.boot.actuate.health=OFF
I'm still getting DEBUG logging in the console and the log file. The only thing that works is setting the first one to INFO or higher. But, that is not desirable. So, right now i'm getting this
2020-05-06 17:14:01.545 DEBUG 58588 --- [nio-9095-exec-5] o.s.web.servlet.DispatcherServlet : DispatcherServlet with name 'dispatcherServlet' processing GET request for [/health]
2020-05-06 17:14:01.552 DEBUG 58588 --- [nio-9095-exec-5] o.s.web.servlet.DispatcherServlet : Last-Modified value for [/health] is: -1
2020-05-06 17:14:01.848 DEBUG 58588 --- [nio-9095-exec-5] m.m.a.RequestResponseBodyMethodProcessor : Written [UP {}] as "application/vnd.spring-boot.actuator.v1+json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#144409aa]
2020-05-06 17:14:01.849 DEBUG 58588 --- [nio-9095-exec-5] o.s.web.servlet.DispatcherServlet : Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
2020-05-06 17:14:01.849 DEBUG 58588 --- [nio-9095-exec-5] o.s.web.servlet.DispatcherServlet : Successfully completed request
Do i need to set a different logger property? For a different class/package?
I use logback, so I added a logging filter for this. You need to change the regexes to match your own logging format.
package eu.stackoverflow.logging;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
public class IgnoringHealthAndPrometheusLoggingFilter extends Filter<ILoggingEvent> {
private static final Pattern HEALTH_OR_PROMETHEUS =
Pattern.compile("GET \"/(health|prometheus)\", parameters=\\{}");
private static final Pattern COMPLETED =
Pattern.compile("Completed 200 OK");
private Set<String> activeThreads = new HashSet<>();
#Override
public FilterReply decide(ILoggingEvent loggingEvent) {
if (isHealthOrPrometheus(loggingEvent.getMessage())) {
activeThreads.add(loggingEvent.getThreadName());
return FilterReply.DENY;
} else if (isCompleted200Ok(loggingEvent.getMessage()) && activeThreads.remove(loggingEvent.getThreadName())) {
return FilterReply.DENY;
} else {
return FilterReply.ACCEPT;
}
}
private boolean isHealthOrPrometheus(String message) {
return HEALTH_OR_PROMETHEUS.matcher(message).matches();
}
private boolean isCompleted200Ok(String message) {
return COMPLETED.matcher(message).matches();
}
}
<appender class="ch.qos.logback.core.ConsoleAppender" name="CONSOLE">
<filter class="eu.stackoverflow.logging.IgnoringHealthAndPrometheusLoggingFilter" />
</appender>
More info on logback filters: https://logback.qos.ch/manual/filters.html

Resources