I'm trying to configure my spring security application.
I want to create my own UserDetailsService.
For that i do something like this:
public class ApplicationUserService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return this.someUser();
}
}
I got 2 ways to add this UserService to Spring Security
Add it to configuration class. Something like this:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
#Override
protected UserDetailsService userDetailsService() {
return applicationUserService;
}
}
Or add annotaion #Component, or #Service on my class.
Everything is working fine when i chose only 1 way, but i got an question: why when i trying to use both variants (add #Service and add #Bean to config) nothing is working?
I got no exceptions, error or something like this in console:
2021-09-11 17:26:16.755 INFO 15819 --- [ main] com.example.test.TestApplication : Starting TestApplication using Java 16.0.2 on aleksander-MS-7A71 with PID 15819 (/home/aleksander/programming/java/4fun/test/target/classes started by aleksander in /home/aleksander/programming/java/4fun/test)
2021-09-11 17:26:16.756 INFO 15819 --- [ main] com.example.test.TestApplication : No active profile set, falling back to default profiles: default
2021-09-11 17:26:17.402 INFO 15819 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-09-11 17:26:17.409 INFO 15819 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-09-11 17:26:17.409 INFO 15819 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.52]
2021-09-11 17:26:17.442 INFO 15819 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-09-11 17:26:17.442 INFO 15819 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 630 ms
2021-09-11 17:26:17.555 INFO 15819 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#6981f8f3, org.springframework.security.web.context.SecurityContextPersistenceFilter#38bb9d7a, org.springframework.security.web.header.HeaderWriterFilter#62db3891, org.springframework.security.web.authentication.logout.LogoutFilter#48528634, org.springframework.security.web.authentication.www.BasicAuthenticationFilter#80bfdc6, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#78d6447a, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#5e65afb6, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#623dcf2a, org.springframework.security.web.session.SessionManagementFilter#2819c460, org.springframework.security.web.access.ExceptionTranslationFilter#6f49d153, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#60bbacfc]
2021-09-11 17:26:17.676 INFO 15819 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-09-11 17:26:17.682 INFO 15819 --- [ main] com.example.test.TestApplication : Started TestApplication in 1.215 seconds (JVM running for 1.794)
The way you've described the question, the application will definitely throw an exception unless you've defined the bean preference.
First case:
Basically, UserDetailsService is an interface and you've provided the implementation of it by declaring the bean as
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
#Override
protected UserDetailsService userDetailsService() {
return new ApplicationUserService();
}
}
Second case: You want to check the behaviour by declaring another bean using #Service or #Component annotation as following
#Service
public class ApplicationUserService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new UserDetails();
}
}
If you try to use the above cases together, it won't work. The case is very simple you are providing two beans of type UserDetailsService to the spring container and hence it won't be able to identify which one it should use.
If you want to check the behaviour with both the cases you've to set the priority for beans, so in that case you can mark one of the bean with #Primary annotation.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Primary
#Bean
#Override
protected UserDetailsService userDetailsService() {
return new ApplicationUserService();
}
}
Related
I have this controller:
#RestController
public class NumbersController {
#PreAuthorize("hasRole('ROLE_ONE')")
#GetMapping("/one")
private String one(){
return "This is one.";
}
#PreAuthorize("hasRole('ROLE_TWO')")
#GetMapping("/two")
private String two(){
return "This is two.";
}
}
And this security configuration:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends GlobalMethodSecurityConfiguration {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
auth
.inMemoryAuthentication()
.withUser("user").password(encoder.encode("password")).roles("ONE");
auth
.inMemoryAuthentication()
.withUser("user2").password(encoder.encode("password2")).roles("TWO");
}
}
And while running both of my users can access both of the resources. What I want is only for user to be able to access /one and only for user2 to access /two.
I also tried using #Secured("ONE") with the same result.
Console output:
2021-01-14 16:10:20.026 INFO 4376 --- [ main] security.security.SecurityApplication : Starting SecurityApplication on Ivan-PC with PID 4376 (D:\Z\security\target\classes started by Ivan in D:\Z\security)
2021-01-14 16:10:20.041 INFO 4376 --- [ main] security.security.SecurityApplication : No active profile set, falling back to default profiles: default
2021-01-14 16:10:24.363 INFO 4376 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-01-14 16:10:24.378 INFO 4376 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-01-14 16:10:24.378 INFO 4376 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.41]
2021-01-14 16:10:24.565 INFO 4376 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-01-14 16:10:24.565 INFO 4376 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 4321 ms
2021-01-14 16:10:25.221 INFO 4376 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2021-01-14 16:10:25.860 INFO 4376 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#57a48985, org.springframework.security.web.context.SecurityContextPersistenceFilter#17740dae, org.springframework.security.web.header.HeaderWriterFilter#14bf57b2, org.springframework.security.web.csrf.CsrfFilter#48535004, org.springframework.security.web.authentication.logout.LogoutFilter#3cee53dc, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#67440de6, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter#35835e65, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter#1ab6718, org.springframework.security.web.authentication.www.BasicAuthenticationFilter#7ce7e83c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#345cf395, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#7144655b, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#3910fe11, org.springframework.security.web.session.SessionManagementFilter#14379273, org.springframework.security.web.access.ExceptionTranslationFilter#cfbc8e8, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#49293b43]
2021-01-14 16:10:25.969 INFO 4376 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-01-14 16:10:25.985 INFO 4376 --- [ main] security.security.SecurityApplication : Started SecurityApplication in 6.771 seconds (JVM running for 8.031)
2021-01-14 16:10:29.847 INFO 4376 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-01-14 16:10:29.848 INFO 4376 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2021-01-14 16:10:29.870 INFO 4376 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 22 ms
The requests are made with Postman to http://localhost:8080/two and using the authorization fields.
Check this if it helps,
We can configure multiple HttpSecurity instances just as we can have multiple blocks. The key is to extend the WebSecurityConfigurerAdapter multiple times. For example, the following is an example of having a different configuration for URL’s that start with /api/.
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Bean
public UserDetailsService userDetailsService() throws Exception {
// ensure the passwords are encoded properly
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());
return manager;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeRequests(authorize -> authorize
.anyRequest().hasRole("ADMIN")
)
.httpBasic(withDefaults());
}
}
#Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(withDefaults());
}
}
}
Configure Authentication as normal
Create an instance of WebSecurityConfigurerAdapter that contains #Order to specify which WebSecurityConfigurerAdapter should be considered first.
The http.antMatcher states that this HttpSecurity will only be applicable to URLs that start with /api/
Create another instance of WebSecurityConfigurerAdapter.
If the URL does not start with /api/ this configuration will be used.
This configuration is considered after ApiWebSecurityConfigurationAdapter since it has an #Order value after 1 (no #Order defaults to last).
Try out this in your SecurityConfig class
#EnableGlobalMethodSecurity(
prePostEnabled = true,
jsr250Enabled = true)
The prePostEnabled property enables Spring Security pre/post annotations
The jsr250Enabled property allows us to use the #RoleAllowed annotation
I am new to Spring and Spring Boot and I played around with different ways how to resolve Beans. In my example I've got a Bean that should always be a singleton. What surprises me is that there seems to be a way where this bean is resolved as, I assume, "prototype".
Could anyone explain to me why it's not a singleton when it is resolved in the signature of the method showSingletonBeans?
#SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
#Service("stackSingletonBean")
// #Scope("singleton")
class MySingletonBean {
init {
println("Created MySingletonBean " + this.hashCode())
}
}
#RestController
class MyController {
#Autowired
// #Qualifier("singletonBean")
lateinit var memberSingletonBean: MySingletonBean
#Autowired
lateinit var singeltonFactory: ObjectFactory<MySingletonBean>
fun buildSingleton() : MySingletonBean {
return singeltonFactory.`object`
}
#Lookup
fun getSingletonInstance() : MySingletonBean? {
return null
}
#GetMapping("/")
fun showSingletonBeans(#Autowired stackSingletonBean: MySingletonBean) {
println("member " + memberSingletonBean.hashCode() )
println("stack " + stackSingletonBean.hashCode())
println("lookup:" + getSingletonInstance().hashCode())
println("factory: " + buildSingleton().hashCode())
}
}
The log looks like that:
2020-08-13 18:44:32.604 INFO 172175 --- [ main] com.example.demo.DemoApplicationKt : No active profile set, falling back to default profiles: default
2020-08-13 18:44:33.118 INFO 172175 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-08-13 18:44:33.124 INFO 172175 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-08-13 18:44:33.124 INFO 172175 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.37]
2020-08-13 18:44:33.164 INFO 172175 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-08-13 18:44:33.164 INFO 172175 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 528 ms
Created MySingletonBean 1747702724
2020-08-13 18:44:33.286 INFO 172175 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-08-13 18:44:33.372 INFO 172175 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-08-13 18:44:33.379 INFO 172175 --- [ main] com.example.demo.DemoApplicationKt : Started DemoApplicationKt in 1.011 seconds (JVM running for 1.24)
2020-08-13 18:44:37.341 INFO 172175 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-08-13 18:44:37.341 INFO 172175 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2020-08-13 18:44:37.344 INFO 172175 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 3 ms
Created MySingletonBean 562566586
member 1747702724
stack 562566586
lookup:1747702724
factory: 1747702724
Created MySingletonBean 389331797
member 1747702724
stack 389331797
lookup:1747702724
factory: 1747702724
Resolving controller method parameters is actually quite different mechanism. It has nothing to do with dependency injection and the #Autowired annotation: the annotation can be removed and it won't change the behavior.
Although #Autowired can technically be declared on individual method or constructor parameters since Spring Framework 5.0, most parts of the framework ignore such declarations. The only part of the core Spring Framework that actively supports autowired parameters is the JUnit Jupiter support in the spring-test module (see the TestContext framework reference documentation for details).
https://docs.spring.io/
In your case, the stackSingletonBean is instantiated by the ModelAttributeMethodArgumentResolver. It's not aware of the #Service annotation nor of its scope: it simply uses the default constructor on each request.
Model attributes are sourced from the model, or created using a default constructor and then added to the model.
Note that use of #ModelAttribute is optional — for example, to set its attributes. By default, any argument that is not a simple value type( as determined by BeanUtils#isSimpleProperty) and is not resolved by any other argument resolver is treated as if it were annotated with #ModelAttribute. Web on Reactive Stack
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);
}
}
The CamelContextStartedEvent is called twice for the same camel context (camel-1). The issue might be the way I register the EventNotifier. You can reproduce the issue with Spring Initializr with Spring Boot 1.5.14, Spring Boot Camel Starter 2.21.1 and Spring Boot Web Starter.
See the logs:
2018-07-06 11:04:41.104 INFO 19092 --- [ main] o.a.camel.spring.SpringCamelContext : Apache Camel 2.21.1 (CamelContext: camel-1) is starting
2018-07-06 11:04:41.106 INFO 19092 --- [ main] o.a.c.m.ManagedManagementStrategy : JMX is enabled
2018-07-06 11:04:41.191 INFO 19092 --- [ main] o.a.camel.spring.SpringCamelContext : StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2018-07-06 11:04:41.193 INFO 19092 --- [ main] o.a.camel.spring.boot.RoutesCollector : Starting CamelMainRunController to ensure the main thread keeps running
2018-07-06 11:04:41.193 INFO 19092 --- [ main] o.a.camel.spring.SpringCamelContext : Total 0 routes, of which 0 are started
2018-07-06 11:04:41.194 INFO 19092 --- [ main] o.a.camel.spring.SpringCamelContext : Apache Camel 2.21.1 (CamelContext: camel-1) started in 0.090 seconds
2018-07-06 11:04:41.195 INFO 19092 --- [ main] c.e.bug.service.StartupEventNotifier : CamelContextStartedEvent for SpringCamelContext(camel-1) with spring id application:11223
2018-07-06 11:04:41.195 INFO 19092 --- [ main] c.e.bug.service.StartupEventNotifier : CamelContextStartedEvent for SpringCamelContext(camel-1) with spring id application:11223
2018-07-06 11:04:41.216 INFO 19092 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 11223 (http)
2018-07-06 11:04:41.221 INFO 19092 --- [ main] com.example.bug.BugApplication : Started BugApplication in 4.684 seconds (JVM running for 6.773)
The service that initializes the EventNotifier:
#Service
public class SchedulerService {
private final CamelContext camelContext;
private final StartupEventNotifier startupEventNotifier;
public SchedulerService(CamelContext camelContext, StartupEventNotifier startupEventNotifier) {
this.camelContext = camelContext;
this.startupEventNotifier = startupEventNotifier;
}
#PostConstruct
public void init() {
camelContext.getManagementStrategy().addEventNotifier(startupEventNotifier);
}
}
The EventNotifier:
#Component
public class StartupEventNotifier extends EventNotifierSupport {
private static final Logger logger = LoggerFactory.getLogger(StartupEventNotifier.class);
#Override
public void notify(EventObject event) throws Exception {
if (event instanceof CamelContextStartedEvent) {
logger.info("CamelContextStartedEvent for {}", event.getSource());
}
}
#Override
public boolean isEnabled(EventObject event) {
if (event instanceof CamelContextStartedEvent) {
return true;
}
return false;
}
}
application.yml:
camel:
springboot:
main-run-controller: true
server:
port: 11223
It is called twice, because it is registered twice. Once by you and once by Apache Camel. EventNotifier is registered automatically, if is found in Registry. Since your StartupEventNotifier is annotated as Component, it is part of Registry and Apache Camel registered it during CamelContext startup (You can see it in CamelAutoConfiguration line 424).
You have four options:
Remove your custom registration from SchedulerService.
Remove #Component annotation from StartupEventNotifier and register it with with camelContext.getManagementStrategy().addEventNotifier(new StartupEventNotifier())
Add duplicity check to your SchedulerService. Something like:
if (!context.getManagementStrategy().getEventNotifiers().contains(startupEventNotifier)){
context.getManagementStrategy().addEventNotifier(startupEventNotifier);
}
Register EventNotifier in #PostConstruct of RouteBuilder. It will be registered before automatic discovery is started and then it will be skipped in CamelAutoConfiguration (See line 422)
I have a basic API setup with Spring MVC Rest as following.
public abstract class AbstractApi implements InitializingBean {
#Autowired
protected ValidatorFactory validatorFactory;
/* ... */
#Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(validatorFactory);
}
}
#Controller
#RequestMapping("books")
public class BookApi extends AbstractApi {
private final BookRepository bookRepository;
#Autowired
public BookApi(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<Book> getBooks() {
return new ResponseEntity<>(bookRepository.findAll(), HttpStatus.OK);
}
}
The server returns 404 - Not Found if I send GET /books request with above configuration.
But, if I make AbstractApi un-implement InitializingBean, it works fine. Also, annotating #PostConstruct to afterPropertiesSet() instead of implementing InitializingBean works.
Why is Spring #Controller API not working when implementing InitializingBean?
Your code looks correct. I tested in on my own and everything works as expected. What I'm suggesting is to remove #Autowired ValidatorFactory in AbstractApi class just for testing purpose. Implementing InitializingBean is not related to the request mapping handler mapping. My working code is:
public abstract class AbstractApi implements InitializingBean {
#Override
public void afterPropertiesSet() throws Exception {
System.out.println("after properties set");
}
}
and my controller
#Controller
#RequestMapping("books")
public class BooksController extends AbstractApi {
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<String> getBooks() {
return new ResponseEntity<>("", HttpStatus.OK);
}
}
and starting log from tomcat:
2016-01-28 16:48:03.141 INFO 2238 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2016-01-28 16:48:03.317 INFO 2238 --- [ main] o.apache.catalina.core.StandardService : Starting service Tomcat
2016-01-28 16:48:03.329 INFO 2238 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.0.23
2016-01-28 16:48:03.405 INFO 2238 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2016-01-28 16:48:03.405 INFO 2238 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1815 ms
2016-01-28 16:48:03.512 INFO 2238 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2016-01-28 16:48:03.515 INFO 2238 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
after properties set
Your current #RequestMapping("books") path is incorrectly specified. When running locally on port 8080 looks like http://localhost:8080books
and should be #RequestMapping("/books") - http://localhost:8080/books
give that a try.