I'm trying to create multiple dispatcherservlet ("/rest/", "/jsp/", "mq/*"). Springboot initialises only one dispatcherservlet.
i have two bean creation methods for DispatcherServletRegistrationBean in order to create two dispatcher servlet. I has set the order and precedence level for both beans. When i start the application, only one of the dispatcherservlet is getting Initialised. In this case, it is "restDisparcher" (please look for console output). what should i do in order to setup multiple dispatcher servlet.
#SpringBootConfiguration
public class AppConfiguration {
#Bean
#Primary
public DispatcherServletRegistrationBean dispatcherServletRegistrationBeanRest() {
DispatcherServlet dispatcherServlet = new DispatcherServlet(new AnnotationConfigServletWebApplicationContext("com.michael.springsecurityentitlement.rest"));
DispatcherServletRegistrationBean dispatcher = new DispatcherServletRegistrationBean(dispatcherServlet , "/rest/*");
dispatcher.setName("restDispatcher");
dispatcher.setLoadOnStartup(1);
dispatcher.setOrder(Ordered.HIGHEST_PRECEDENCE);
return dispatcher;
}
#Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBeanJsp() {
DispatcherServlet dispatcherServlet = new DispatcherServlet(new AnnotationConfigServletWebApplicationContext("com.michael.springsecurityentitlement.jsp"));
DispatcherServletRegistrationBean dispatcher = new DispatcherServletRegistrationBean(dispatcherServlet , "/jsp/*");
dispatcher.setName("restDispatcher");
dispatcher.setLoadOnStartup(1);
dispatcher.setOrder(Ordered.HIGHEST_PRECEDENCE);
return dispatcher;
}
#Bean
public TomcatServletWebServerFactory servletWebServerFactory() {
TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory("/custom",8081);
return tomcatServletWebServerFactory;
}
public static void main(String[] args) {
SpringApplication app = new SpringApplication(AppConfiguration.class);
app.run(args);
}
}
Console:
2021-03-04 12:09:39.015 INFO 1108 --- [ main] c.m.s.AppConfiguration : Starting AppConfiguration using Java 15.0.2 on ASINTHs-MacBook-Pro.local with PID 1108 (/Users/asinth/git/spring-security-entitlement/target/classes started by asinth in /Users/asinth/git/spring-security-entitlement)
2021-03-04 12:09:39.030 INFO 1108 --- [ main] c.m.s.AppConfiguration : No active profile set, falling back to default profiles: default
2021-03-04 12:09:39.539 INFO 1108 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8081 (http)
2021-03-04 12:09:39.551 INFO 1108 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-03-04 12:09:39.551 INFO 1108 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.43]
2021-03-04 12:09:39.704 INFO 1108 --- [ main] o.a.c.c.C.[.[localhost].[/custom] : Initializing Spring embedded WebApplicationContext
2021-03-04 12:09:39.704 INFO 1108 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 635 ms
2021-03-04 12:09:39.806 INFO 1108 --- [ main] o.s.boot.web.servlet.RegistrationBean : Servlet restDispatcher was not registered (possibly already registered?)
2021-03-04 12:09:39.880 INFO 1108 --- [ main] o.a.c.c.C.[.[localhost].[/custom] : Initializing Spring DispatcherServlet 'restDispatcher'
2021-03-04 12:09:39.881 INFO 1108 --- [ main] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'restDispatcher'
2021-03-04 12:09:40.131 INFO 1108 --- [ main] o.s.web.servlet.DispatcherServlet : Completed initialization in 250 ms
2021-03-04 12:09:40.136 INFO 1108 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path '/custom'
2021-03-04 12:09:40.155 INFO 1108 --- [ main] c.m.s.AppConfiguration : Started AppConfiguration in 2.093 seconds (JVM running for 3.252)
I think the problem is your dispatcher name. Set different names for different Dispatcher and it should works. Maybe like this :
#SpringBootApplication
public class AppConfiguration {
#Bean
#Primary
public DispatcherServletRegistrationBean dispatcherServletRegistrationBeanRest() {
DispatcherServlet dispatcherServlet = new DispatcherServlet(new AnnotationConfigServletWebApplicationContext("com.michael.springsecurityentitlement.rest"));
DispatcherServletRegistrationBean dispatcher = new DispatcherServletRegistrationBean(dispatcherServlet , "/rest/*");
dispatcher.setName("restDispatcher");
dispatcher.setLoadOnStartup(1);
dispatcher.setOrder(Ordered.HIGHEST_PRECEDENCE);
return dispatcher;
}
#Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBeanJsp() {
DispatcherServlet dispatcherServlet = new DispatcherServlet(new AnnotationConfigServletWebApplicationContext("com.michael.springsecurityentitlement.jsp"));
DispatcherServletRegistrationBean dispatcher = new DispatcherServletRegistrationBean(dispatcherServlet , "/jsp/*");
dispatcher.setName("jspDispatcher");
dispatcher.setLoadOnStartup(1);
dispatcher.setOrder(Ordered.HIGHEST_PRECEDENCE);
return dispatcher;
}
#Bean
public TomcatServletWebServerFactory servletWebServerFactory() {
TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory("/custom",8081);
return tomcatServletWebServerFactory;
}
public static void main(String[] args) {
SpringApplication.run(AppConfiguration.class, args);
}
}
Related
I prepared very simple REST APi.
I am trying to do requests with postman but i get 401 Unauthorized. No matter what kind request it is. I have Windows 11 system, Java 11, Postman Version 9.8.2
Postman:
application.properties file:
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/students
spring.datasource.username=
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.show-sql=true
Spring Application:
#SpringBootApplication(exclude = {UserDetailsServiceAutoConfiguration.class})
public class StudentsmanagerApplication {
public static void main(String[] args) {
SpringApplication.run(StudentsmanagerApplication.class, args);
}
Controller class:
#RestController
#RequestMapping("/student")
public class StudentController {
private StudentService studentService;
#GetMapping
public ResponseEntity<List<Student>> getAllStudents() {
List<Student> students = studentService.findAllStudent();
return new ResponseEntity<>(students, HttpStatus.OK);
}
#GetMapping("find/{id}")
public ResponseEntity<Student> getAllStudentsById(#PathVariable Long id) {
Student student = studentService.findStudentById(id);
return new ResponseEntity<>(student, HttpStatus.OK);
}
#PostMapping
public ResponseEntity<Student> addEmployee(#RequestBody Student student) {
Student newStudent = studentService.addStudent(student);
return new ResponseEntity<>(newStudent, HttpStatus.CREATED);
}
#PutMapping("/{id}")
public ResponseEntity<Student> updateStudent(#PathVariable Long id, #RequestBody Student
student) {
Student updatedStudent = studentService.updateStudent(id, student);
return new ResponseEntity<>(updatedStudent, HttpStatus.UPGRADE_REQUIRED);
}
#DeleteMapping("/{id}")
public ResponseEntity<?> deleteStudent(#PathVariable Long id) {
studentService.deleteStudent(id);
return new ResponseEntity<>(HttpStatus.OK);
}
Service class:
#Service
public class StudentService {
private StudentRepository studentRepository;
public Student addStudent(Student student) {
student.setStudentCode(UUID.randomUUID().toString());
return studentRepository.save(student);
}
public List<Student> findAllStudent() {
return studentRepository.findAll();
}
public Student updateStudent(Long id, Student student) {
Student studentById = studentRepository
.findById(id).orElseThrow(() -> new StudentNotFoundException("Student by id
" + " doesn't Exist"));
studentById.setName(student.getName());
studentById.setLastName(student.getLastName());
studentById.setEmail(student.getEmail());
studentById.setPhone(student.getPhone());
return studentRepository.save(studentById);
}
public Student findStudentById(Long id) {
return studentRepository
.findById(id).orElseThrow(() -> new StudentNotFoundException("Student
doesn't exist "));
}
public void deleteStudent(Long id) {
studentRepository.deleteById(id);
}
Spring logs:
2022-01-08 10:53:19.661 INFO 20120 --- [ main] p.s.s.StudentsmanagerApplication : Starting StudentsmanagerApplication using Java 11.0.13 on LAPTOP-9F9MO24J with PID 20120 (C:\Users\mkord\IdeaProjects\studentsmanager\target\classes started by mkord in C:\Users\mkord\IdeaProjects\studentsmanager)
2022-01-08 10:53:19.661 INFO 20120 --- [ main] p.s.s.StudentsmanagerApplication : No active profile set, falling back to default profiles: default
2022-01-08 10:53:20.539 INFO 20120 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2022-01-08 10:53:20.596 INFO 20120 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 50 ms. Found 1 JPA repository interfaces.
2022-01-08 10:53:21.262 INFO 20120 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2022-01-08 10:53:21.278 INFO 20120 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-01-08 10:53:21.278 INFO 20120 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.56]
2022-01-08 10:53:21.422 INFO 20120 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-01-08 10:53:21.422 INFO 20120 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1684 ms
2022-01-08 10:53:21.662 INFO 20120 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2022-01-08 10:53:21.703 INFO 20120 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.6.3.Final
2022-01-08 10:53:21.856 INFO 20120 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2022-01-08 10:53:21.976 INFO 20120 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-01-08 10:53:22.336 INFO 20120 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2022-01-08 10:53:22.352 INFO 20120 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
2022-01-08 10:53:22.968 INFO 20120 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2022-01-08 10:53:22.984 INFO 20120 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2022-01-08 10:53:23.032 WARN 20120 --- [ 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
2022-01-08 10:53:23.824 INFO 20120 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#36931450, org.springframework.security.web.context.SecurityContextPersistenceFilter#451a4187, org.springframework.security.web.header.HeaderWriterFilter#6db04a6, org.springframework.security.web.csrf.CsrfFilter#630c3af3, org.springframework.security.web.authentication.logout.LogoutFilter#4866e0a7, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#66d44581, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter#4ac0d49, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter#74919649, org.springframework.security.web.authentication.www.BasicAuthenticationFilter#2ea4e762, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#5c215642, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#1317ac2c, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#7d07e04e, org.springframework.security.web.session.SessionManagementFilter#426913c4, org.springframework.security.web.access.ExceptionTranslationFilter#38197e82, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#5a07ae2f]
2022-01-08 10:53:23.914 INFO 20120 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2022-01-08 10:53:23.930 INFO 20120 --- [ main] p.s.s.StudentsmanagerApplication : Started StudentsmanagerApplication in 4.851 seconds (JVM running for 6.309)
2022-01-08 10:59:13.709 INFO 20120 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-01-08 10:59:13.709 INFO 20120 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-01-08 10:59:13.709 INFO 20120 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms
2022-01-08 10:59:13.941 WARN 20120 --- [nio-8080-exec-2] o.a.c.util.SessionIdGeneratorBase : Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [184] milliseconds.
Thank You in advance for any suggestion
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
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 GET method in my REST API
This is my controller class
#RestController
#RequestMapping("/api")
#Validated
public class ApiController {
#Autowired
private ApiService service;
#GetMapping(path = { "/check/{type}", "/check" },
produces= {MediaType.APPLICATION_JSON_UTF8_VALUE,MediaType.APPLICATION_XML_VALUE})
public List<Myobject> check(#MyConstraint #RequestParam("email") final List<String> emails,
#PathVariable(name = "type", required = false) final String type) {
final String subscriptiontype = StringUtils.isEmpty(type) ? "all" : type;
final List<Myobject> objects= service.check(emails, subscriptiontype);
return objects;
}
}
I'm trying to write a Unit Test For this controller class
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = SubscriberApiController.class)
public class ApiControllerTest {
private MockMvc mvc;
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
private ApiService service;
#Before
public void setUp() {
// mvc = MockMvcBuilders.standaloneSetup(new HandlerController()).build();
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void getIndex() throws Exception {
mvc.perform(get("/my-service/api/check?email=someone#someone.com").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
But it's showing me mapping error No mapping found for HTTP request with URI
2018-04-20 11:51:23.409 INFO 12968 --- [ main] c.a.d.s.service.api.ApiControllerTest : No active profile set, falling back to default profiles: default
2018-04-20 11:51:23.432 INFO 12968 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Refreshing org.springframework.web.context.support.GenericWebApplicationContext#43f02ef2: startup date [Fri Apr 20 11:51:23 BST 2018]; root of context hierarchy
2018-04-20 11:51:24.847 INFO 12968 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/check/{type} || /api/check],methods=[GET],produces=[application/json;charset=UTF-8 || application/xml]}" onto public com.aerlingus.dei.subscriber.service.api.model.Subscriptions com.aerlingus.dei.subscriber.service.api.controller.SubscriberApiController.checkSubscriptionForUSersByType(java.util.List<java.lang.String>,java.lang.String)
2018-04-20 11:51:24.852 INFO 12968 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-04-20 11:51:24.852 INFO 12968 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-04-20 11:51:24.944 INFO 12968 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for #ControllerAdvice: org.springframework.web.context.support.GenericWebApplicationContext#43f02ef2: startup date [Fri Apr 20 11:51:23 BST 2018]; root of context hierarchy
2018-04-20 11:51:24.993 INFO 12968 --- [ main] .m.m.a.ExceptionHandlerExceptionResolver : Detected #ExceptionHandler methods in badRequestExceptionHandler
2018-04-20 11:35:47.040 INFO 30276 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for #ControllerAdvice: org.springframework.web.context.support.GenericWebApplicationContext#43f02ef2: startup date [Fri Apr 20 11:35:44 BST 2018]; root of context hierarchy
2018-04-20 11:35:47.110 INFO 30276 --- [ main] .m.m.a.ExceptionHandlerExceptionResolver : Detected #ExceptionHandler methods in badRequestExceptionHandler
2018-04-20 11:35:47.454 INFO 30276 --- [ main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring FrameworkServlet ''
2018-04-20 11:35:47.454 INFO 30276 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : FrameworkServlet '': initialization started
2018-04-20 11:35:47.475 INFO 30276 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : FrameworkServlet '': initialization completed in 21 ms
2018-04-20 11:35:47.625 INFO 30276 --- [ main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 2147483647
2018-04-20 11:35:47.626 INFO 30276 --- [ main] d.s.w.p.DocumentationPluginsBootstrapper : Context refreshed
2018-04-20 11:35:47.656 INFO 30276 --- [ main] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
2018-04-20 11:35:47.702 INFO 30276 --- [ main] s.d.s.w.s.ApiListingReferenceScanner : Scanning for api listing references
2018-04-20 11:35:47.938 INFO 30276 --- [ main] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: checkSubscriptionForUSersByTypeUsingGET_1
2018-04-20 11:35:47.960 INFO 30276 --- [ main] c.a.d.s.s.api.HandlerControllerTest : Started HandlerControllerTest in 3.653 seconds (JVM running for 4.761)
2018-04-20 11:35:47.983 INFO 30276 --- [ main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring FrameworkServlet ''
2018-04-20 11:35:47.983 INFO 30276 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : FrameworkServlet '': initialization started
2018-04-20 11:35:47.985 INFO 30276 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : FrameworkServlet '': initialization completed in 2 ms
2018-04-20 11:35:48.031 WARN 30276 --- [ main] o.s.web.servlet.PageNotFound : No mapping found for HTTP request with URI [/my-service/api/check] in DispatcherServlet with name ''
2018-04-20 11:35:48.050 INFO 30276 --- [ Thread-2] o.s.w.c.s.GenericWebApplicationContext : Closing org.springframework.web.context.support.GenericWebApplicationContext#43f02ef2: startup date [Fri Apr 20 11:35:44 BST 2018]; root of context hierarchy
2018-04-20 11:35:48.052 INFO 30276 --- [ Thread-2] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 2147483647
My ciontext root is not getting mapped to servlet
server.servlet.contextPath=/my-service
if I change test to
mvc.perform(get("/api/check?email=someone#someone.com").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
It works fine
Update
http://localhost:8080/my-service/api/check?email=someone#someone.com
Could some one tell me What's wrong here ???
The MockMvc is a mock and it is not load context path from the server configuration. You must add contextPath to your mockMvc object:
mvc.perform(get("/my-service/api/check?email=someone#someone.com")
.contextPath("/my-service")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
The configuration in wrong. Replace:
server.servlet.contextPath=/my-service
with:
server.contextPath=/my-service