You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

继承的@Autowired字段在构造函数执行时未初始化?原因与解决方法

父类@Autowired字段在子类构造函数中为空的原因及解决办法

问题场景

子类UserQuestionCommentRestControllerTest继承自IntegrationTestContext,在构造函数中调用父类的testUtil获取JWT token时触发空指针异常(NPE)。父类的testUtil字段用@Autowired修饰,但子类构造执行时该字段并未完成初始化。

异常原因

Spring Bean的初始化顺序是先执行构造函数,再完成依赖注入:

  1. 创建子类对象时,会先调用父类的构造函数(子类构造默认隐含super()调用);
  2. 整个对象构造完成后,Spring才会通过反射为@Autowired标注的字段赋值;
  3. 因此子类构造函数执行时,父类的testUtil还是null,调用其方法自然会抛出NPE。你误以为的“反射初始化父类字段”是在构造函数执行完毕后才会发生的操作。

解决方法

方法1:使用@PostConstruct初始化token

将token的初始化逻辑放到被@PostConstruct标注的方法中,该方法会在Spring完成所有依赖注入、Bean初始化完成后自动执行。注意需要去掉token的final修饰符:

public class UserQuestionCommentRestControllerTest extends IntegrationTestContext {
    private static final String testUsername = "mickey_m";
    private static final String testPassword = "mickey";
    private String token; // 移除final修饰符
    private static final String BASE_URI = "/api/v1/user/question-comments/";
    private static final String BASE_SCRIPT_PATH = "/sql/UserQuestionCommentRestControllerTest/";
    private ExpectationTester expectationTester;

    @PostConstruct
    public void initToken() throws Exception {
        token = testUtil.getToken(testUsername, testPassword);
    }
}

方法2:改用构造函数注入(推荐)

让父类通过构造函数接收依赖,子类继承后也通过构造函数获取所需依赖。这种方式下,依赖会在对象构造阶段就被注入,从根源避免空指针问题:

父类调整:

@SpringBootTest
@AutoConfigureMockMvc
@Testcontainers
public class IntegrationTestContext {

    protected final MockMvc mockMvc;
    protected final ObjectMapper objectMapper;
    protected final TestUtil testUtil;
    @PersistenceContext
    protected EntityManager entityManager;

    // 构造函数注入依赖
    public IntegrationTestContext(MockMvc mockMvc, ObjectMapper objectMapper, TestUtil testUtil) {
        this.mockMvc = mockMvc;
        this.objectMapper = objectMapper;
        this.testUtil = testUtil;
    }
}

子类调整:

public class UserQuestionCommentRestControllerTest extends IntegrationTestContext {
    private static final String testUsername = "mickey_m";
    private static final String testPassword = "mickey";
    private final String token;
    private static final String BASE_URI = "/api/v1/user/question-comments/";
    private static final String BASE_SCRIPT_PATH = "/sql/UserQuestionCommentRestControllerTest/";
    private ExpectationTester expectationTester;

    // 调用父类构造注入依赖,直接使用testUtil初始化token
    public UserQuestionCommentRestControllerTest(MockMvc mockMvc, ObjectMapper objectMapper, TestUtil testUtil) throws Exception {
        super(mockMvc, objectMapper, testUtil);
        token = testUtil.getToken(testUsername, testPassword);
    }
}

Spring会自动将对应的Bean注入到构造函数中,此时testUtil在构造函数执行时已经是实例化完成的对象。

方法3:使用JUnit的@BeforeEach(适配测试场景)

由于这是测试类,可借助JUnit的@BeforeEach注解标注初始化方法,该方法会在每个测试方法执行前运行,此时Spring已完成所有依赖注入:

public class UserQuestionCommentRestControllerTest extends IntegrationTestContext {
    private static final String testUsername = "mickey_m";
    private static final String testPassword = "mickey";
    private String token;
    private static final String BASE_URI = "/api/v1/user/question-comments/";
    private static final String BASE_SCRIPT_PATH = "/sql/UserQuestionCommentRestControllerTest/";
    private ExpectationTester expectationTester;

    @BeforeEach
    public void setup() throws Exception {
        token = testUtil.getToken(testUsername, testPassword);
    }
}

这种方式适合测试场景,能确保每次测试前token都是有效的最新值。


内容的提问来源于stack exchange,提问作者Sergey Zolotarev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 02:55:33