Runnable内部的实例变量在执行时为何消失了?

问题描述

@Autowired
ThreadPoolTaskScheduler taskScheduler;

@Autowired
SendUpdatesRunnable sendUpdatesRunnable;

private void createJob(String timezone) {
        zoneddatetime zoned = LocalDate.Now().atTime(10,11).atZone(ZoneId.of(timezone));
           
        sendUpdatesRunnable.timezone = timezone;
        taskScheduler.schedule(
                sendUpdatesRunnable,Date.from(zoned.toInstant())
        );
    }

@Component
public class SendUpdatesRunnable implements Runnable{

    @Autowired
    ProductRepository productRepository;

    String timezone;

    @Override
    @Transactional
    public void run() {
        List<Product> newProds = productRepository.findProductByCreateDateTimeIsAfter(LocalDateTime.Now().minusHours(24));
        List<Product> updatedProds = productRepository.findProductByUpdateDateTimeIsAfter(LocalDateTime.Now().minusHours(24));

       //System print out timezone variable = null
    }
}

解决方法

问题

当您注入带有AOP内容的Spring Bean(即@Transcational)时,Spring Framework不会注入真正的具体类,而是注入一个代理对象。代理类包含字段timezone,并且不会委派给您的真实类实例。

Spring Framework中的代理如何工作:

How Proxies work

source

存在代理是因为您的run()方法看起来像这样:

public void run() {
  transactionManager.begin();
  realInstance.run();
  transactionManager.commit();
}

因此,当您像这样设置时区sendUpdatesRunnable.timezone = timezone;时,它实际上是在执行proxy.timezone = timezone;,并且您的sendUpdatesRunnable对象仍然具有timezone为空。

解决方案

您需要使用getters / setters。

当您使用setter方法时,代理类将具有如下的实现:

public void setTimezone(String timezone) {
  realInstance.setTimezone(timezone);
}

因此,当您调用set方法时,它将正确传播到具体实现所在的真实实例。

话虽如此,您不应该在Spring Bean实现中完全使用实例级可变变量。唯一的实例变量应该是在bean初始化时注入的依赖项。