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!
A good practice is to explicitly import a @Configuration class with the @Import annotation and add the @ComponentScan annotation to that configuration class to auto-scan only the package of that class. This way, we have clean boundaries between the packages of our application.
@Component and @ComponentScan are for different purposes. @Component indicates that a class might be a candidate for creating a bean. It's like putting a hand up. @ComponentScan is searching packages for Components.
One of the most important annotations in spring is @ComponentScan which is used along with the @Configuration annotation to specify the packages that we want to be scanned. @ComponentScan without arguments tells Spring to scan the current package and all of its sub-packages.
@EnableAutoConfiguration : enable Spring Boot's auto-configuration mechanism. @ComponentScan : enable @Component scan on the package where the application is located (see the best practices) @Configuration : allow to register extra beans in the context or import additional configuration classes.
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?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With