Reputation: 5661
To learn more about multiple contexts usage in a same application I decided to create a very basic example application managing post resources and using 2 child contexts : one for Rest API and the other one for MVC.
The application is deployed in a separate Tomcat 8.5 server and is listening on port 8081.
Here's the multi-context
application structure :
1) Api package (first child context)
ApiConfig.java
@Configuration
@EnableWebMvc
@ComponentScan
public class ApiConfig {
}
Only responsible of the loading of the PostApiController
in API child context
PostApiController.java
@RestController
@AllArgsConstructor
@RequestMapping("/posts")
public class PostApiController {
private final PostService postService;
@PostMapping
public ResponseEntity<Post> createPost(@RequestBody Post post) {
return ok(postService.createPost(post.getTitle(), post.getContent()));
}
@GetMapping
public ResponseEntity<List<Post>> getAllPosts() {
return ok(postService.getAllPosts());
}
}
2) Mvc package (second sibling context)
MvcConfig.java
@Configuration
@EnableWebMvc
@ComponentScan
public class MvcConfig {
}
Only responsible of the loading of the MVC PostController
in MVC child context
PostController.java
@Controller
@AllArgsConstructor
@RequestMapping("/posts")
public class PostController {
private final PostService postService;
@GetMapping
public ModelAndView getAllPostsView() {
Map<String, Object> values = new HashMap<>();
values.put("posts", postService.getAllPosts());
return new ModelAndView("posts", values);
}
}
3) App package (root application context)
Package responsible of the initialization of the root application context (loading of common beans (PostService) sharable in every child context) and the initialization of the 2 children contexts (API and MVC).
MultiContextApplication.java
@SpringBootApplication
public class MultiContextApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(MultiContextApplication.class, args);
}
/**
* Root context initialization
*/
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MultiContextApplication.class);
}
/**
* Rest API dispatcher servlet initialization
*/
@Bean(name = "apiDispatcherServlet")
public DispatcherServlet apiDispatcherServlet(WebApplicationContext webApplicationContext) {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.setParent(webApplicationContext);
applicationContext.register(ApiConfig.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setApplicationContext(applicationContext);
return dispatcherServlet;
}
/**
* Rest API child context initialization
*/
@Bean(name = "apiServletRegistrationBean")
public ServletRegistrationBean apiServletRegistrationBean(
@Qualifier("apiDispatcherServlet") DispatcherServlet apiDispatcherServlet) {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
servletRegistrationBean.addUrlMappings("/api/*");
servletRegistrationBean.setServlet(apiDispatcherServlet);
servletRegistrationBean.setLoadOnStartup(1);
servletRegistrationBean.setName("api-servlet");
return servletRegistrationBean;
}
/**
* MVC dispatcher servlet initialization
*/
@Bean(name = "mvcDispatcherServlet")
public DispatcherServlet mvcDispatcherServlet(WebApplicationContext webApplicationContext) {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.setParent(webApplicationContext);
applicationContext.register(MvcConfig.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setApplicationContext(applicationContext);
return dispatcherServlet;
}
/**
* MVC child context initialization
*/
@Bean(name = "mvcServletRegistrationBean")
public ServletRegistrationBean mvcServletRegistrationBean(
@Qualifier("mvcDispatcherServlet") DispatcherServlet mvcDispatcherServlet) {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
servletRegistrationBean.addUrlMappings("/mvc/*");
servletRegistrationBean.setServlet(mvcDispatcherServlet);
servletRegistrationBean.setLoadOnStartup(1);
servletRegistrationBean.setName("mvc-servlet");
return servletRegistrationBean;
}
}
As we can expect if the incoming request is http://localhost:8081/multi-context/api/posts it will be treated by the apiDispatcherServlet
(urlMapping = /api/*) and if the incoming request is http://localhost:8081/multi-context/mvc/posts it will be treated the mvcDispatcherServlet
(urlMapping = /mvc/*).
The problem is I have a 404
for both cases and I really don't understand why.
GET http://localhost:8081/multi-context/api/posts
{
"timestamp": "2020-07-14T00:28:33.461+00:00",
"status": 404,
"error": "Not Found",
"message": "",
"path": "/multi-context/api/posts"
}
Output log :
2020-07-14 02:46:01.203 INFO 11120 --- [on(3)-127.0.0.1] c.i.m.app.MultiContextApplication : Starting MultiContextApplication v0.0.1-SNAPSHOT on L-5CD9474WTJ with PID 11120
2020-07-14 02:46:01.207 INFO 11120 --- [on(3)-127.0.0.1] c.i.m.app.MultiContextApplication : No active profile set, falling back to default profiles: default
2020-07-14 02:46:02.162 INFO 11120 --- [on(3)-127.0.0.1] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 919 ms
2020-07-14 02:46:02.626 INFO 11120 --- [on(3)-127.0.0.1] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-07-14 02:46:02.771 INFO 11120 --- [on(3)-127.0.0.1] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page template: index
2020-07-14 02:46:02.904 INFO 11120 --- [on(3)-127.0.0.1] c.i.m.app.MultiContextApplication : Started MultiContextApplication in 2.253 seconds (JVM running for 4.955)
2020-07-14 02:46:03.003 INFO 11120 --- [on(3)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'api-servlet'
2020-07-14 02:46:03.084 INFO 11120 --- [on(3)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Completed initialization in 81 ms
2020-07-14 02:46:03.086 INFO 11120 --- [on(3)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'mvc-servlet'
2020-07-14 02:46:03.138 INFO 11120 --- [on(3)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Completed initialization in 52 ms
2020-07-14 02:46:03.449 INFO 11120 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2020-07-14 02:46:03.451 INFO 11120 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
As we can see the api-servlet
context and the mvc-servlet
context are loaded successfully.
Maybe a conflict with the default dispatcherServlet
context but if so I have no idea the way to solve the problem.
Upvotes: 2
Views: 2791
Reputation: 5661
I finally found the solution. I just forgot to @EnableWebMvc
from the main configuration class. That's why the classes PostController
and PostApiController
was not properly loaded and thus lead to a 404
error.
AppConfig.java
@Configuration
@EnableWebMvc
public class AppConfig {
}
MultiContextApplication.java
@SpringBootApplication
@Import(AppConfig.class)
public class MultiContextApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(MultiContextApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MultiContextApplication.class);
}
@Bean
public ServletRegistrationBean apiServletRegistrationBean() {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(ApiConfig.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
servletRegistrationBean.addUrlMappings("/api/*");
servletRegistrationBean.setServlet(dispatcherServlet);
servletRegistrationBean.setLoadOnStartup(1);
servletRegistrationBean.setName("api-servlet");
return servletRegistrationBean;
}
@Bean
public ServletRegistrationBean mvcServletRegistrationBean() {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(MvcConfig.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
servletRegistrationBean.addUrlMappings("/mvc/*");
servletRegistrationBean.setServlet(dispatcherServlet);
servletRegistrationBean.setLoadOnStartup(1);
servletRegistrationBean.setName("mvc-servlet");
servletRegistrationBean.setEnabled(true);
return servletRegistrationBean;
}
}
I finally removed useless DispatcherServlet
bean registration. Only ServletRegistrationBean
are important.
Thanks for viewers. I hope this post will be helpfull for somebody.
Upvotes: 3