【问题标题】:Right way to use @ComponentScan in multi module Java-Config Spring MVC app在多模块 Java-Config Spring MVC 应用程序中使用 @ComponentScan 的正确方法
【发布时间】:2014-02-02 01:44:47
【问题描述】:

我刚刚开始了一个新的春季项目,这次我想做“正确”的事情。在上一个项目中,由于多个 @ComponentScan 注释,我遇到了多个注册某些类的问题。 (即所有服务类都注册了两次)

基本上我使用的是以下布局:

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 {
    /* ... */
}

第一个基本问题是: 哪个类应该扫描哪些类/注解?

应该只有WebMvcConfig 扫描@Controller 类吗?哪个应该扫描@Service@Configuration@Component

第二个问题是: 或者我应该简单地使用包来缩小扫描路径?

例子:

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

然后将所有@Controller 类放在rootpackage.mvc.* 下?

第三个问题是: RootConfig扫描DatabaseConfig更常见吗?还是应该将DatabaseConfig 放在WebAppInitializer 类的getRootConfigClasses 方法中?

最后一个问题是: 在一个多模块项目中:你如何组织这些东西?

示例:如果我选择我在问题二中描述的方式,我可以说,应用程序的每个模块实际上都由几个不同的模块组成。比方说,我想创建一个模块X,它将有一个@Service 类和一些@Controller 类,我可以将它们放在不同的包中。像这样:

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

如果您建议采用这种方式,那么:在哪里放置模型和存储库(用于访问数据库)?我应该为每个模块创建一个新模块吗?

提前致谢!

【问题讨论】:

    标签: spring maven spring-mvc multi-module spring-java-config


    【解决方案1】:

    我想我现在找到了一个非常不错的项目布局:

    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 {
        /* ... */
    }
    

    安全配置:

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

    服务配置:

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

    我在所有这些类的构造函数中放置了一些“System.out.println”,以查看它们注册/加载的频率。每个构造函数只执行一次!

    您对此有何看法?有什么改进吗?

    【讨论】:

    • 只是对 Spring Security 的说明。您正在 web 模块中初始化 springSecurityFilterChain (SSFC),现在可以了。问题是应用程序中只能有一个 SSFC,即不能在其他模块(子上下文)中注册另一个 SSFC。我最近在一个我想模块化的项目中遇到了它。不幸的是,我不知道如何解决这个问题。
    • 对不起,我不太明白...:-( 有什么更好的解决方案来获得更大的灵活性?我应该为每个模块放置一个吗?我现在还没有用 Spring Security 做太多工作。
    • 您当前的配置是合理的。我只是说你会遇到问题,如果你添加另一个(调度程序)servlet 上下文(即子上下文),也许使用 REST API,它也将使用 Spring Security 进行保护。原因是您不能在第二个 servlet 上下文中注册另一个 springSecurityFilterChain,可能只有一个实例。我知道的唯一解决方法是在根上下文中注册 SSFC,然后任何 servlet 上下文(以及根上下文本身)都可以使用它,但这非常有限且不灵活。请注意此限制。
    • 好的,现在我明白了一点:-)。我认为当前的项目布局不会遇到这样的问题,因为它是一个纯 REST 的 API。没有 HTML,没有 JSP,没有别的,只有 JSON(如果有人强迫我们,也许还有 XML……)。
    • 我很高兴。 :) 如果你现在只在一个上下文中使用 Spring Security,那么就没有什么好担心的了。
    【解决方案2】:

    使用基于 XML 的配置,您通常会有 2 个上下文,一个父上下文将加载您的所有业务服务、数据库配置、存储库、域对象等,还有一个用于加载控制器等的 Web 上下文。

    两者都应该使用包来确保它们不会尝试两次加载相同的 bean。您将两者都指定给 ContextLoaderListener 以创建 ApplicationContext。

    Web 应用程序上下文知道父级(而不是相反),并将搜索父级以查找在它自己的上下文中找不到的任何 bean。这意味着您的控制器可以访问您的服务。

    我没有在 Java 配置中这样做,但我认为方法是相同的

    【讨论】:

    • 谢谢。是的,它应该与 XML 配置中的相同。它仍然是相同的框架,但只是描述配置的另一种方式。 ...现在我只需要知道,在这些包装上究竟在哪里划线:您使用了两次 etc. ;-)。您能否提供一些好的解释的链接,或者您自己更详细地解释一下?
    • 这实际上取决于您如何组织代码,但是如果组件分散,您可以为每个提供一个包列表。稍后我会添加更多细节
    • 我用自己的解决方案创建了一个新闻答案。到目前为止,它似乎按预期工作。也许您想对此发表评论:)
    • 是的,和 XML 差不多,只是基于 Java 的配置更灵活。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-25
    • 1970-01-01
    • 2019-11-16
    • 2018-08-30
    • 2015-09-29
    • 1970-01-01
    相关资源
    最近更新 更多