likes
comments
collection
share

亿点点小问题:代码单元测试时发现注入的service对应bean加载为null

作者站长头像
站长
· 阅读数 6

@TOC


问题背景

模型搜索算法侧召回出现了badCase,需要对其进行问题排查,以往的人工排查流程划分了很多步骤,现在服务端需要把每一个step的返回值情况串联起来,获得最终的排查结果,流程图结构如下。 亿点点小问题:代码单元测试时发现注入的service对应bean加载为null


一、基本功能实现

经过上一篇文章的修正,重新测试代码,发现能够完全实现对应流程图的判定需求,但是中途出现了亿点点难以理解的小问题,所以导致我不得不对这个小问题继续进行深入探究。

小剧场:中途出现的bean扫描与传递问题

(1)bean加载扫描为null

在做这整个流程的单元测试的时候出现了一个难以理解的groundingService一直为null的问题。首先我这个单元测试是在test包下面的,如果通过生成实例GroundingService groundingService = new GroundingServiceImpl(); 这种方式调badCaseDetect(userInputParam);方法时,会报错groundingService为null ; 然而我通过 @Resource GroundingService groundingService; 注入了这个以后调用这个注入的groundingService就不会报错了

  @Resource 
  GroundingService groundingService;
  
  @Test
    public void testAllProgressRes() {
        GroundingDTO userInputParam = new GroundingDTO();
        userInputParam.setXXX("XXXXXX");
        userInputParam.setXXX("XXXXXX");
        userInputParam.setXXX("XXXXXX");
        // GroundingService groundingService = new GroundingServiceImpl();
        Object finalRes = groundingService.badCaseDetect(userInputParam);
        System.out.println(finalRes);
    }

对此,GPT给出了答案: 亿点点小问题:代码单元测试时发现注入的service对应bean加载为null

总结起来意思是意思是: 使用 @Resource 注解时,Spring 框架会负责实例化 GroundingService 并将其依赖的其他组件也一并初始化,但是直接实例化 GroundingServiceImpl 这种方式并不能保证 GroundingService 依赖的其他组件也被正确地初始化,如果这些组件没有被正确地初始化,那么 groundingService 就可能为 null ,所以在 Spring 中,我们通常不直接实例化服务类,而是通过 Spring 的依赖注入功能来获取服务类的实例依赖注入.

并且还需要注意,这个测试类应该包括一些基本的属性,比如组件扫描的配置,可以通过注解方式进行继承,不然也会找不到service

亿点点小问题:代码单元测试时发现注入的service对应bean加载为null 如示例中需要先继承BaseTest属性,就包括了@ComponentScan(basePackages = "XXXX")的配置 亿点点小问题:代码单元测试时发现注入的service对应bean加载为null

(2)状态转移过程上下文bean传递问题

由于状态机里面的execute方法以及每个State类里面的execute方法内部都需要调用 @Service标注的GroundingService groundingService涉及到的对应内部方法。

 abstract public FinalResult execute(GroundingService groundingService, GroundingDTO groundingDTO, FinalResult finalResult);
public FinalResult execute(GroundingService groundingService, GroundingDTO groundingDTO) {

(1)最初报错写法:用的时候再注入,所有透传的groundingService都去掉,直接在7个State里面注入@Resource GroundingService groundingService; 修改之后发现运行起来报错,出现groundingService为null的问题。 猜测可能和bean的加载和注入有关。 亿点点小问题:代码单元测试时发现注入的service对应bean加载为null

(2)投机取巧的写法:手动管理和透传groundingService(不推荐) 由于害怕运行时候还是会报出groundingService为null的问题,于是想到可以直接透传test层面的groundingServie,用this进行替代,如: finalResult = stateMachine.execute(this,groundingDTO); // 执行状态机并获取结果。这种方法虽然能解决问题,然而此方案可能并不是最佳实践,因为它将测试代码和业务逻辑紧密地耦合在一起。理想情况下,你应该能够让Spring框架负责管理所有的依赖关系,而不是在测试代码中手动管理。

//test层:
@Resource  
GroundingService groundingService; 
// ........
Object finalRes = groundingService.badCaseDetect(userInputParam);
-----------------------------------------------------------
//GroundingServiceImpl层:
@Resource
GroundingService groundingService;
finalResult = stateMachine.execute(groundingService,groundingDTO);
// 下面这样改也不会出现null的问题
 // finalResult = stateMachine.execute(this,groundingDTO); // 执行状态机并获取结果

(3)问题分析和定位,debug模式仔细分析代码,隐藏的小问题很多,下面一一列举: 【1】IOC容器里面管理的是同一个bean对象,但是我们每次手动new一个对象是不会自动注入的。其他方式可以通过配置@Bean注解或者beanFactory.getBean(name)方式获取。(这里可以顺便复习一下Spirng如何解决循环依赖问题)

之前的写法就存在问题,应该把new对象转换成注入实例: 亿点点小问题:代码单元测试时发现注入的service对应bean加载为null 【2】查看UML类图关系分析,State1、StateA2、StateA3、StateB2、StateB3、StateB4都继承了一个公共的抽象类State,抽象类标注@Component注解是没有意义的,因为它本身不能被实例化,所以要对每个子类上面加上@Component标注。 亿点点小问题:代码单元测试时发现注入的service对应bean加载为null

【3】@Component注解是不支持继承的,判断一个注解是否支持继承,要看它的底层代码,是不是加了@Inherited注解,查看后发现@Component没有加,测试了一下也确实不能。 具体测试可以参考这篇文章 blog.csdn.net/sunnyzyq/ar… 亿点点小问题:代码单元测试时发现注入的service对应bean加载为null

【4】mapperScan的配置在SpringBootAppliaction启动时候扫描生效,其他地方没必要加。

另一个问题: 为什么每次我们注入的都是@Resource GroundingService groundingService,而不直接注入GroundingServiceImpl 如果有多个类实现了这个接口,我怎么知道我要调用哪个 GPT给的答案: 亿点点小问题:代码单元测试时发现注入的service对应bean加载为null


总结

案例问题涉及到Spring框架容器对于bean的加载与管理,对于研发过程中出现的匪夷所思的小问题,推荐还是认真研究明白深层此原因,不要觉得侥幸改成功了就万事大吉,有可能这个小问题的底层是一个以前没有注意过的盲点,若不处理就很有可能会给后面埋下大坑。