为什么我的 Spring @Autowired 字段为 null?
技术背景
在Spring应用开发中,@Autowired
注解是实现依赖注入的常用方式,它可以自动将所需的Bean注入到相应的字段中。然而,有时候会遇到使用 @Autowired
注解的字段为 null
的情况,这会导致 NullPointerException
异常,影响程序的正常运行。
实现步骤
1. 检查对象创建方式
Spring IoC容器负责管理Bean的生命周期和依赖注入。如果使用 new
关键字手动创建对象,Spring无法对其进行配置和注入依赖。
错误示例:
1 2 3 4 5 6 7 8 9
| @Controller public class MileageFeeController { @RequestMapping("/mileage/{miles}") @ResponseBody public float mileageFee(@PathVariable int miles) { MileageFeeCalculator calc = new MileageFeeCalculator(); return calc.mileageCharge(miles); } }
|
正确示例:使用 @Autowired
注解让Spring自动注入对象。
1 2 3 4 5 6 7 8 9 10 11 12
| @Controller public class MileageFeeController {
@Autowired private MileageFeeCalculator calc;
@RequestMapping("/mileage/{miles}") @ResponseBody public float mileageFee(@PathVariable int miles) { return calc.mileageCharge(miles); } }
|
2. 确保类是Spring Bean
要让Spring对类进行管理和依赖注入,类必须是Spring Bean。可以使用 @Component
、@Service
、@Repository
、@Controller
等注解将类标记为Spring Bean。
1 2 3 4 5 6 7 8 9 10
| @Service public class MileageFeeCalculator {
@Autowired private MileageRateService rateService;
public float mileageCharge(final int miles) { return (miles * rateService.ratePerMile()); } }
|
3. 配置组件扫描
确保Spring能够扫描到标记为Bean的类。可以使用 @ComponentScan
注解指定要扫描的包。
1 2 3 4 5
| @Configuration @ComponentScan(basePackages = "com.chrylis.example") public class AppConfig { }
|
4. 检查注解导入的包
确保使用的注解(如 @Autowired
、@Inject
、@Service
等)导入的是正确的包。
1
| import org.springframework.beans.factory.annotation.Autowired;
|
5. 避免在构造函数中使用未初始化的依赖
在构造函数中,@Autowired
注解的字段可能还未初始化。可以使用 @PostConstruct
注解在Bean初始化后执行一些操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Component public class MyComponent { @Autowired ComponentDAO dao;
public MyComponent() { }
@PostConstruct public void init() { } }
|
核心代码
示例代码结构
MileageFeeController
:控制器类,处理HTTP请求。MileageFeeCalculator
:服务类,计算里程费用。MileageRateService
:服务类,提供每英里的费率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Controller public class MileageFeeController {
@Autowired private MileageFeeCalculator calc;
@RequestMapping("/mileage/{miles}") @ResponseBody public float mileageFee(@PathVariable int miles) { return calc.mileageCharge(miles); } }
@Service public class MileageFeeCalculator {
@Autowired private MileageRateService rateService;
public float mileageCharge(final int miles) { return (miles * rateService.ratePerMile()); } }
@Service public class MileageRateService { public float ratePerMile() { return 0.565f; } }
|
最佳实践
- 使用构造函数注入:优先使用构造函数注入,而不是字段注入,这样可以提高代码的可测试性和可读性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Service public class MileageFeeCalculator {
private final MileageRateService rateService;
@Autowired public MileageFeeCalculator(MileageRateService rateService) { this.rateService = rateService; }
public float mileageCharge(final int miles) { return (miles * rateService.ratePerMile()); } }
|
- 避免循环依赖:循环依赖会导致依赖注入失败,尽量避免在Bean之间形成循环依赖。
常见问题
1. 使用 @Configurable
注解时的问题
如果使用 @Configurable
注解让Spring对 new
创建的对象进行配置,需要进行额外的配置,如使用AspectJ编译时织入。
2. 测试类中 @Autowired
字段为 null
在测试类中,确保使用正确的注解(如 @RunWith(SpringRunner.class)
和 @SpringBootTest
)来启动Spring上下文。
1 2 3 4 5
| @RunWith(SpringRunner.class) @SpringBootTest public class MyTests { }
|
3. 静态字段注入问题
不要对静态字段使用 @Autowired
注解,因为静态字段在类加载时就已经初始化,Spring无法对其进行注入。