说实话,第一次碰到 Bean 注入失败的时候,真的挺让人抓狂的。控制台那一堆红色的报错,堆栈信息长得像天书,换谁都得懵一会儿。我当年也是,盯着屏幕发呆,怀疑人生:“我都写好了啊,明明都写好了,为什么 Spring 就是不认识我的 Bean?”
别急,咱们一起把这个问题彻底捋清楚。这不仅仅是为了修复一个 Bug,更是为了让你真正理解 Spring 的“性格”。
先看看报错长什么样
最常见的错误大概长这样:
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'userService': Injection of autowired dependencies failed;
nested exception is org.springframework.beans.factory.BeanCreationException:
Could not autowire field: com.example.mapper.UserMapper com.example.service.UserService.userMapper;
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [com.example.mapper.UserMapper] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
翻译成人话就是:Spring 在创建 UserService 的时候,想给它塞一个 UserMapper,结果在容器里找了半天,发现压根没有这个 Bean。
这就是典型的“找不到对象”现场。但为什么找不到?原因五花八门。
第一个坑:包扫描没覆盖到
这是新手最容易踩的坑,没有之一。
Spring Boot 默认只扫描启动类所在包及其子包下的组件。如果你的项目结构是这样的:
com.example
├── MyApp.java (启动类)
├── service
│ └── UserService.java
└── mapper
└── UserMapper.java
这就没毛病,service 和 mapper 都在 com.example 下面,Spring 能扫到。
但如果你的项目是这样:
com.example
├── MyApp.java (启动类)
└── service
└── UserService.java
com.other
├── mapper
│ └── UserMapper.java
这时候,UserService 注入 UserMapper 就会失败,因为 com.other 这个包根本不在扫描范围内。
解决方法很简单,在启动类上加上 @ComponentScan 注解,明确告诉 Spring 要去哪里找人:
@SpringBootApplication
@ComponentScan(basePackages = {"com.example", "com.other"})
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
或者,更粗暴一点,直接扫描根包:
@SpringBootApplication
@ComponentScan(basePackages = "com")
public class MyApp {
// ...
}
记得,@SpringBootApplication 本身就包含了 @ComponentScan,但它只扫描当前类所在的包。一旦你打破了这个约定,就得手动补上。
第二个坑:Mapper 没有加注解,或者注解放错了地方
MyBatis 的 Mapper 接口,必须被 Spring 识别成一个 Bean。通常有两种做法。
做法一:用 @Mapper 注解(推荐单个 Mapper 时用)
在每个 Mapper 接口上加上 @Mapper:
@Mapper
public interface UserMapper {
List<User> findAll();
}
这样 Spring 就能识别它了。但如果你有 100 个 Mapper 接口,每个都加一遍,太累了。
做法二:用 @MapperScan 批量扫描(项目常用)
在启动类或者配置类上,用 @MapperScan 指定一个包路径,让 Spring 自动扫描下面的所有 Mapper 接口:
@SpringBootApplication
@MapperScan("com.example.mapper")
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
注意,@MapperScan 扫描的是接口,不是实现类。MyBatis-Spring 会自动为这些接口生成代理对象,并注册到 Spring 容器中。
如果你同时用了 @Mapper 和 @MapperScan,也没关系,Spring 不会重复注册,它会去重。
第三个坑:Service 类没有加 @Service 注解
有些小伙伴觉得,Service 又不是接口,不需要注解也能注入。这是错的。
@Service、@Component、@Repository 这些都是告诉 Spring:“嘿,我是个 Bean,你要管我哦!”
如果你只写了类,没加任何注解:
public class UserService {
@Autowired
private UserMapper userMapper;
public List<User> getUserList() {
return userMapper.findAll();
}
}
Spring 根本不知道 UserService 的存在,自然也无法把它注入到 Controller 或其他地方。
加上注解:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// ...
}
顺便提一句,@Repository 是给 Mapper 接口用的吗?不是。@Repository 是给 MyBatis 的实现类用的,但通常我们用的是接口 + 代理,所以给接口加 @Mapper 就够了。不过,有些老项目或者特殊配置下,可能会看到 @Repository 加在 Mapper 上,这也没错,MyBatis-Spring 也能识别。
第四个坑:循环依赖
这个稍微复杂一点,但也很常见。
假设你的业务逻辑是:UserService 需要 OrderService,而 OrderService 又需要 UserService。这就形成了环路:
@Service
public class UserService {
@Autowired
private OrderService orderService;
// ...
}
@Service
public class OrderService {
@Autowired
private UserService userService;
// ...
}
Spring 在初始化 UserService 的时候,发现需要 OrderService,就去创建 OrderService。结果 OrderService 又需要 UserService,而 UserService 还没创建完……这就陷入了死循环,Spring 会抛出 BeanCurrentlyInCreationException。
解决办法有几个:
- 重构代码,打破循环:这是最根本的解决办法。把两个 Service 共同依赖的部分抽离出来,放到第三个 Service 里。
- 用
@Lazy注解延迟加载:在其中一个注入点上加@Lazy,让 Spring 先创建另一个 Bean。
@Service
public class UserService {
@Autowired
@Lazy
private OrderService orderService;
// ...
}
- 用
@Resource替代@Autowired:有时候@Resource能绕过某些循环依赖的检测(但不推荐依赖这个,代码可读性会变差)。
第五个坑:多数据源配置冲突
如果你项目里有多个数据源,每个数据源配了自己的 SqlSessionFactory 和 MapperScannerConfigurer,那就要小心了。
假设你有两个数据源:primary 和 secondary。
@Configuration
@MapperScan(basePackages = "com.example.primary.mapper", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {
// ...
}
@Configuration
@MapperScan(basePackages = "com.example.secondary.mapper", sqlSessionFactoryRef = "secondarySqlSessionFactory")
public class SecondaryDataSourceConfig {
// ...
}
如果你在 UserService 里注入的是 com.example.primary.mapper.UserMapper,但 @MapperScan 配错了,或者 sqlSessionFactoryRef 写错了,Spring 就找不到正确的 Mapper Bean。
排查建议:
- 检查每个
@MapperScan的basePackages是否真的包含你用的 Mapper 接口。 - 检查
sqlSessionFactoryRef是否指向了正确的SqlSessionFactoryBean。 - 确保不同数据源的 Mapper 接口不在同一个包下,否则会冲突。
第六个坑:组件没有被编译或打包
这个坑比较隐蔽。有时候你改了代码,但项目没重新编译,或者 Maven 依赖没刷新,导致旧的字节码还在运行。
检查清单:
- 确保
mvn clean install或者 IDE 的 Build 按钮点过了。 - 检查
target目录下的.class文件是不是最新的。 - 如果你用了 IDEA,试试
Invalidate Caches / Restart。
第七个坑:静态变量或静态方法注入
有些小伙伴喜欢这样写:
@Component
public class UserService {
private static UserMapper userMapper; // 错误!
@Autowired
public void setUserMapper(UserMapper userMapper) {
UserService.userMapper = userMapper; // 静态注入
}
}
这是大忌!Spring 的依赖注入机制是给实例变量注入的,静态变量在类加载时就固定了,Spring 根本管不了。
正确写法:
@Component
public class UserService {
private UserMapper userMapper; // 实例变量
@Autowired
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
}
第八个坑:接口有多个实现类,Spring 不知道该用哪个
假设你有两个 UserMapper 的实现:
@Repository
public class UserMapperImpl implements UserMapper {
// ...
}
@Repository
public class UserMapperProxy implements UserMapper {
// ...
}
然后你在 UserService 里注入:
@Autowired
private UserMapper userMapper;
Spring 会懵:你有两个 UserMapper,我该用哪个? 于是报错 NoUniqueBeanDefinitionException。
解决办法:
- 用
@Qualifier指定 Bean 名称:
@Autowired
@Qualifier("userMapperImpl")
private UserMapper userMapper;
- 用
@Primary标记首选 Bean:
@Primary
@Repository
public class UserMapperImpl implements UserMapper {
// ...
}
- 直接用接口,让 MyBatis 生成代理(推荐,避免手写实现类)。
第九个坑:测试环境没配置好
有时候主程序能跑,但单元测试就报错。这是因为测试类的扫描路径和主程序不一样。
比如,主程序的启动类在 com.example 包下,但测试类在 com.example.test 包下,而你的测试类没有加 @SpringBootTest 或者扫描路径不对。
解决办法:
确保测试类有正确的注解:
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void testGetUserList() {
// ...
}
}
或者,如果测试类在子包外,手动指定扫描包:
@SpringBootTest
@ComponentScan(basePackages = "com.example")
public class UserServiceTest {
// ...
}
第十个坑:XML 配置和注解配置混用,顺序问题
如果你还在用 XML 配置(虽然不推荐了),可能会遇到这样的问题:
<!-- applicationContext.xml -->
<bean id="userMapper" class="com.example.mapper.UserMapperImpl"/>
<bean id="userService" class="com.example.service.UserService">
<property name="userMapper" ref="userMapper"/>
</bean>
如果你在 UserService 里用了 @Autowired,而 XML 里又配了 userMapper,Spring 可能会冲突。
最佳实践: 尽量统一用注解配置,或者统一用 XML 配置,不要混用。如果非要混用,记得在 XML 里加上 @Autowired 支持:
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
总结一下
Spring Boot 注入失败,本质上是 “Spring 找不到你想要的 Bean” 或者 “找到了但不知道用哪个”。
排查思路:
- 先看注解:Service 有没有
@Service,Mapper 有没有@Mapper或@MapperScan。 - 再看包路径:启动类能扫描到这些类吗?
- 检查循环依赖:有没有两个 Service 互相注入?
- 检查多实现:有没有多个 Bean 实现了同一个接口?
- 检查数据源:多数据源配置对不对?
- 最后看编译和打包:代码是不是最新的?
记住,Spring 是个“认死理”的家伙,它只会在它扫描到的包里找 Bean。你写的类,它看不见,它就是找不到。所以,确保你的类在正确的包下,并且有正确的注解。
希望这篇指南能帮到你。如果还有问题,欢迎在评论区贴出你的代码和报错,咱们一起看。