问题场景

springboot测试类某方法如下:

启动子线程,每个线程调用monitorPluginService.issueHostPlugin方法

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@ContextConfiguration
@Slf4j
class MonitorPluginServiceImplTest {

    @Autowired
    private MonitorPluginServiceImpl monitorPluginService;



    @Test
    void test1() {
        for (int i = 0; i < 4; i++) {
            new Thread(()->{
                monitorPluginService.issueHostPlugin(new MonitorGroupHostModel());
            }).start();
        }
    }
}

其中MonitorPluginServiceImpl类注入了FtpConfig

  @Autowired
  private FtpConfig ftpConfig;

FtpConfig类上有注解@Validated

@Validated
@Component
@Data
@ConfigurationProperties("ftp")
public class FtpConfig {
}

issueHostPlugin方法里会获取FtpConfig的属性

ftpClient = FtpUtil.openFtpClient(ftpConfig.getHost(), ftpConfig.getPort(), ftpConfig.getUser(), ftpConfig.getPassword());

结果测试时,在获取FtpConfig的属性时,抛出如下异常

org.springframework.beans.factory.BeanCreationNotAllowedException:
Error creating bean with name ‘defaultValidator’: Singleton bean
creation not allowed while singletons of this factory are in
destruction (Do not request a bean from a BeanFactory in a destroy
method implementation!)

问题跟踪

MethodValidationPostProcessorBeanPostProcessor的一种,它的@Bean方法如下。
在这里插入图片描述
其中参数Validator加了@Lazy注解,于是最终生成的Validator参数是代理对象。
在这里插入图片描述

MethodValidationPostProcessor的作用是,如果类上加了注解@Validated,那么在实例化过程中,会为类生成代理。
FtpConfig类上有注解@Validated,所以生成了FtpConfig的代理。

当进入issueHostPlugin方法时,会获取FtpConfig的属性,此时调用的是FtpConfig的代理方法。如下
在这里插入图片描述
进入super.proceed
在这里插入图片描述
红框处是MethodValidationInterceptor类,该类中的validator成员就是之前MethodValidationPostProcessor中的Validator代理
在这里插入图片描述
于是此时进入Validator代理方法。方法中会调用targetSource的方法
在这里插入图片描述
由于之前生成Validator代理时,已设置了targetSource,所以此时进入如下getTarget方法
在这里插入图片描述
getTarget方法开始实例化Validator。最终进入
在这里插入图片描述
此时从一级缓存获取不到数据,且if语句为true,于是抛出开头提到的异常。

问题分析

为什么最后能进入 if (this.singletonsCurrentlyInDestruction) {条件呢?
查看源码得知singletonsCurrentlyInDestruction初始值为false,只有当spring容器销毁时才会赋值为true。如下
在这里插入图片描述
测试类方法还未运行结束,为什么容器会销毁呢?
因为@Test方法逻辑是放在new Thread里的,当线程启动后,虽然业务方法monitorPluginService.issueHostPlugin还在运行,但@Test方法已结束,此时spring开始调用上述destroySingletons方法。
在业务方法里获取FtpConfig的属性时,会开始实例化Validator。由于spring已清空容器,导致无法从一级缓存获取到,而singletonsCurrentlyInDestruction又为true,于是抛出异常。

问题解决

线程启动后sleep一段时间,防止spring提前清空容器

@Test
void test2() throws InterruptedException {
    for (int i = 0; i < 4; i++) {
        new Thread(()->{
            monitorPluginService.issueHostPlugin(new MonitorGroupHostModel());
        }).start();
    }
    
    Thread.sleep(100000);
}
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