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

@TOC


问题背景

模型搜索算法侧召回出现了badCase,需要对其进行问题排查,以往的人工排查流程划分了很多步骤,现在服务端需要把每一个step的返回值情况串联起来,获得最终的排查结果,流程图结构如下。


一、基本功能实现

经过上一篇文章引入状态模式的3步修改,代码从原来的80行优化成了7个状态类+1个状态机+1个不超过10行的核心方法的结构,结构变得更加清晰,实现了状态封装和逻辑流程解耦,很大程度上优化了代码,赏心悦目。

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

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

(1)bean加载扫描为null

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

java 复制代码
  @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给出了答案:

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

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

如示例中需要先继承BaseTest属性,就包括了@ComponentScan(basePackages = "XXXX")的配置

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

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

java 复制代码
 abstract public FinalResult execute(GroundingService groundingService, GroundingDTO groundingDTO, FinalResult finalResult);
java 复制代码
public FinalResult execute(GroundingService groundingService, GroundingDTO groundingDTO) {

(1)最初写法:用的时候再注入,所有透传的groundingService都去掉,直接在7个State里面注入@Resource GroundingService groundingService; 修改之后发现运行起来报错,出现groundingService为null的问题。 猜测可能和bean的生命周期有关,state子类可能拿到了一个没有经过初始化的bean,所以报错值为null

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

java 复制代码
//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)更进一步写法:test层和serviceImpl都引入了@Resource GroundingService groundingService; 之后有试过在每个出现groundingService的地方都注入 @Resource GroundingService groundingService; 运行起来也没有问题

所以推测@Resource GroundingService groundingService;这种方式无论在哪一层,加载的是同一个@Service的bean,至于为什么每次我们注入的都是@Resource GroundingService groundingService,而不直接注入GroundingServiceImpl

提问: 为什么每次我们注入的都是@Resource GroundingService groundingService,而不直接注入GroundingServiceImpl 如果有多个类实现了这个接口 我怎么知道我要调用哪个 GPT给的答案:


总结

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

相关推荐
TDengine (老段)6 分钟前
TDengine 数学函数 CRC32 用户手册
java·大数据·数据库·sql·时序数据库·tdengine·1024程序员节
心随雨下25 分钟前
Tomcat日志配置与优化指南
java·服务器·tomcat
Kapaseker31 分钟前
Java 25 中值得关注的新特性
java
wljt35 分钟前
Linux 常用命令速查手册(Java开发版)
java·linux·python
撩得Android一次心动38 分钟前
Android 四大组件——BroadcastReceiver(广播)
android·java·android 四大组件
canonical_entropy41 分钟前
Nop平台到底有什么独特之处,它能用在什么场景?
java·后端·领域驱动设计
chilavert31844 分钟前
技术演进中的开发沉思-174 java-EJB:分布式通信
java·分布式
不是株1 小时前
JavaWeb(后端进阶)
java·开发语言·后端
编程火箭车2 小时前
【Java SE 基础学习打卡】02 计算机硬件与软件
java·电脑选购·计算机基础·编程入门·计算机硬件·软件系统·编程学习路线