user1364743
user1364743

Reputation: 5661

How to configure several child context in a Spring Boot 2 application?

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

Answers (1)

user1364743
user1364743

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

Related Questions