Spring的@autowired注解是否不适用于非单例容器?

IT小君   2023-09-16T19:55:36

我有一个MyTask类,它实现了Runnable接口,可以在任何给定的时刻实例化许多这样的对象。我想将某些属性自动注入到MyTask类中。

但是我认为,如果我用@Component标记MyTask,它会变成一个由Spring管理的单例,对吗?这不是我想要的,我需要由TaskExecutor运行的这个类的许多独立实例。

所以我的问题是:

  • a)我对@Component注解的理解基本上是错误的吗?它不会使MyTask成为一个由Spring管理的单例吗?
  • b)是否有其他注解我应该使用,以便Spring检测到@Autowired并注入属性?
  • c)Spring的自动注入不适用于像MyTask这样的非单例容器/类吗?

更新#1 - 这些方法都不起作用:

public class MyTask implements Runnable { // 我希望这个类不是单例的
    @Autowired
    public SomeSpecialSpringConfiguredConnectionClass blah; // 这是应该被注入的单例bean
    @Override
    public void run() {
        // BLAH是NULL,这不应该是NULL,这不是我想要的
        // 这是有道理的,因为Spring从来不知道它必须处理这个类
        //
    }
}

@Component
public class MyTask implements Runnable { // 我希望这个类不是单例的
    @Autowired
    public SomeSpecialSpringConfiguredConnectionClass blah; // 这是应该被注入的单例bean
    @Override
    public void run() {
        // 这个可以工作,但现在MyTask是单例的:(
    }
}

@Component
@Scope("prototype")
public class MyTask implements Runnable { // 我希望这个类不是单例的
    @Autowired
    public SomeSpecialSpringConfiguredConnectionClass blah; // 这是应该被注入的单例bean
    @Override
    public void run() {
        // BLAH是NULL,这不应该是NULL,这不是我想要的
    }
}

更新#2 - 在等待更多关于如何以简单的方式做到这一点的建议时,我正在研究:使用AspectJ和Spring对领域对象进行依赖注入作为替代方案。

评论(4)
IT小君

首先,使用@Component声明并被Spring组件扫描捕获的Bean,默认情况下将成为Spring管理的单例。

我不知道你如何使用MyTask,但在你的情况下使用AspectJ有些过度,而且将MyTask声明为Spring管理的Bean没有太多意义。另一种做法是:

  1. 将MyTask定义为一个普通的Java类,并添加一个构造函数来初始化依赖的blah。

  2. 在使用MyTask的地方自动装配blah,并在每次执行任务时实例化一个MyTask对象,具体代码如下:

    //在另一个Spring Bean中默认使用单例作用域自动装配MyTask的依赖
    @Autowired private SomeSpecialSpringConfiguredConnectionClass blah
    //创建任务并手动装配blah
    executor.submit(new MyTask(blah))
    
2023-09-16T19:55:47   回复
IT小君

在使用context:component scan进行类路径扫描时,@Component注解允许自动检测。

Spring的自动装配可以用于原型和单例作用域。在原型作用域的情况下,bean的销毁生命周期回调不会被调用。

在Spring文档页面中有很好的解释。

我看不出来你提到的任何原因不起作用。

这是我尝试做的一个工作示例。

public class SpringContainerStartClass {

   public static void main(final String[] args) {
      
2023-09-16T19:55:57   回复
IT小君

通常,添加@Scope("prototype")不应该导致自动装配的blah bean出现空错误,你应该检查你如何实例化MyTask bean。 我认为问题在于你手动实例化了MyTask,像这样:

   MyTask task = new MyTask();

因此,它超出了Spring的控制,这就是为什么它的依赖关系blah bean为null的原因,而不是手动实例化,你需要使用@Autowired进行自动装配,并让Spring处理它的依赖关系,然后blah将不为空。 但是还有另一个问题。在另一个单例对象中自动装配一个原型bean MyTask是错误的。Spring容器只创建一个单例bean,因此仅设置原型bean一次,这导致原型作用域不起作用。如下所示,MyActivity是一个单例,它自动装配了MyTask,我还为MyTask添加了一个构造函数,以便在创建MyTask的新实例时打印一些内容。在下面的情况下,它只打印一次,因此原型不起作用。

@Component
@Scope("prototype")
public class MyTask implements Runnable {

  @Autowired
  public SomeSpecialSpringConfiguredConnectionClass blah;

  public MyTask(){
    System.out.println("MyTask的新实例");
  }

  @Override
  public void run() {
    assert(blah != null);
  }
}


@Component
public class MyActivity {

  @Autowired
  private MyTask task;

  public MyTask start() {
    return task;
  }
}

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {MyActivity.class, MyTask.class, 
   SomeSpecialSpringConfiguredConnectionClass.class})
public class MyTaskTest {

  @Autowired
  private MyActivity activity;

  @Test
  public void testActivity() {
    for (int i = 0; i < 5; i++) {
        MyTask task = activity.start();
        task.run();
    }
  }
 }

根据Spring AOP Scoped proxies,我将@Scope("prototype")更改为@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "prototype") 因此,当单例MyActivity bean被调用时,作用域代理会注入MyTask的一个新实例。

@Component
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "prototype")
public class MyTask implements Runnable {

  @Autowired
  public SomeSpecialSpringConfiguredConnectionClass blah;

  public MyTask(){
    System.out.println("MyTask的新实例");
  }

  @Override
  public void run() {
    assert(blah != null);
  }
}

现在,原型工作正常,这是在控制台中打印的结果:

MyTask的新实例
MyTask的新实例
MyTask的新实例
MyTask的新实例
MyTask的新实例
2023-09-16T19:56:48   回复
IT小君

不要使用@Autowire,使用@Inject看看魔法效果。 我有相同的情况,其中一个Validator类是Java的单例类,而不是Spring的作用域Bean。我需要注入另一个团队提供的UAA Client Spring Bean。所以@Autowire不起作用,但@Inject起作用。

2023-09-16T19:56:53   回复