Spring Boot多模块项目自定义注解的@ComponentScan失效问题
项目结构
基于Gradle构建的多模块项目结构如下:
Root project 'mp-search' +--- Project ':analyzer' +--- Project ':common' | \--- Project ':common:es-model' ...
模块职责
:analyzer:Spring Boot主应用模块- 包含
@SpringBootApplication注解标注的启动类,集成Web、Feign等全部业务依赖
- 包含
:common:es-model:Spring Data Elasticsearch模型与仓储层公共模块- 仅引入
spring-boot-starter-data-elasticsearch依赖,无Spring Boot启动类
- 仅引入
公共模块代码实现
com.example.esmodel.document包下定义了ES相关的实体、仓储与配置类:
- ES实体类
Document,位于com.example.esmodel.document.model包:
package com.example.esmodel.document.model; //imports @org.springframework.data.elasticsearch.annotations.Document(indexName = "document") public class Document { @Id private String documentId; @Field private String content; // Getter + Setter + Constructor }
- 仓储接口
DocumentRepository,位于com.example.esmodel.document.repository包:
package com.example.esmodel.document.repository; // imports @Repository public interface DocumentRepository extends ElasticsearchRepository<Document, String> { }
- 配置类
DocumentConfiguration,位于com.example.esmodel.document.configuration包,通过@ComponentScan指定扫描路径:
package com.example.esmodel.document.configuration; // Imports @Configuration @ComponentScan(basePackages = "com.example.esmodel.document") public class DocumentConfiguration { }
- 自定义启用注解
@EnableDocumentModel,位于com.example.esmodel.document包,通过@Import导入配置类简化引入:
package com.example.esmodel.document; //Imports @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(DocumentConfiguration.class) public @interface EnableDocumentModel { }
问题现象
主模块启动类上测试了三种配置引入方式:
package com.example.analyzer; import com.example.esmodel.document.EnableDocumentModel; // Other imports @SpringBootApplication @EnableFeignClients @EnableDocumentModel // [1] //@Import(DocumentConfiguration.class) // [2] //@ComponentScan(basePackages = "com.example.esmodel.document") // [3] public class AnalyzerApplication { public static void main(String[] args) { SpringApplication.run(AnalyzerApplication.class, args); } }
测试结果:
- 方式1(添加自定义
@EnableDocumentModel注解)、方式2(直接@Import导入DocumentConfiguration)均启动失败,抛出UnsatisfiedDependencyException,提示找不到DocumentRepository类型Bean,错误信息如下:
*************************** APPLICATION FAILED TO START *************************** Description: Parameter 0 of constructor in com.example.analyzer.controller.DocumentController required a bean of type 'com.example.esmodel.document.repository.DocumentRepository' that could not be found.
- 方式3(直接在启动类上添加同路径
@ComponentScan)可正常启动,但该方式不符合公共模块封装的预期。
已验证DocumentConfiguration确实被Spring加载(曾在配置类中尝试@Import DocumentRepository接口,Spring抛出接口无法实例化的BeanInstantiationException可佐证),但配置类上的@ComponentScan未完成对应包的Bean扫描。
核心问题是Spring Data系列的Repository Bean根本不是@ComponentScan扫描注册的。
不管是Elasticsearch还是JPA的Repository接口,都是由对应模块的ImportBeanDefinitionRegistrar实现类通过动态代理生成并注册为Bean的,触发这个扫描逻辑的是@EnableElasticsearchRepositories这类专属注解,普通的@ComponentScan只能识别加了@Component、@Configuration、@Service等注解的普通类,对Repository接口完全无效。
至于把相同包路径的@ComponentScan写在启动类上就能生效,本质是沾了@SpringBootApplication的光:Spring Boot自动配置默认会触发Spring Data Repository的扫描,默认扫描范围是启动类所在包及其子包,你在启动类上额外加@ComponentScan把es-model的包纳入扫描范围后,默认的Repository扫描逻辑也会覆盖到这个路径,所以能找到Bean,这和配置类上的@ComponentScan没有任何关系。
另外DocumentRepository上加的@Repository注解没有实际作用,这个注解是给普通持久层类用的,Spring Data Repository接口不需要加这个注解。
把配置类上无效的@ComponentScan替换为ES Repository专属的扫描注解,显式指定扫描路径:
- 修改
DocumentConfiguration配置类:
package com.example.esmodel.document.configuration; import org.springframework.context.annotation.Configuration; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; @Configuration // 明确指定ES Repository接口的扫描路径 @EnableElasticsearchRepositories(basePackages = "com.example.esmodel.document.repository") // 如果后续出现实体类找不到的问题,追加下面注解指定实体扫描路径即可 // @org.springframework.boot.autoconfigure.domain.EntityScan(basePackages = "com.example.esmodel.document.model") public class DocumentConfiguration { // 如果模块内有其他普通@Componet、@Configuration类需要扫描,补回@ComponentScan即可,和Repository扫描逻辑互不冲突 // @ComponentScan(basePackages = "com.example.esmodel.document") }
- 不需要修改
@EnableDocumentModel注解的定义,主启动类上直接添加@EnableDocumentModel注解即可正常注入DocumentRepository,不需要在启动类上写任何额外的扫描配置。
内容的提问来源于stack exchange,提问作者jhueg

