5

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 {
}

2 Answers 2

10

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;
Sign up to request clarification or add additional context in comments.

5 Comments

Awesome thanks a lot this solves for packaging by functionality problem as well!
AppConfig: @ComponentScan( excludeFilters={ @ComponentScan.Filter(Controller.class) } )
WebAppConfig: @ComponentScan( basePackageClasses=AppConfig.class , useDefaultFilters = false , includeFilters = { @ComponentScan.Filter(Controller.class) } )
You can add a static import for the Filter.
Won't this create two copies of every bean that AppConfig defines, one which the root context creates and another that the dispatcher context creates?
1

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

12 Comments

Packaging by layer is a bad practice imho because you loose encapsulation, you try to hide everything behind a service layer but make everything public because you have seperated everything by layer.
I disagree, it all depends on how you module your project. for example, you can supply an api and multiple implementations. Another example is that modeling correctly can produce multiple building blocks and enable you to share different parts of your code. Meaning, in this example, that you can call your service layer not only though "one set of controller" but multiple protocols (tcp, rest, thrift...) and they all use the same service building block,
You should combine both approaches.. .Your business should be structered by finctionality and eveyrthing else (web, ws, rest, jms) is an small integration layer for your functionality. You should protect your domain and with a technical layer everything is public and there goes your protection.
what is "everything is public"? If you model your project correctly then you do not loose encapsulation nor protection! The opposite is true, you gain loose coupling and finer granularity. The only thing that is public are APIs between layer.
Regarding the layered approach: I have a clear separation between all layers, each layer is even a different maven module, so in my case I will never have a singe package containing all user logic. Modeling like this forces me to separate the concerns of each layer. meaning that the controller layer is responsible for the "external" API (e.g. REST) where the service layer can be modeled differently (changes in one layer does not affect the another) and the same goes for the DAL. It also enables to re-use layers inside other applications (e.g. use the 'user' data layer in other apps)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.