wlin
wlin

Reputation: 65

Inject different service instance into controller

I have the following Controller and Service

@Controller
public class MyController
{
   @Autowired
   private MyService myService;

  @RequestMapping(method=RequestMethod.GET, value="/connect.do")
  public String connect(Model model)
  {
    invokeService();
    return  "xxx";
  }

  private void invokeService()
  {
        myService.test();
  }

}


@Service
public class MyService
{
 private int value1 = 200;
 private int value2 = 333;
 private String value3 ;
 private String value4 ;
 private String value5 ;
 ....

 public void test()
 {
        System.out.println(value1++);
        foo();
  }

 private void foo()
 {

 }

}

When I use 2 browsers to connect to the application, the output is "200" and "201", which means Spring inject the same MyService instance into the controller for different connection.

I need the output to be "200" and "200" when I use 2 different connections to access the application, because I need to share values1, values2, value3, etc between "test()" and "foo()". How to do it in Spring? Basically, I want Spring to inject different instance for different connection. I tried @Scope("prototype") in Service bean and it doesn't work.

I can make it work by using:

@Controller
public class MyController
{
    private void invokeService()
   {
        new MyService.test();
   }
}

I am just wondering that how to do it in Spring.

Another way of asking this question is: How to have multiple controller instances (one for each user connection) as opposed to one controller instance serving for all connections?

EDIT: I can see the difference of prototype (output: 200 200 200 200) and singleton (output: 200 201 202 203) by using the follow code and 2 browser connections

private void invokeService() 
{ 
  myService = applicationContext.getBean( MyService.class ); 
  new MyService.test();
  myService = applicationContext.getBean( MyService.class ); 
  new MyService.test(); 
} 

But when I put "applicationContext.getBean( MyService.class )" in postConstructor instead:

public class MyController implements ApplicationContextAware {

private SearchService searchService;

@PostConstruct
public void init() {
    searchService = applicationContext.getBean( SearchService.class );
}

protected ApplicationContext applicationContext;

@Override
public void setApplicationContext( ApplicationContext applicationContext ) throws BeansException {
    this.applicationContext = applicationContext;
}

}

and use "@Scope(BeanDefinition.SCOPE_PROTOTYPE)" on MyService, the output are still "200" and "201" when I use 2 browsers to connection to the application

Upvotes: 2

Views: 2692

Answers (1)

Aaron Digulla
Aaron Digulla

Reputation: 328860

@Autowired doesn't mix with @Scope("prototype") uness you enable proxy generation (which is probably not what you want - it quickly gets complex).

Make your MyController ApplicationContextAware instead:

public class MyController implements ApplicationContextAware {

    public MyService getMyService() {
        return applicationContext.getBean( MyService.class );
    }

    protected ApplicationContext applicationContext;

    @Override
    public void setApplicationContext( ApplicationContext applicationContext ) throws BeansException {
        this.applicationContext = applicationContext;
    }

}

Now @Scope(BeanDefinition.SCOPE_PROTOTYPE) will work as expected.

Note that you will get a new instance of MyService every time you call getMyService().

Why doesn't your approach (and my old one) doesn't work? MyController is a singleton, so the field myService is wired exactly once - Spring can't give you a new instance of the bean every time you access the field for two reasons:

  1. It would mean that Spring would have to analyze the byte code of the class to find all places where the code accesses the field.
  2. It would break in other ways. For example when you call two methods of the bean:

    myService.foo(); myService.bar(); // do you really want a new bean here?

PS: If you use Spring < 3.1.2, you will want to specify the ID of the bean since getBean(Class<?> type) was very slow for those versions.

Upvotes: 3

Related Questions