7

I've just started a new spring project, and this time I want to do things "right". In the last project I had issues with multiple registering of certain classes because of multiple @ComponentScan annotations. (i.e. all service classes got registered twice)

Basically I'm using the following layout:

WebAppInitializer:

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { WebMvcConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

}

RootConfig:

@Configuration
@ComponentScan
public class RootConfig {
    /* ... */
}

WebMvcConfig:

@EnableWebMvc
@ComponentScan
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    /* ... */
}

DatabaseConfig:

@Configuration
@EnableJpaRepositories("my.base.class.path")
public class DataConfig {
    /* ... */
}

The first basic question is: Which class should scan for which classes/annotations?

Should only the WebMvcConfig scan for @Controller classes? Which one should scan for @Service and for @Configuration and for @Component?

The second question is: Or should I simply use packages to narrow down the scan path?

Example:

rootpackage
rootpackage.config.RootConfig
rootpackage.config.DatabaseConfig
rootpackage.mvc.WebMvcConfig

and then place all @Controller classes under rootpackage.mvc.*?

The third question is: Is it more common to let RootConfig scan for DatabaseConfig? Or should I place DatabaseConfig inside the getRootConfigClasses method of WebAppInitializer class?

The last question is: In a multi module project: How do you organize those things?

Example: If I chose the way I described in question two, I could say, that every module of the app will in fact consist of a few different modules. Let's say, I want to create a module X which will have a @Service class and a few @Controller classes, I could put them in them in different packages. Like this:

Maven Module X Service

rootpackage.services.x.XService
rootpackage.services.x.XServiceImpl

Maven Module X Controller

rootpackage.mvc.controller.x.X1Controller
rootpackage.mvc.controller.x.X2Controller
rootpackage.mvc.controller.x.X3Controller

And if you'd suggest this way, then: Where to place models and repositories (for accessing the database)? Should I create a new module for each of those?

Thanks in advance!

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

2 Answers2

2

I think I found now a pretty nice project layout:

rootpackage.web.WebAppInitializer (see below)
rootpackage.web.SecurityWebAppInitializer (creates "springSecurityFilterChain")
rootpackage.web.WebMvcConfig (scans for everything in its own package and subpackages)
rootpackage.web.SecurityConfig (Spring Security config)

rootpackage.web.moduleA.SomeAController
rootpackage.web.moduleB.SomeBController

rootpackage.service.ServiceConfig (scans for everything in its own package and subpackages)
rootpackage.service.moduleA.AService
rootpackage.service.moduleA.AServiceImpl
rootpackage.service.moduleB.BService
rootpackage.service.moduleB.BServiceImpl
rootpackage.service.security.UserDetailsServiceImpl (for Spring Security)

rootpackage.model.DatabaseConfig (scans for everything in its own package and subpackages)
rootpackage.model.moduleA.SomeADomainObject
rootpackage.model.moduleB.SomeBDomainObject

WebAppInitializer:

@Order(2)
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {
            SecurityConfig.class,
            ServiceConfig.class,
            DatabaseConfig.class
        };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { WebMvcConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

}

SecurityWebAppInitializer:

@Order(1) // should always be registered in first place (= before WebAppInitializer)
public class SecurityWebAppInitializer extends AbstractSecurityWebApplicationInitializer {
    /* ... */
}

WebMvcConfig:

@Configuration
@EnableWebMvc
@ComponentScan // scans for everything in its own package and subpackages
               // so it only finds SomeAController.class and SomeBController.class
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    /* ... */
}

SecurityConfig:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /* ... */
}

ServiceConfig:

@Configuration
@ComponentScan // scans for everything in its own package and subpackages
               // so it only finds AServiceImpl.class and BServiceImpl.class
public class ServiceConfig {
    /* ... */   
}

I put some "System.out.println" inside the constructor of all those classes in order to see how often they get registered/loaded. Each constructor is getting executed exactly once!

What do you think about this? Any improvements?

Benjamin M
  • 23,599
  • 32
  • 121
  • 201
  • Just a note to Spring Security. You’re initializing `springSecurityFilterChain` (SSFC) in the web module, that’s okay for now. The problem is that you can have just one SSFC in the application, i.e. you cannot register another SSFC in some other module (child context). I’ve came across it recently in one project that I wanna modularize. Unfortunately, I don’t know how to solve this problem. – Jakub Jirutka Jan 15 '14 at 00:04
  • Sorry, I don't really understand... `:-(` What's the better solution to have more flexibility? Should I place one for each module? I haven't worked that much with Spring Security for now. – Benjamin M Jan 15 '14 at 00:09
  • Your current configuration is fine and reasonable. I’m just saying that you will have a problem, if you add another (dispatcher) servlet context (i.e. child context), maybe with REST API, that will be secured with Spring Security as well. The reason is that you cannot register another `springSecurityFilterChain` inside the second servlet context, there can be just one instance. The only workaround I know about is to register SSFC in the root context, then any servlet context (and the root context itself) can use it, but that’s quite limiting and inflexible. Just be aware of this limitation. – Jakub Jirutka Jan 15 '14 at 00:27
  • Okay, now I understand a bit better `:-)`. I think the current project layout will not get into such problems, because it's a REST-only API. No HTML, no JSP, nothing else, only JSON (and maybe XML if someone forces us to...). – Benjamin M Jan 15 '14 at 00:34
  • I’m glad. :) If you’ll use Spring Security in just one context as now, then there’s nothing to worry about. – Jakub Jirutka Jan 15 '14 at 00:38
  • Just struggling with this myself. I ended up not using packages, but telling the scan exactly what to include/exclude. ie, for my ServiceConfig, I used @ComponentScan(value = "com.mypackage", excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class), @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Configuration.class) } ) My MvcConfig just scans com.mypackage.controllers – Bal Mar 11 '14 at 22:14
  • I'm curious about your implementation. According to [AbstractSecurityWebApplicationInitializer](http://docs.spring.io/autorepo/docs/spring-security/4.0.0.M1/apidocs/org/springframework/security/web/context/AbstractSecurityWebApplicationInitializer.html), it suggests that the `AbstractDispatcherServletInitializer` should be added first. But your `@Order` goes the other way. – end-user Jan 20 '15 at 15:17
1

With an XML based config you would typically have 2 contexts one parent context that will load all your business services, database config, repositories, domain objects, etc. and a web context for loading controllers, etc.

Both should use packages to ensure they don't try to load the same beans twice. You specify both to the ContextLoaderListener to get the ApplicationContext created.

The web application context is aware of the parent (not the other way round) and will search the parent for any beans not found in it's own context. This means your controllers can access your services.

I've not done this in Java config, but I'm presuming the approach is the same

Romski
  • 1,912
  • 1
  • 12
  • 27
  • 1
    Thank you. Yes, it should be the same as it's in XML config. It's still the same framework, but just another way to describe the configuration. ... Now I just need to know, where exactly to draw the line at those packages: You used two times **etc.** `;-)`. Could you provide a link to some good explanation or just explain it yourself in some more detail? – Benjamin M Jan 14 '14 at 14:28
  • It really depends on how you organise your code, but you can provide a list of packages to each if the components are scattered. I'll add more detail later – Romski Jan 14 '14 at 22:24
  • I created a news answer to this with my own solution. It seems to work as expected so far. Maybe you want to comment on it :) – Benjamin M Jan 14 '14 at 23:07
  • Yeah, it’s almost the same as with XML, Java-based configuration is just more flexible. – Jakub Jirutka Jan 15 '14 at 00:40