I am learning spring-cloud-config and I would like to use different profiles but I always get back values from the default profile.
I have two property files in the git repository and the file with prod suffix overrides a key from default profile:
image-service.properties
image-service-prod.properties
It seems that my config server works fine:
GET http://localhost:8888/image-service/default:
{"name":"image-service","profiles":["default"],"label":null,"version":"fb78fe4429a33c266d6eb07a9e482b8fd264dd7c","state":null,"propertySources":
[{"name":"https://bitbucket.org/.../...-configuration.git/image-service.properties","source":{"service.image.hello":"image-common"}}]}
GET http://localhost:8888/image-service/prod
{"name":"image-service","profiles":["prod"],"label":null,"version":"fb78fe4429a33c266d6eb07a9e482b8fd264dd7c","state":null,"propertySources":
[{"name":"https://bitbucket.org/.../...-configuration.git/image-service-prod.properties","source":{"service.image.hello":"image-prod"}},
{"name":"https://bitbucket.org/.../...-configuration.git/image-service.properties","source":{"service.image.hello":"image-common"}}]}
I activated prod profile in my REST application but always value from the default profile is shown.
application.properties of the client app:
server.port=8889
spring.application.name=image-service
spring.cloud.config.uri=http://localhost:8888
spring.profiles.active=prod
REST controller of the client app:
#RefreshScope
#RestController
public class EchoController {
#Value("${service.image.hello}")
private String hello;
#RequestMapping("/show")
#ResponseBody
public String showConfig() {
return new StringBuilder()
.append("image-service: ").append(hello)
.toString();
}
}
Result:
image-service: image-common
Log from client app:
c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:8888
c.c.c.ConfigServicePropertySourceLocator : Located environment: name=image-service, profiles=[default], label=null, version=fb78fe4429a33c266d6eb07a9e482b8fd264dd7c, state=null
b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource {name='configService', propertySources=[MapPropertySource {name='configClient'}, MapPropertySource {name='https://bitbucket.org/.../...-configuration.git/image-service.properties'}]}
c.r.d.springconfig.client.Application : The following profiles are active: prod
ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext#7ccdc9e7: startup date [Sun Sep 23 22:31:55 CEST 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext#7fd618b5
o.s.cloud.context.scope.GenericScope : BeanFactory id=51790958-c0a2-3d61-91d6-a8dcd5395c7e
trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$bf37cae6] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8889 (http)
It seems I missed something but I can not see what.
Related
I have post this question but with different format and got no answer and I think I didn't provide enough info.
What I am trying to do is parse a login request payload that contain a JSON with username and password. So, I had to extend UsernamePasswordAuthenticationFilter and replace the UsernamePasswordAuthenticationFilter in filter chain with my custom filter.
I've done that and registered my custom filter as you can see in the log from DefaultSecurityFilterChain:
2023-01-30T22:33:13.993+03:00 INFO 20436 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter#1ec09a68, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#7da9b32c, org.springframework.security.web.context.SecurityContextHolderFilter#2b6ff016, org.springframework.security.web.header.HeaderWriterFilter#118c1faa, org.springframework.security.web.authentication.logout.LogoutFilter#4f5df012, ali.yousef.chatty.config.security.JsonUsernamePasswordAuthenticationFilter#7e5c8c80, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#7a491a60, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#4a0f4282, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#46df794e, org.springframework.security.web.access.ExceptionTranslationFilter#470866d1, org.springframework.security.web.access.intercept.AuthorizationFilter#1d0fc0bc]
Until now everything works fine.
Here is the code:
SecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig
{
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {
JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordAuthenticationFilter = new JsonUsernamePasswordAuthenticationFilter();
jsonUsernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManager);
http
.csrf().disable()
.formLogin().disable()
.addFilterAfter(jsonUsernamePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests()
.requestMatchers("/api/**").authenticated()
.anyRequest().permitAll();
return http.build();
}
}
JsonUsernamePasswordAuthenticationFilter.java
public class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
Logger logger = LoggerFactory.getLogger(JsonUsernamePasswordAuthenticationFilter.class);
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordDto usernamePasswordDto;
try {
usernamePasswordDto = new ObjectMapper().readValue(request.getInputStream(), UsernamePasswordDto.class);
logger.info(usernamePasswordDto.toString());
} catch (IOException ioe) {
throw new AuthenticationServiceException(ioe.getMessage(), ioe);
}
UsernamePasswordAuthenticationToken authToken =
UsernamePasswordAuthenticationToken.unauthenticated(usernamePasswordDto.getUsername(), usernamePasswordDto.getPassword());
Authentication result = this.getAuthenticationManager().authenticate(authToken);
for (GrantedAuthority a : result.getAuthorities()) {
logger.info(a.getAuthority());
}
return result;
}
}
TestController.java
#RestController
public class TestController
{
#GetMapping("/")
public String home() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null)
return "null";
return auth.getName();
}
}
I send a request to /login and I get anonymousUser response. I thought I missed something in the custom filter so I printed the authorities the user has and got ROLE_USER.
Here is the full output
2023-01-30T22:48:30.329+03:00 INFO 20900 --- [ main] ali.yousef.chatty.ChattyApplication : No active profile set, falling back to 1 default profile: "default"
2023-01-30T22:48:30.851+03:00 INFO 20900 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2023-01-30T22:48:30.903+03:00 INFO 20900 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 37 ms. Found 1 JPA repository interfaces.
2023-01-30T22:48:31.355+03:00 INFO 20900 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2023-01-30T22:48:31.361+03:00 INFO 20900 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-01-30T22:48:31.361+03:00 INFO 20900 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.5]
2023-01-30T22:48:31.431+03:00 INFO 20900 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-01-30T22:48:31.432+03:00 INFO 20900 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1064 ms
2023-01-30T22:48:31.559+03:00 INFO 20900 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2023-01-30T22:48:31.591+03:00 INFO 20900 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.1.6.Final
2023-01-30T22:48:31.696+03:00 WARN 20900 --- [ main] org.hibernate.orm.deprecation : HHH90000021: Encountered deprecated setting [javax.persistence.sharedCache.mode], use [jakarta.persistence.sharedCache.mode] instead
2023-01-30T22:48:31.781+03:00 INFO 20900 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2023-01-30T22:48:31.983+03:00 INFO 20900 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl#77ea806f
2023-01-30T22:48:31.984+03:00 INFO 20900 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2023-01-30T22:48:32.032+03:00 INFO 20900 --- [ main] SQL dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
2023-01-30T22:48:32.847+03:00 INFO 20900 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2023-01-30T22:48:32.853+03:00 INFO 20900 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-01-30T22:48:33.070+03:00 WARN 20900 --- [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2023-01-30T22:48:33.220+03:00 INFO 20900 --- [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page: class path resource [static/index.html]
2023-01-30T22:48:33.519+03:00 INFO 20900 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter#5cd6a827, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#6cd65042, org.springframework.security.web.context.SecurityContextHolderFilter#5c30decf, org.springframework.security.web.header.HeaderWriterFilter#5aefdb9e, org.springframework.security.web.authentication.logout.LogoutFilter#50d6af87, ali.yousef.chatty.config.security.JsonUsernamePasswordAuthenticationFilter#7674f9d4, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#4a0f4282, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#356ab368, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#4b5ceb5d, org.springframework.security.web.access.ExceptionTranslationFilter#99f75e4, org.springframework.security.web.access.intercept.AuthorizationFilter#7da9b32c]
2023-01-30T22:48:33.688+03:00 INFO 20900 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-01-30T22:48:33.689+03:00 INFO 20900 --- [ main] o.s.m.s.b.SimpleBrokerMessageHandler : Starting...
2023-01-30T22:48:33.690+03:00 INFO 20900 --- [ main] o.s.m.s.b.SimpleBrokerMessageHandler : BrokerAvailabilityEvent[available=true, SimpleBrokerMessageHandler [org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry#5d3a238]]
2023-01-30T22:48:33.690+03:00 INFO 20900 --- [ main] o.s.m.s.b.SimpleBrokerMessageHandler : Started.
2023-01-30T22:48:33.696+03:00 INFO 20900 --- [ main] ali.yousef.chatty.ChattyApplication : Started ChattyApplication in 3.673 seconds (process running for 3.952)
2023-01-30T22:48:52.209+03:00 INFO 20900 --- [nio-8080-exec-3] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-01-30T22:48:52.209+03:00 INFO 20900 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2023-01-30T22:48:52.209+03:00 INFO 20900 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms
2023-01-30T22:48:52.250+03:00 INFO 20900 --- [nio-8080-exec-3] JsonUsernamePasswordAuthenticationFilter : UsernamePasswordDto(username=user01, password=password)
2023-01-30T22:48:52.421+03:00 INFO 20900 --- [nio-8080-exec-3] JsonUsernamePasswordAuthenticationFilter : ROLE_USER
What I think is after finishing the authentication process and sending a redirect to homepage the context is deleted somehow or cleared, but I'm not Spring Security expert.
I've done this in older versions with WebSecurityConfigurerAdapter without any problem but using this way of configuration didn't work.
I really tried everything I could and I would appreciate any help.
The SecurityContext is not saved by default after successful authentication. Moreover, the UsernamePasswordAuthenticationFilter is a form-login-based mechanism, so the Authentication object cannot be detected by the SessionManagementFilter, as the filter is not invoked during the authenticating request. So, you query the wrong Authentication object which in your case defaults to an Anonymous user.
So, first, after a successful authentication you have to save the authenticated Authentication object(returned from the attemptAuthentication() method) in the Security Context. You can do that by overriding the successfulAuthentication() method of your filter.
#Override
protected void successfulAuthentication(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, jakarta.servlet.FilterChain chain, Authentication authResult) throws IOException, jakarta.servlet.ServletException {
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authResult);
//SecurityContextHolder.setContext(context);
HttpSessionSecurityContextRepository secRepo = new HttpSessionSecurityContextRepository();
secRepo.saveContext(context, request, response);
super.successfulAuthentication(request, response, chain, authResult);
}
You have to save it, in a persistent way, i.e. it has to be preserved during the whole session. A way to do so is via using an instance of the HttpSessionSecurityContextRepository class and using its saveContext() method to save it.
After that, in your handler method for the default “/” home(), you can obtain the saved SecurityContextHolder, again by using an instance of the HttpSessionSecurityContextRepository class and via its loadDeferredContext() method.
Note, that for this purpose, first, it is necessary to bring the request object (returned from your filter) in your handler home() method. (For completeness you can do the same for the response object as well).
After that, you can obtain the authenticated user from the extracted security context.
#GetMapping("/")
public String home(jakarta.servlet.http.HttpServletRequest request,
jakarta.servlet.http.HttpServletResponse response) {
//Authentication auth = SecurityContextHolder.getContext().getAuthentication();
HttpSessionSecurityContextRepository secRepo = new HttpSessionSecurityContextRepository();
Authentication auth = secRepo.loadDeferredContext(request).get().getAuthentication();
if (auth == null) return "null";
return (String) auth.getPrincipal();
}
One more thing that should be mentioned, is that if you want you can invalidate the session in your handler. This is a "must" for security reasons, especially if you use a STATELESS application e.g.a REST API application.
Hope this helped you!
I got the solution for my problem from Marcus when I posted an issue on the spring security repository on github because I thought it was a bug. If you want to check the answer yourself you can go here
The issue was that the my custom filter was using RequestAttributeSecurityContextRepository but the default was DelegatingSecurityContextRepository, hence made the authentication not getting saved between requests.
Setting the SecurityContextRepository in jsonUsernamePasswordAuthenticationFilter to DelegatingSecurityContextRepository will solve the issue.
jsonUsernamePasswordAuthenticationFilter.setSecurityContextRepository(new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(),
new HttpSessionSecurityContextRepository()
));
I have implemented a basic Spring Boot Application which uses Spring Kafka. I want my producer to connect to the Kafka Topic before the first .send() is called but I can't find a way to do so. Is that possible?
Logs to show that KafkaTemplate only connects to the Kafka Topic after I trigger the .send method at 16:12:44:
2021-11-24 16:12:12.602 INFO 63930 --- [ main] c.e.k.KafkaProducerExampleApplication : The following profiles are active: dev
2021-11-24 16:12:13.551 INFO 63930 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-11-24 16:12:13.559 INFO 63930 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-11-24 16:12:13.559 INFO 63930 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.53]
2021-11-24 16:12:13.613 INFO 63930 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-11-24 16:12:13.613 INFO 63930 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 974 ms
2021-11-24 16:12:13.989 INFO 63930 --- [ main] pertySourcedRequestMappingHandlerMapping : Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2Controller#getDocumentation(String, HttpServletRequest)]
2021-11-24 16:12:14.190 INFO 63930 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-11-24 16:12:14.190 INFO 63930 --- [ main] d.s.w.p.DocumentationPluginsBootstrapper : Context refreshed
2021-11-24 16:12:14.207 INFO 63930 --- [ main] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
2021-11-24 16:12:14.239 INFO 63930 --- [ main] s.d.s.w.s.ApiListingReferenceScanner : Scanning for api listing references
2021-11-24 16:12:14.336 INFO 63930 --- [ main] c.e.k.KafkaProducerExampleApplication : Started KafkaProducerExampleApplication in 7.055 seconds (JVM running for 7.341)
2021-11-24 16:12:44.550 INFO 63930 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-11-24 16:12:44.550 INFO 63930 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2021-11-24 16:12:44.551 INFO 63930 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
2021-11-24 16:12:44.649 INFO 63930 --- [nio-8080-exec-1] o.a.k.clients.producer.ProducerConfig : ProducerConfig values:
Regarding Linh Vu's answer, it's best not to create a connection in a bean definition - it's too early in the application context's lifecycle.
Instead, add a bean that implements SmartLifecycle and create the connection in start(); that way, the context will be completely initialized before connecting.
#Bean
SmartLifecycle connector(ProducerFactory<Object ,Object> pf) {
return new SmartLifecycle() {
#Override
public void stop() {
}
#Override
public void start() {
pf.createProducer().close();
}
#Override
public boolean isRunning() {
return false;
}
};
}
With non-transactional producer (transactionIdPrefix is not supplied), when you first call KafkaTemplate.send, it will delegate to ProducerFactory to get a single instance of Producer. At this time, because there's no a single instance of Producer before, ProducerFactory will create this one for you (that's why you saw the log ProducerConfig : ProducerConfig values ...). This producer instance is now used/shared by all clients.
So if you want to create the above producer instance beforehand, you could directly call it on the ProducerFactory, e.g:
#Bean
public KafkaTemplate<?, ?> kafkaTemplate(ProducerFactory<Object, Object> kafkaProducerFactory) {
KafkaTemplate<Object, Object> kafkaTemplate = new KafkaTemplate(kafkaProducerFactory);
kafkaProducerFactory.createProducer();
return kafkaTemplate;
...
SmartLifecycle bean works for us, thanks.
#Component
class KafkaProducer (
private val userChangeLogTemplate: KafkaTemplate<String, UserChangeLog>
private val kafkaProperties: MizenKafkaProperties
) : NotificationProducer{
#Bean
fun connector(pf: ProducerFactory<String, Any>): SmartLifecycle {
return object : SmartLifecycle {
override fun stop() {}
override fun start() {
pf.createProducer().close()
}
override fun isRunning(): Boolean {
return false
}
}
}
override fun sendUserChangeLog(message: UserChangeLog) {
userChangeLogTemplate.send(kafkaProperties.userChangeLogTopic, message)
}
}
Spring boot 2.5.4 I used #PostConstruct for the very first time in my service class. As following:-
#Slf4j
#Service
#AllArgsConstructor
public class FileMonitorService {
private final AppProperties appProperties;
private final WatchService watchService;
private final RestTemplate restTemplate;
#PostConstruct
#Async
public void startMonitoring() {
FileUtils.setAppProperties(appProperties);
FileUtils.setRestTemplate(restTemplate);
FileUtils.readFilesForDirectory();
log.info("START_MONITORING");
try {
WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
log.info("Event kind: {}; File affected: {}", event.kind(), event.context());
if((event.kind() == StandardWatchEventKinds.ENTRY_CREATE ||
event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) &&
event.context().toString().contains(".xml")){
try {
restTemplateRequest(event.context().toString()+" processing");
FileUtils.readXml(Path.of(FileUtils.getFileAbsolutePath(appProperties.getDataIn()),
event.context().toString()));
}catch (Exception e){
log.error("startMonitoring Exception: "+e.getMessage());
}
}
}
key.reset();
}
} catch (InterruptedException e) {
log.warn("startMonitoring: interrupted exception for monitoring service: "+e.getMessage());
}
}
}
This method is called as soon as app launched. That is my requirements to process all file as soon as the app starts. I have controller as following:-
#RestController
#RequestMapping("/xml")
public class FileController {
#Autowired
FileMonitorService fileMonitorService;
#SneakyThrows
#GetMapping("/restart")
public String restartFileMonitoring(){
fileMonitorService.startMonitoring();
return "File monitoring restarted started successfully";
}
}
My app starts on port 8080 and no exception at all. But when I get call this end point localhost:8080/xml/restart
It is not reachable. If I comment out the #PostConstruct then I can call the end point. I am confused how to use this annotation properly. What is wrong in my code?
Update info:-
:: Spring Boot :: (v2.5.4)
2021-09-14 18:23:21.521 INFO 71192 --- [ main] c.f.i.task.BatchProcessorApplication : Starting BatchProcessorApplication using Java 14.0.2 on dev with PID 71192 (/home/dev/Desktop/batch-processor/batch-processor/target/classes started by dev in /home/dev/Desktop/batch-processor/batch-processor)
2021-09-14 18:23:21.523 INFO 71192 --- [ main] c.f.i.task.BatchProcessorApplication : No active profile set, falling back to default profiles: default
2021-09-14 18:23:22.485 INFO 71192 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-09-14 18:23:22.495 INFO 71192 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-09-14 18:23:22.495 INFO 71192 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.52]
2021-09-14 18:23:22.564 INFO 71192 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-09-14 18:23:22.564 INFO 71192 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 988 ms
File to monitor: /home/dev/Desktop/batch-processor/batch-processor/data/in
2021-09-14 18:23:22.647 INFO 71192 --- [ main] c.f.i.task.config.FileMonitorConfig : MONITORING_DIR: /home/dev/Desktop/batch-processor/batch-processor/data/in/
2021-09-14 18:23:22.667 INFO 71192 --- [ main] c.f.i.task.service.FileMonitorService : START_MONITORING
That is the log when I run the app. After debugging I found that while ((key = watchService.take()) != null) { call never returns until I copy some XML file as this app process xml files. Then I copy any xml file in the monitoring dir. I was expecting that #Async it will run in back ground thread in async mode. How to monitory this dir in background thread? So the caller of this method won't be blocked.
PostContstruct semantics
The PostConstruct annotation is part of JSR 330 (Dependency Injection) and is not a Spring custom annotation.
The annotation specification dictates that the annotated method MUST run before the service being injected into context or translated into a service.
Spring supports the PostConstruct lifecycle hook allowing to perform extra post-initialization actions once a bean has been initialized, i.e., it had all its dependencies injected.
Async semantics
The Async annotation on the other hand is a Spring specific annotation allowing to mark a method or a type as being a candidate for asynchronous execution.
Alternative
In a case where you are interested into starting a background process as long as you application starts, you should better use the application lifecycle events and more specifically the ApplicationReadyEvent to spin your monitoring activity:
#Slf4j
#Service
#AllArgsConstructor
public class FileMonitorService {
private final AppProperties appProperties;
private final WatchService watchService;
private final RestTemplate restTemplate;
#EventListener(ApplicationReadyEvent.class)
#Async
public void startMonitoring() {
// ...
}
}
And don't forget to add the #EnableAsync annotation on your Spring Boot configuration type to activate the asynchronous processing feature.
For your case, you don't need to use #PostConstruct and that is why its working when removing the #PostConstruct
to simplify, #PostConstruct is considered as a class empty constructor but it make sure all the Beans are loaded before being called
I'm playing around with Spring Boot and the reactive jdbc driver called r2dbc. In my main application I'm using Postgres as a database and now I want to the use h2 for the tests. And the Flyway migration is working with the setup but when the Spring application is able to insert records.
Here is my setup and code
#SpringBootTest
class CustomerRepositoryTest {
#Autowired
CustomerRepository repository;
#Test
void insertToDatabase() {
repository.saveAll(List.of(new Customer("Jack", "Bauer"),
new Customer("Chloe", "O'Brian"),
new Customer("Kim", "Bauer"),
new Customer("David", "Palmer"),
new Customer("Michelle", "Dessler")))
.blockLast(Duration.ofSeconds(10));
}
}
Here is the error that I'm getting
:: Spring Boot :: (v2.3.4.RELEASE)
2020-10-14 15:59:18.538 INFO 25279 --- [ main] i.g.i.repository.CustomerRepositoryTest : Starting CustomerRepositoryTest on imalik8088.fritz.box with PID 25279 (started by imalik in /Users/imalik/code/private/explore-java/spring-example)
2020-10-14 15:59:18.540 INFO 25279 --- [ main] i.g.i.repository.CustomerRepositoryTest : No active profile set, falling back to default profiles: default
2020-10-14 15:59:19.108 INFO 25279 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode.
2020-10-14 15:59:19.273 INFO 25279 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 160ms. Found 1 R2DBC repository interfaces.
2020-10-14 15:59:19.894 INFO 25279 --- [ main] o.f.c.internal.license.VersionPrinter : Flyway Community Edition 6.5.0 by Redgate
2020-10-14 15:59:20.052 INFO 25279 --- [ main] o.f.c.internal.database.DatabaseFactory : Database: jdbc:h2:mem:///DBNAME (H2 1.4)
2020-10-14 15:59:20.118 INFO 25279 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.022s)
2020-10-14 15:59:20.131 INFO 25279 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table "PUBLIC"."flyway_schema_history" ...
2020-10-14 15:59:20.175 INFO 25279 --- [ main] o.f.core.internal.command.DbMigrate : Current version of schema "PUBLIC": << Empty Schema >>
2020-10-14 15:59:20.178 INFO 25279 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema "PUBLIC" to version 1.0.0 - schma
2020-10-14 15:59:20.204 INFO 25279 --- [ main] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.036s)
2020-10-14 15:59:20.689 INFO 25279 --- [ main] i.g.i.repository.CustomerRepositoryTest : Started CustomerRepositoryTest in 2.466 seconds (JVM running for 3.326)
2020-10-14 15:59:21.115 DEBUG 25279 --- [ main] o.s.d.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [INSERT INTO customer (first_name, last_name) VALUES ($1, $2)]
org.springframework.data.r2dbc.BadSqlGrammarException: executeMany; bad SQL grammar [INSERT INTO customer (first_name, last_name) VALUES ($1, $2)]; nested exception is io.r2dbc.spi.R2dbcBadGrammarException: [42102] [42S02] Tabelle "CUSTOMER" nicht gefunden
Table "CUSTOMER" not found; SQL statement:
INSERT INTO customer (first_name, last_name) VALUES ($1, $2) [42102-200]
My src/test/resources/application.yaml is looking like this:
spring:
r2dbc:
url: r2dbc:h2:mem:///DBNAME?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
username: sa
password:
flyway:
url: jdbc:h2:mem:///DBNAME
baseline-on-migrate: true
user: sa
password:
Any ideas whats missing missing or whats wrong with the setup? If further information is needed please let me know.
Addition/Solution:
The url pattern is different between jdbc and r2dbc. The working solution for me is as follows:
url: r2dbc:h2:file:///./tmp/test-database
url: jdbc:h2:file:./tmp/test-database
And In order to setup Flyway you have to Configure Flyway:
// Flyway is not compatible with r2dbc yet, therefore this config class is created
#Configuration
public class FlywayConfig {
private final Environment env;
public FlywayConfig(final Environment env) {
this.env = env;
}
#Bean(initMethod = "migrate")
public Flyway flyway() {
return new Flyway(Flyway.configure()
.baselineOnMigrate(true)
.dataSource(
env.getRequiredProperty("spring.flyway.url"),
env.getRequiredProperty("spring.flyway.user"),
env.getRequiredProperty("spring.flyway.password"))
);
}
}
I've faced the same issue to setup and access to h2 database in memory for tests:
Liquibase for database migration using JDBC driver
Tests Reactive Crud Repository using R2DBC driver
Error encoutred:
org.springframework.data.r2dbc.BadSqlGrammarException: executeMany; bad SQL grammar [INSERT INTO MY_TABLE... Table "MY_TABLE" not found ...
Inspired by Chris's solution, i configured my src/testresources/application.properties file as follow:
spring.r2dbc.url=r2dbc:h2:mem:///~/db/testdb
spring.r2dbc.username=sa
spring.r2dbc.password=
spring.liquibase.url=jdbc:h2:mem:~/db/testdb;DB_CLOSE_DELAY=-1
spring.liquibase.user=sa
spring.liquibase.password=
spring.liquibase.enabled=true
I am currently having the same problem using r2dbc with liquibase. I am suspecting that the JDBC url points to a different database due to a slightly different syntax between R2DB and JDBC. I can manage to get h2 running from the file system though...
url: r2dbc:h2:file:///~/db/testdb
...
url: jdbc:h2:file:~/db/testdb
EDIT:
In non-reactive Spring Data I'd usually populate the Schema into the H2 memory database using a schema.sql/data.sql pair. This is also possible with R2DBC, but you have to configure the populator yourself.
It's also in the Getting Started R2DBC Tutorial. Basically you have to register a ConnectionFactoryInitializer bean.
#Bean
public ConnectionFactoryInitializer initializer(#Qualifier("connectionFactory") ConnectionFactory connectionFactory) {
var initializer = new ConnectionFactoryInitializer();
initializer.setConnectionFactory(connectionFactory);
var populator = new CompositeDatabasePopulator();
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("schema.sql")));
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("data.sql")));
initializer.setDatabasePopulator(populator);
return initializer;
}
I was able to get it working.
First of all I created following test configuration class (because I want to execute tests only agains H2, on production mode I am using PostgreSQL):
#TestConfiguration
public class TestConfig {
#Bean
#Profile("test")
public ConnectionFactory connectionFactory() {
System.out.println(">>>>>>>>>> Using H2 in mem R2DBC connection factory");
return H2ConnectionFactory.inMemory("testdb");
}
#Bean(initMethod = "migrate")
#Profile("test")
public Flyway flyway() {
System.out.println("####### Using H2 in mem Flyway connection");
return new Flyway(Flyway.configure()
.baselineOnMigrate(true)
.dataSource(
"jdbc:h2:mem:testdb",
"sa",
"")
);
}
}
As you can see in the code above, both beans are scoped to the "test" profile only. As you can imagine I have pretty much the same beans in a regular ApplicationConfiguration class but annotated as a #Profile("default") and configured to use a PostgreSQL.
Second thing is that I created annotation which combines several other annotations to not repeat myself and to easily pickup beans declared in the TestConfig class:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Inherited
#SpringBootTest
#ActiveProfiles("test")
#Import(TestConfig.class)
public #interface IntegrationTest {
}
Now the test itself:
#IntegrationTest
class CartsIntegrationTest {
// test methods here ....
}
I believe the main hint is to use H2ConnectionFactory.inMemory("testdb");
Flyway currently only supports the blocking JDBC APIs, and it is not compatible with the reactive r2dbc if possbile do not mix them in the same application.
Try to register a ConnectionFactoryInitializer to initiate the database schema and data as #Chris posted, my working example can be found here.
Try nkonev/r2dbc-migrate which is trying to migrate the flyway to the R2dbc world.
There were 2 issues I was experiencing in my project.
I needed to include the dependency:
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-h2</artifactId>
<scope>test</scope>
</dependency>
I needed to change the value for spring.r2dbc.url to r2dbc:h2:mem:///test_db
With these changes, rd2bc worked with an in memory h2 database for testing. See also:
https://github.com/r2dbc/r2dbc-h2
Building a separate Web Client and Rest Service in Spring Boot. Making two separate apps. Trying to make an AJAX call from the client on http://localhost:8081/ and hit the rest service on http://localhost:8080/someURL/invokeMethod which should invoke the rest service controller method and make a LOGGER.info() out to the console. When I launch the page at http://localhost:8081/ the AJAX on the client should make a call to the service however I am unable to get a log out to the console from the controller getLogOut() method on the rest service controller.
Rest Service Controller
package com.demo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/someURL")
public class ServiceController {
private static final Logger LOGGER = LoggerFactory.getLogger(SearchController.class.getName());
#GetMapping("/invokeMethod")
public void getLogOut() {
LOGGER.info("/n/n/n/n/n***************LOGGER IN getLogOut************");
}
}
Web Client index.html :
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
$.ajax({
url: "http://localhost:8080/someURL/invokeMethod"
});
});
</script>
</head>
<body>
<h1>Load Page to make ajax call</h1>
</body>
</html>
Web Client Controller :
package com.demo.Web.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
#RequestMapping
public class WebController {
private static final Logger LOGGER = LoggerFactory.getLogger(WebController.class.getName());
#GetMapping("/")
public String viewPage() {
LOGGER.info("\n\n\n**********In Index**********");
return "index";
}
}
Not getting errors from the console. Just simply getting from console :
0:0:0:0:0:0:0:1 - - [06/Oct/2019:21:21:28 -0500] "GET /someURL/invokeMethod HTTP/1.1" 200 722
Not sure what above from console is trying tell me.
Stacktrace (should be complete)
[INFO]~2019-10-07-09.53.05.313CDT~~~~~~ c.e.h.RestServiceApplication Starting RestServiceApplication on 5CG9107HK6 with PID 162992
[INFO]~2019-10-07-09.53.05.317CDT~~~~~~ c.e.h.RestServiceApplication No active profile set, falling back to default profiles: default
[INFO]~2019-10-07-09.53.05.543CDT~~~~~~ o.s.b.d.e.DevToolsPropertyDefaultsPostProcessor Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
[INFO]~2019-10-07-09.53.05.544CDT~~~~~~ o.s.b.d.e.DevToolsPropertyDefaultsPostProcessor For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
[INFO]~2019-10-07-09.53.06.685CDT~~~~~~ o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker Bean 'org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration' of type [org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration$$EnhancerBySpringCGLIB$$6e19173b] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[INFO]~2019-10-07-09.53.06.692CDT~~~~~~ o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker Bean 'objectPostProcessor' of type [org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[INFO]~2019-10-07-09.53.06.694CDT~~~~~~ o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker Bean 'org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler#cb177' of type [org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[INFO]~2019-10-07-09.53.06.701CDT~~~~~~ o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker Bean 'org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration' of type [org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration$$EnhancerBySpringCGLIB$$92edb9ed] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[INFO]~2019-10-07-09.53.06.706CDT~~~~~~ o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker Bean 'org.springframework.security.config.annotation.method.configuration.Jsr250MetadataSourceConfiguration' of type [org.springframework.security.config.annotation.method.configuration.Jsr250MetadataSourceConfiguration$$EnhancerBySpringCGLIB$$b842d203] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[INFO]~2019-10-07-09.53.06.707CDT~~~~~~ o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker Bean 'jsr250MethodSecurityMetadataSource' of type [org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[INFO]~2019-10-07-09.53.06.708CDT~~~~~~ o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker Bean 'methodSecurityMetadataSource' of type [org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[INFO]~2019-10-07-09.53.07.074CDT~~~~~~ o.s.b.w.e.t.TomcatWebServer Tomcat initialized with port(s): 8080 (http)
[INFO]~2019-10-07-09.53.07.092CDT~~~~~~ o.a.c.h.Http11NioProtocol Initializing ProtocolHandler ["http-nio-8080"]
[INFO]~2019-10-07-09.53.07.104CDT~~~~~~ o.a.c.c.StandardService Starting service [Tomcat]
[INFO]~2019-10-07-09.53.07.105CDT~~~~~~ o.a.c.core.StandardEngine Starting Servlet engine: [Apache Tomcat/9.0.26]
[INFO]~2019-10-07-09.53.07.225CDT~~~~~~ o.a.c.c.C.[.[.[/] Initializing Spring embedded WebApplicationContext
[INFO]~2019-10-07-09.53.07.225CDT~~~~~~ o.s.w.c.ContextLoader Root WebApplicationContext: initialization completed in 1681 ms
[INFO]~2019-10-07-09.53.07.962CDT~~~~~~ n.r.s.b.l.a.LogbackAccessContext Configured the Logback-access: context=[default], config=[classpath:logback-access.xml]
[INFO]~2019-10-07-09.53.08.093CDT~~~~~~ o.s.s.c.ThreadPoolTaskExecutor Initializing ExecutorService 'applicationTaskExecutor'
[INFO]~2019-10-07-09.53.08.334CDT~~~~~~ o.s.s.w.DefaultSecurityFilterChain Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#1c48ba2, org.springframework.security.web.context.SecurityContextPersistenceFilter#c57134, org.springframework.security.web.header.HeaderWriterFilter#1248239, org.springframework.security.web.csrf.CsrfFilter#183a2cf, org.springframework.security.web.authentication.logout.LogoutFilter#159fde7, com.edwardjones.framework.security.spring.wam.WamAuthenticationFilter#146ee78, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#261552, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter#e0f96e, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter#300ae4, org.springframework.security.web.authentication.www.BasicAuthenticationFilter#44ff58, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#1de5909, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#15d7b9c, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#df7a4d, org.springframework.security.web.session.SessionManagementFilter#348fee, org.springframework.security.web.access.ExceptionTranslationFilter#13719ad, org.springframework.security.web.access.ExceptionTranslationFilter#b78775, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#1f01a41]
[INFO]~2019-10-07-09.53.08.807CDT~~~~~~ o.s.s.c.ThreadPoolTaskScheduler Initializing ExecutorService 'taskScheduler'
[INFO]~2019-10-07-09.53.08.836CDT~~~~~~ o.s.b.d.a.OptionalLiveReloadServer LiveReload server is running on port 35729
[INFO]~2019-10-07-09.53.08.843CDT~~~~~~ o.s.b.a.e.w.EndpointLinksResolver Exposing 2 endpoint(s) beneath base path '/actuator'
[INFO]~2019-10-07-09.53.08.889CDT~~~~~~ o.a.c.h.Http11NioProtocol Starting ProtocolHandler ["http-nio-8080"]
[INFO]~2019-10-07-09.53.08.996CDT~~~~~~ o.s.b.w.e.t.TomcatWebServer Tomcat started on port(s): 8080 (http) with context path ''
[INFO]~2019-10-07-09.53.09.001CDT~~~~~~ c.e.h.RestServiceApplication Started RestServiceApplication in 3.959 seconds (JVM running for 4.804)
[INFO]~2019-10-07-09.53.39.001CDT~~~~~~ o.a.c.c.C.[.[.[/] Initializing Spring DispatcherServlet 'dispatcherServlet'
[INFO]~2019-10-07-09.53.39.001CDT~~~~~~ o.s.w.s.DispatcherServlet Initializing Servlet 'dispatcherServlet'
[INFO]~2019-10-07-09.53.39.008CDT~~~~~~ o.s.w.s.DispatcherServlet Completed initialization in 7 ms
0:0:0:0:0:0:0:1 - - [07/Oct/2019:09:53:39 -0500] "GET /someURL/invokeMethod HTTP/1.1" 200 722