亿点点小问题:代码单元测试时发现注入的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的加载与管理,对于研发过程中出现的匪夷所思的小问题,推荐还是认真研究明白深层此原因,不要觉得侥幸改成功了就万事大吉,有可能这个小问题的底层是一个以前没有注意过的盲点,若不处理就很有可能会给后面埋下大坑(玄冥黑洞)。

相关推荐
Amagi.3 分钟前
Spring中Bean的作用域
java·后端·spring
2402_8575893627 分钟前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
繁依Fanyi30 分钟前
旅游心动盲盒:开启个性化旅行新体验
java·服务器·python·算法·eclipse·tomcat·旅游
J老熊35 分钟前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
蜜桃小阿雯38 分钟前
JAVA开源项目 旅游管理系统 计算机毕业设计
java·开发语言·jvm·spring cloud·开源·intellij-idea·旅游
CoderJia程序员甲38 分钟前
重学SpringBoot3-集成Redis(四)之Redisson
java·spring boot·redis·缓存
sco528239 分钟前
SpringBoot 集成 Ehcache 实现本地缓存
java·spring boot·后端
OLDERHARD1 小时前
Java - LeetCode面试经典150题 - 矩阵 (四)
java·leetcode·面试
原机小子1 小时前
在线教育的未来:SpringBoot技术实现
java·spring boot·后端
慕明翰1 小时前
Springboot集成JSP报 404
java·开发语言·spring boot