Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring root and servlet context with Java config

I'm running a Spring application in a Servlet 3.0+ environment to programmatically configure the servlet context using all Java configuration. My question (with details below): how is a project structured to support component scanning for both root and web application contexts without duplicating component initialization?

As I understand it, there are two contexts in which to register Spring beans. First, the root context is where non-servlet-related components go. For example batch jobs, DAOs, etc. Second, the servlet context is where servlet-related components go such as controllers, filters, etc.

I've implemented a WebApplicationInitializer to register these two contexts just as the JavaDoc in WebApplicationInitializer specifies with a AppConfig.class and DispatcherConfig.class.

I want both to automatically find their respective components so I've added @ComponentScan to both (which is resulting in my Hibernate entities being initiated twice). Spring finds these components by scanning some specified base package. Does that mean I need to put all my DAO-related objects in a separate package from the controllers? If so, that'd be quite inconvenient as I generally like to package by functionality (as opposed to type).

Code snippets...

WebApplicationInitializer:

public class AppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) throws ServletException {
        // Create the 'root' Spring application context
        AnnotationConfigWebApplicationContext rootContext =
                new AnnotationConfigWebApplicationContext();
        rootContext.register(AppConfig.class);

        // Manage the lifecycle of the root application context
        container.addListener(new ContextLoaderListener(rootContext));

        // Create the dispatcher servlet's Spring application context
        AnnotationConfigWebApplicationContext dispatcherContext =
                new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(WebAppConfig.class);

        // Register and map the dispatcher servlet
        ServletRegistration.Dynamic dispatcher =
                container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
    }

}

AppConfig:

@Configuration
@ComponentScan
public class AppConfig {
}

WebAppConfig:

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
@ComponentScan(basePackageClasses = AppConfig.class)
public class WebAppConfig extends WebMvcConfigurerAdapter {
}
like image 858
Corey Avatar asked Dec 19 '22 07:12

Corey


2 Answers

Just define what you want to scan for in each configuration. In general your root configuration should scan for everything but @Controllers and your web configuration should only detect @Controllers.

You can accomplish this by using the includeFilters and excludeFilters attributes of the @ComponentScan annotation. When using include filters, in this case, you also need to disable using the default filters by setting useDefaultFilters to false.

@Configuration
@ComponentScan(excludeFilters={@Filter(org.springframework.stereotype.Controller.class)})
public class AppConfig {}

And for your WebConfig

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
@ComponentScan(basePackageClasses = AppConfig.class, useDefaultFilters=false, includeFilters={@Filter(org.springframework.stereotype.Controller.class)})
public class WebAppConfig extends WebMvcConfigurerAdapter {}

In addition, you need to import the @Filter annotation:

import static org.springframework.context.annotation.ComponentScan.Filter;
like image 67
M. Deinum Avatar answered Dec 30 '22 17:12

M. Deinum


The short answer is: Yes, you should define seperate component scans per context, thus modeling your project differently and extracting the DAO into a seperate name-space (package).

The longer answer is split into two parts, one regarding the servlet contexts and the other regarding modeling a project.

Regarding root and servlet contexts: You have defined correctly the different responsibilities of root context and servlet context. This is an understanding most developers tend to miss and it is very important.
Just to clarify a bit more on this subject, you can create one root context and several (0+) servlet contexts. All of the beans defined in your root context will be available for all servlet contexts but beans defined in each servlet context will be not be shared with other servlet contexts (different spring containers).

Now since you have multiple spring containers and you use component scan on the same packages then beans are created twice, once per container. To avoid this you can do a few things:

  1. Use one container and not two, meaning you can define only the root container or only the servlet container (I have seen many applications like this).
  2. Seperate your beans into different places and provide each container its unique component scan. remember that all of the beans defined in the root container are available to use in the servlet container (you do not need to define them twice).

Lastly, a few words regarding project modeling. I personally like to write my project in layers, thus separating my code into controllers (application layer), business logic (bl layer) and DAO (database layer). Modeling like this helps in many ways, including this root/servlet context issue you have encountered. There are tons of information regarding layered architecture, here is a quick peed: wiki-Multilayered_architecture

like image 26
Amit Avatar answered Dec 30 '22 17:12

Amit