Is Spring @autowired not meant for non-singleton c

2019-01-25 09:55发布

I have a MyTask class which implements Runnable and there can be many such objects instantiated at any given moment. There are certain properties that I would like to autowire into MyTask class.

But I think that if I mark MyTask with @Component then it will become a spring-managed singleton correct? That's not what I want, I need many independent instances of this class to be run by a TaskExecutor.

So my question(s):

  • a) Am I fundamentally wrong in my understanding of @Component annotation? Does it NOT make MyTask into a spring-managed singleton?
  • b) Is there some other annotation I should use so that spring detects @Autowired and injects the property?
  • c) Is spring autowiring not meant for non-singleton containers/classes like MyTask?

Update # 1 - These don't work:

public class MyTask implements Runnable { // I want this class to be non-singleton
    @Autowired
    public SomeSpecialSpringConfiguredConnectionClass blah; // this is the singleton bean that should be injected
    @Override
    public void run() {
        // BLAH IS NULL, this shouldn't be NULL, that is not what I want
        // which makes sense considering Spring never knew it had to work
        // on this class
    }
}

@Component
public class MyTask implements Runnable { // I want this class to be non-singleton
    @Autowired
    public SomeSpecialSpringConfiguredConnectionClass blah; // this is the singleton bean that should be injected
    @Override
    public void run() {
        // this works BUT now MyTask is singleton :(
    }
}

@Component
@Scope("prototype")
public class MyTask implements Runnable { // I want this class to be non-singleton
    @Autowired
    public SomeSpecialSpringConfiguredConnectionClass blah; // this is the singleton bean that should be injected
    @Override
    public void run() {
        // BLAH IS NULL, again ... this shouldn't be NULL, that is not what I want
    }
}

Update # 2 - While waiting for some more suggestions on how to do it the easy way, I'm looking into: Using AspectJ to dependency inject domain objects with Spring as an alternative.

4条回答
smile是对你的礼貌
2楼-- · 2019-01-25 10:34

first, beans declared with @Component and picked up by spring component scan will become a spring-managed singleton by default.

I have no idea how you use MyTask, but it is overkilled to use AspectJ in your situation, and it does not make much sense to declare MyTask as a spring-managed bean. another way of doing this will be:

  1. define MyTask as a plain java class and add a constructor to initialize the dependency blah

  2. autowire blah in where you use MyTask, and instantiate a MyTask object every time you want to execute a task as follow:

    //autowire the dependency of MyTask in another spring bean with default singleton scope
    @Autowired private SomeSpecialSpringConfiguredConnectionClass blah
    //create task and wire the blah yourself
    executor.submit(new MyTask(blah))
    
查看更多
forever°为你锁心
3楼-- · 2019-01-25 10:35

The @Component annotation would allow them for auto detetion while classpath scanning using the context:component scan That is what it does. there is a fine line between @Service and @Component, and in this case it does not affect in anyway.

Spring autowiring can be done for prototype as well as singleton scopes. In case of the prototype scope though the lifecycle callbacks for the destruction of the bean isnt called.

It is explained very well on the Spring documentation page. http://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch04s04.html

I dont see a reason why whatever mentioned by you shouldnt work.

He is a working sample of what i tried to do to explain it better.

public class SpringContainerStartClass {

   public static void main(final String[] args) {
      final ClassPathXmlApplicationContext bf = new ClassPathXmlApplicationContext("beans.xml");
      final MainApplication1 bean = (MainApplication1) bf.getBean("mainApplication1");
      bean.getMyTask().printSomething();

   }
}

This is the starting point of the app.

Here is your myTask class

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

   @Autowired
   private SomeSpecialSpringConfiguredConnectionClass someSpringObject;

   @Override
   public void run() {
      System.out.println("running now");

   }

   public void printSomething() {
      System.out.println(someSpringObject.getValue());
   }

   public SomeSpecialSpringConfiguredConnectionClass getSomeSpringObject() {
      return someSpringObject;
   }

   public void setSomeSpringObject(final SomeSpecialSpringConfiguredConnectionClass someSpringObject) {
      this.someSpringObject = someSpringObject;
   }

}

Two other classes to show how the prototype scope is working

@Component
public class MainApplication1 {

   @Autowired
   private MyTask myTask;

   public MyTask getMyTask() {
      return myTask;
   }

   public void setMyTask(final MyTask myTask) {
      this.myTask = myTask;
   }

}

@Component
public class MainApplication2 {

   @Autowired
   private MyTask myTask;

   public MyTask getMyTask() {
      return myTask;
   }

