为什么我的 Spring @Autowired 字段为 null?

为什么我的 Spring @Autowired 字段为 null?

技术背景

在Spring应用开发中,@Autowired 注解是实现依赖注入的常用方式,它可以自动将所需的Bean注入到相应的字段中。然而,有时候会遇到使用 @Autowired 注解的字段为 null 的情况,这会导致 NullPointerException 异常,影响程序的正常运行。

实现步骤

1. 检查对象创建方式

Spring IoC容器负责管理Bean的生命周期和依赖注入。如果使用 new 关键字手动创建对象,Spring无法对其进行配置和注入依赖。

错误示例

@Controller
public class MileageFeeController {    
    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        MileageFeeCalculator calc = new MileageFeeCalculator(); // 手动创建对象
        return calc.mileageCharge(miles);
    }
}

正确示例:使用 @Autowired 注解让Spring自动注入对象。

@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。

@Service
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile());
    }
}

3. 配置组件扫描

确保Spring能够扫描到标记为Bean的类。可以使用 @ComponentScan 注解指定要扫描的包。

@Configuration
@ComponentScan(basePackages = "com.chrylis.example")
public class AppConfig {
    // 配置类
}

4. 检查注解导入的包

确保使用的注解(如 @Autowired@Inject@Service 等)导入的是正确的包。

import org.springframework.beans.factory.annotation.Autowired; // 正确的导入

5. 避免在构造函数中使用未初始化的依赖

在构造函数中,@Autowired 注解的字段可能还未初始化。可以使用 @PostConstruct 注解在Bean初始化后执行一些操作。

@Component
public class MyComponent {
    @Autowired
    ComponentDAO dao;

    public MyComponent() {
        // dao 在这里为 null
    }

    @PostConstruct
    public void init() {
        // dao 在这里已初始化
    }
}

核心代码

示例代码结构

  • MileageFeeController:控制器类,处理HTTP请求。
  • MileageFeeCalculator:服务类,计算里程费用。
  • MileageRateService:服务类,提供每英里的费率。
// MileageFeeController.java
@Controller
public class MileageFeeController {

    @Autowired
    private MileageFeeCalculator calc;

    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        return calc.mileageCharge(miles);
    }
}

// MileageFeeCalculator.java
@Service
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile());
    }
}

// MileageRateService.java
@Service
public class MileageRateService {
    public float ratePerMile() {
        return 0.565f;
    }
}

最佳实践

  • 使用构造函数注入:优先使用构造函数注入,而不是字段注入,这样可以提高代码的可测试性和可读性。
@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上下文。

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
    // 测试代码
}

3. 静态字段注入问题

不要对静态字段使用 @Autowired 注解,因为静态字段在类加载时就已经初始化,Spring无法对其进行注入。