首页 > 解决方案 > 在 Spring 中,为什么具有范围原型的服务会多次实例化?

问题描述

我创建了一个 Spring 项目并创建了以下文件,因为我试图了解 Spring 如何处理单例与非单例组件类:

调度程序.java:

@Component
public class ScheduledTasks {

    @Autowired
    private TestRun testRun;

    @Scheduled(cron= "*/15 * * * * *")
    public void startTest() {
        testRun.startNewRun();
    }
}

测试运行.java

@Service
public class TestRun {

    private long t = System.currentTimeMillis();

    @Autowired
    private TestService testService;

    public void startNewRun() {
        new Thread(new TestRun.TestRunThread()).start();
    }

    public TestRun(){
        System.out.println("TestRun Constructor " + t);
    }

    public class TestRunThread implements Runnable {
        public void run() {
            testService.toString();
            System.out.println("Counta " + testService.getCount());
            System.out.println("Countb " + testService.getCount());
        }
    }
}

测试服务.java

@Service
@Scope(value="prototype", proxyMode= ScopedProxyMode.TARGET_CLASS)
public class TestService {

    private long t = System.currentTimeMillis();

    private (volatile) int count; //Tried both with and without volatile

    public TestService(){
        System.out.println("Service Constructor " + t);
    }

    public int getCount(){
        return count++;
    }
}

运行后我的期望是:类 TestRun 将被实例化一次(真)并显示“TestRun 构造函数 {毫秒}”(真)但是虽然我期望 TestService 构造函数会被调用多次(每次启动一个新线程) 是真的,下面的输出我无法解释:

TestRun Constructor 1612706390893
...
TestService Constructor 1612706399976
Counta 0 ; 1612706390893
TestService Constructor 1612706400011
Countb 0 ; 1612706390893
TestService Constructor 1612706415006
Counta 0 ; 1612706390893
TestService Constructor 1612706415009
Countb 0 ; 1612706390893

'Counta' 后面的 0 我理解,但为什么 'Countb' 后面的计数也是 0?从日志中我得出结论,在调用“testService.getCount()”方法时再次实例化了 TestService,但我不明白为什么会这样。任何帮助,将不胜感激。

标签: javaspringspring-boot

解决方案


出现问题是因为您使用ScopedProxyMode.TARGET_CLASS的是原型bean。很可能 CGLIB 正在生成一个包装器,并且每次调用TestService都会创建一个新实例,例如

// CGLIB proxy
class TestServiceProxy extends TestService {
  ...

  public int getCount() {
    final TestService testService = ...
    return testService.getCount();
  }
}
 

我实际上为您运行了一个示例。
您可以看到调试调用堆栈,并注意到 CGLIB 生成了一个包装器。

在此处输入图像描述

移除ScopedProxyMode.TARGET_CLASS

@Service
@Scope("prototype")
public class TestService {

从代理中释放调用堆栈,并显示预期结果。

在此处输入图像描述


推荐阅读