   public void setMyTask(final MyTask myTask) {
      this.myTask = myTask;
   }

}

A BeanPostprocessor which will show you how the objects are getting created

public class InstantiationTracingBeanPostProcessor
      implements BeanPostProcessor {

   @Override
   public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
      return bean;
   }

   @Override
   public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
      System.out.println("Bean '" + beanName + "' created : " + bean.toString());
      return bean;
   }
}

your SomeSpringConfig class

@Service
public class SomeSpecialSpringConfiguredConnectionClass {

   private String value = "someValue";

   public String getValue() {
      return value;
   }

   public void setValue(final String value) {
      this.value = value;
   }

}

When you run this sample, you will notice that the output on the console is

INFO: Loading XML bean definitions from class path resource [beans.xml]
Jan 02, 2014 12:07:15 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@26efabf8: defining beans [mainApplication1,mainApplication2,myTask,someSpecialSpringConfiguredConnectionClass,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,com.stackoverflow.DIQuestion.InstantiationTracingBeanPostProcessor#0,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
Bean 'someSpecialSpringConfiguredConnectionClass' created : com.stackoverflow.DIQuestion.SomeSpecialSpringConfiguredConnectionClass@1e20d04b
Bean 'myTask' created : com.stackoverflow.DIQuestion.MyTask@175d6331
Bean 'mainApplication1' created : com.stackoverflow.DIQuestion.MainApplication1@741b31f2
Bean 'myTask' created : com.stackoverflow.DIQuestion.MyTask@2c2815d3
Bean 'mainApplication2' created : com.stackoverflow.DIQuestion.MainApplication2@7bb0e64a

If you notice carefulyy there are 2 objects of myTask with different hashcodes.

If you change the scope of the myTask to "Singleton" here would be the output.

INFO: Loading XML bean definitions from class path resource [beans.xml]
Jan 02, 2014 12:08:35 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@26efabf8: defining beans [mainApplication1,mainApplication2,myTask,someSpecialSpringConfiguredConnectionClass,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,com.stackoverflow.DIQuestion.InstantiationTracingBeanPostProcessor#0,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
Bean 'someSpecialSpringConfiguredConnectionClass' created : com.stackoverflow.DIQuestion.SomeSpecialSpringConfiguredConnectionClass@1e20d04b
Bean 'myTask' created : com.stackoverflow.DIQuestion.MyTask@175d6331
Bean 'mainApplication1' created : com.stackoverflow.DIQuestion.MainApplication1@741b31f2
Bean 'mainApplication2' created : com.stackoverflow.DIQuestion.MainApplication2@2c2815d3

In this case there is one object created for "myTask"

Does this help?

查看更多
4楼-- · 2019-01-25 10:42

Typically adding @Scope("prototype") should not cause null error for autowired blah bean,you should check how you instantiate MyTask bean. I think the problem is that you manually instantiate MyTask like:

   MyTask task = new MyTask();

and therefor it goes out of Spring's control that is why its dependency, blah bean, is null , instead of manual instantiation, you need to autowire it and let Spring takes care of its dependencies then blah willnot be null. But then there is another issue. Autowiring a prototype bean, MyTask, whithin another singleton object is wrong. Spring container creates a singleton bean only once, and thus only sets the prototype bean once and this causes the prototype scope not to work.Like below, Myactivity is a singleton which autowires MyTask, I also added a constructor for MyTask to print something once a new instance of MyTask gets created. In below case it only prints once, therefore prototype is not working.

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

  @Autowired
  public SomeSpecialSpringConfiguredConnectionClass blah;

  public MyTask(){
    System.out.println("New Instance of 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();
    }
  }
 }

Based on Spring AOP Scoped proxies I changed @Scope("prototype") to @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "prototype") so the scoped proxy injects a new instance of MyTask every time singleton MyActivity bean gets called.

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

  @Autowired
  public SomeSpecialSpringConfiguredConnectionClass blah;

  public MyTask(){
    System.out.println("New Instance of MyTask");
  }

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

and now the prototype is working fine and this was the result printed in console:

New Instance of MyTask
New Instance of MyTask
New Instance of MyTask
New Instance of MyTask
New Instance of MyTask
查看更多
孤傲高冷的网名
5楼-- · 2019-01-25 10:46

Instead of @Autowire, use @Inject and see the magic. I have the same situation, where in, a Validator class is Java Singleton class and not spring scoped bean. I need to inject a UAA Client spring bean provided by another team. So @Autowire didn't work, but @Inject did work.

查看更多
登录 后发表回答