23

Basically I want to split my application into 2 parts. Each part has it's own security stuff and own @Controllers. The @Services should be accessible from both parts.

So I thought, I should get 2 DispatcherServlet. One listening to /admin/* and the second listening to everything else ( / ). Each of those will have its own AnnotationConfigWebApplicationContext so I can have separate component scan for the @Controllers.

And because Spring Boot provides one DispatcherServlet listening on / out of the box, I thought, I can just add a second one:

@Configuration
public class MyConfig {
    @Bean(name="myDS")
    public DispatcherServlet myDS(ApplicationContext applicationContext) {
        AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
        webContext.setParent(applicationContext);
        webContext.register(MyConfig2.class);
        // webContext.refresh();
        return new DispatcherServlet(webContext);
    }

    @Bean
    public ServletRegistrationBean mySRB(@Qualifier("myDS") DispatcherServlet dispatcherServlet) {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet);
        servletRegistrationBean.addUrlMappings("/admin/*");
        servletRegistrationBean.setName("adminServlet");
        return servletRegistrationBean;
    }
}

The MyConfig2 class, only has @Configuration and @ComponentScan. Within the same package is a @Controller.

When starting the application, I can see, that the second servlet mapping is getting registered, but the @Controller is not. Additionally I can now access all @Controllers from / and /admin.


Any idea how I can get this working?

Benjamin M
  • 23,599
  • 32
  • 121
  • 201

1 Answers1

49

I got it working somehow!

Here's my Package Layout:

test.foo.
         FooConfig.java
         FooController.java
test.bar.
         BarConfig.java
         BarController.java
test.app.
         Application.java
         MyService.java
src/main/resources/application.properties

Application.java:

@SpringBootApplication(exclude=DispatcherServletAutoConfiguration.class)
public class Application {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
    @Bean
    public ServletRegistrationBean foo() {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();   
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(FooConfig.class);
        dispatcherServlet.setApplicationContext(applicationContext);
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/foo/*");
        servletRegistrationBean.setName("foo");
        return servletRegistrationBean;
    }
    @Bean
    public ServletRegistrationBean bar() {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(BarConfig.class);
        dispatcherServlet.setApplicationContext(applicationContext);
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/bar/*");
        servletRegistrationBean.setName("bar");
        return servletRegistrationBean;
    }
}
  • The exclude does prevent Spring Boot from creating its own DispatcherServlet with / mapping. You can remove that line, if you want that mapping or define your own.
  • You can add servletRegistrationBean.setLoadOnStartup(1) if you want to have your Servlets initialized on application start. Else it will wait for the first request for that servlet.
  • It's important to set servletRegistrationBean.setName(...), else the servlets will override each other.

FooConfig.java & BarConfig.java:

@Configuration @ComponentScan @EnableWebMvc
public class FooConfig { }
  • @EnableWebMvc will enable the component scan. Without it, it won't find the @Controller class.

The Controller and Service code is not important. You just have to know, that if you have @RequestMapping("/foo") inside FooController, the request must be GET /foo/foo because the Servlet's URL mapping is /foo/*. It's not possible to call the URL GET /foo because the Servlet URL mapping needs a / at the end of its path (in other words: GET /foo will look for a Servlet with / mapping!), though @RequestMapping("") must be called via GET /foo/. And of course it was not possible to use /foo or /foo* as Servlet mapping (or I just did not find the correct settings for that)

Scope: The Controllers can't see each other, though it's not possible to @Autowired them in each other. Also the Service can't @Autowired any of the Controllers. But the Controllers can @Autowired the Service.

Though it's a classical parent child context hierarchy.

The only "bad" thing is, that we need @EnableMvcConfig and don't get the auto configured sugar from Spring boot within the context. The parent context is getting auto configured. I put some database stuff within the application.properties and did a query inside MyService which got called by FooController and it worked flawlessly! :)

I hope this may help some people!

Benjamin M
  • 23,599
  • 32
  • 121
  • 201
  • Thank you for the answer. Really helps. I have one more challenge to overcome. How can I authenticate+authorize(DB based) the /foo endpoints but only authenticate the /bar endpoints? I have a configuration from here[0]. http://stackoverflow.com/questions/36909226/how-to-configure-waffle-in-spring-using-java-configuration – JHS Sep 03 '16 at 18:53
  • 1
    Thanks Benjamin, I was struggling with same problem and found that @EnableMvcConfig is required after reading your answer. – jatanp Oct 11 '16 at 19:24
  • Thanks for that thorough explanation! It helped me to solve two different problems. I mean it's common to self-answer with "this works:" plus the code bits without explanation... – sjngm May 25 '17 at 17:13
  • why we need FooConfig.java because it is empty class. – DHARMENDRA SINGH May 21 '18 at 04:45
  • @DHARMENDRASINGH FooConfig.java has annotations that will cause Spring to scan for components, enable Spring Web MVC, etc. – Joshua Davis Jan 25 '19 at 22:15
  • I received the following error: `Parameter 1 of constructor in org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration required a bean of type 'org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath' that could not be found.` I had to add also this configuration: `@Bean public DispatcherServletPath dispatcherServletPath1() { return () -> "/"; }` – xonya Mar 17 '19 at 18:34
  • @Benjamin Aren't you supposed to specify the exact `Foo` or `Bar` `@Controller`'s packages in their respective `ConfigFoo` and `ConfigBar` `@ComponentScan` parts? By the way, there is no `@EnableMvcConfig` but rather `@EnableWebMvc`. – Malvon Dec 10 '19 at 01:41
  • setting the name was crucial for me! – hocikto Jun 12 '20 at 09:44