如何将含MongoDB操作的Spring Boot App1作为依赖引入App2?
我将操作MongoDB的Spring Boot App1打包为Jar作为依赖引入App2,调用其基于MongoRepository的Service方法时,App2启动报错,提示找不到PersonRepo类型的Bean。尝试过@Autowired注入App1的PersonService失败,后来通过配置类手动创建Service的Bean,仍无法解决Repo缺失问题。
错误日志
. ____ _ __ _ _ /\/ ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.7.7) 2023-01-06 22:51:27.472 INFO 13892 --- [ main] com.example.apptwo.ApptwoApplication : Starting ApptwoApplication using Java 17.0.1 on HP with PID 13892 (C:\Users\saurabh\Desktop\apptwo\apptwo\target\classes started by saurabh in C:\Users\saurabh\Desktop\apptwo\apptwo) 2023-01-06 22:51:27.475 INFO 13892 --- [ main] com.example.apptwo.ApptwoApplication : No active profile set, falling back to 1 default profile: "default" 2023-01-06 22:51:27.926 INFO 13892 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data MongoDB repositories in DEFAULT mode. 2023-01-06 22:51:27.939 INFO 13892 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 8 ms. Found 0 MongoDB repository interfaces. 2023-01-06 22:51:28.178 WARN 13892 --- [ main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'appTwoService': Unsatisfied dependency expressed through field 'service'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'appone': Unsatisfied dependency expressed through field 'repo'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.appone.repo.PersonRepo' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} 2023-01-06 22:51:28.191 INFO 13892 --- [ main] ConditionEvaluationReportLoggingListener : Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled. 2023-01-06 22:51:28.217 ERROR 13892 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPLICATION FAILED TO START *************************** Description: Field repo in com.example.appone.service.PersonService required a bean of type 'com.example.appone.repo.PersonRepo' that could not be found. The injection point has the following annotations: - @org.springframework.beans.factory.annotation.Autowired(required=true) Action: Consider defining a bean of type 'com.example.appone.repo.PersonRepo' in your configuration.
相关代码
App1代码
实体类 Person.java
package com.example.appone.entity; import lombok.ToString; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "test1") @ToString public class Person { @Id private String _id; private String name; private String email; }
Repository接口 PersonRepo.java
package com.example.appone.repo; import com.example.appone.entity.Person; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; @Repository public interface PersonRepo extends MongoRepository<Person, String> { }
Service类 PersonService.java
package com.example.appone.service; import com.example.appone.entity.Person; import com.example.appone.repo.PersonRepo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.util.List; @Component public class PersonService implements CommandLineRunner { @Autowired private PersonRepo repo; public List<Person> getAllTest() { return repo.findAll(); } @Override public void run(String... args) throws Exception { System.out.println(getAllTest()); } }
配置文件 application.properties
spring.data.mongodb.uri=mongodb://localhost:27017/DB1 spring.data.mongodb.database=DB1
App2代码
配置类 AppOneConfig.java
package com.example.apptwo.service; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.example.appone.service.PersonService; @Configuration public class AppOneConfig { @Bean public PersonService appone() { return new PersonService(); } }
Service类 AppTwoService.java
package com.example.apptwo.service; import com.example.appone.service.PersonService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class AppTwoService implements CommandLineRunner { @Autowired private PersonService service; @Override public void run(String... args) throws Exception { System.out.println(service.getAllTest()); } }
POM文件 pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.7</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>apptwo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>apptwo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.example</groupId> <artifactId>appone</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
解决方法
问题根源
- Spring Boot默认只扫描启动类所在包及其子包的组件,App2不会自动扫描依赖包App1中的Repository和Service。
- 手动
new PersonService()的方式绕过了Spring的依赖注入机制,导致PersonRepo无法被自动注入。
具体修复步骤
1. 扩展App2的扫描范围
修改App2的启动类,添加@EnableMongoRepositories指定扫描App1的Repository包,同时用@ComponentScan扫描App1的组件:
package com.example.apptwo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication // 同时扫描App1和App2的Repository @EnableMongoRepositories(basePackages = {"com.example.appone.repo", "com.example.apptwo.repo"}) // 同时扫描App1和App2的所有Spring组件 @ComponentScan(basePackages = {"com.example.appone", "com.example.apptwo"}) public class ApptwoApplication { public static void main(String[] args) { SpringApplication.run(ApptwoApplication.class, args); } }
2. 删除手动创建Bean的配置类
删除AppOneConfig类,因为@ComponentScan会自动扫描到App1中被@Component注解的PersonService,Spring会自动创建Bean并完成PersonRepo的注入。
3. 配置App2的MongoDB连接
在App2的application.properties中添加MongoDB配置(可复用App1的配置):
spring.data.mongodb.uri=mongodb://localhost:27017/DB1 spring.data.mongodb.database=DB1
4. 修正App1的打包方式
App1默认打包成可执行Jar,作为依赖引入时会有类加载问题,需修改App1的POM,生成普通Jar用于依赖:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> <configuration> <classifier>exec</classifier> </configuration> </execution> </executions> </plugin> </plugins> </build>
修改后执行mvn clean package,会生成两个Jar:appone-0.0.1-SNAPSHOT.jar(普通Jar,用于App2依赖)和appone-0.0.1-SNAPSHOT-exec.jar(可执行Jar)。
5. 更新依赖并重启App2
重新安装App1到本地仓库(mvn install),然后更新App2的依赖,启动App2即可正常运行。
内容的提问来源于stack exchange,提问作者Saurabh Vishwakarma

