tags:

views:

232

answers:

2

Hi all,

I want to create some spring beans after startup in a factory-ish pattern. For example every so often I have some work to do and I need to create a task bean (which probably has dependents on other singleton spring beans) and execute it.

There may be several pieces of work to execute concurrently so each task bean needs to be independent (prototype).

Is there any common pattern people use to achieve this?

As I see it I need to interact with the container/applicationContext somehow but I don't really want to scatter injections of applicationContext/beanFactory and calls to getBean("...") everywhere.

I thought of something like this (note the "factory" is something I'm imagining, rather than something that exists)

<bean id="myTask" class="MyTask" scope="prototype">
  <property name="entityManager" ref=".../>
  ...
</bean>

<bean id="myTaskExecutor" class="MyTaskExecutor">
  <property name="taskFactory">
     <xxx:factory bean="myTask"/>
  </property>
</bean>

And then code

class MyTaskExecutor
{
  private Factory<MyTask> taskFactory;

  public void setTaskFactory( Factory<MyTask> taskFactory )
  {
    this.taskFactory = taskFactory;
  }
}

And maybe an annotation version

class MyTaskExecutor
{
  @Factory(MyTask.class)
  private Factory<MyTask> taskFactory;

}

Maybe there's something like the above already? Or am I missing something fundamental somewhere.

I realise I could have a singleton MyTaskFactory and use that to instantiate using "new" but then I'd have to pass all of it's dependents from the factory which feels wrong.

So I guess to sum up the question is

What is the recommended way of creating prototype spring beans on-demand from within application code?

Appreciate any input.

+2  A: 

I think you're over-complicating the problem. All you need to do is write a TaskFactory class (nothing special about it, no special interfaces or annotations). TaskFactory would be injected with all the other beans needed, and would have a createTask method which creates the tasks on demand, and which passes references to the required Spring beans to the new task when it's created. Client code is injected with TaskFactory, and calls createTask when required.

Spring itself provides no explicit support for what you're trying to do. The likes of factory-method XML attributes and FactoryBean interfaces are only useful for one-off creation of a bean within its scope, and if you want to create them on demand, that means scope="prototype", and that means using getBean().

edit: It's probably worth pointing out that prototype-scoped beans are really not what Spring is designed for. Yes, it supports them, but using them is not a very edifying experience. If you really want to go down this road, then it's worth taking a look at @Configurable. It's very powerful, but not always suitable, because of runtime classloader constraints.

skaffman
I get what you're saying but it still doesn't feel like a great solution (I mentioned having a factory above). Consider if MyTask has a dependency on other prototype scoped beans, each with their own dependencies. I would have to either manufacture all those in the factory (and inject their dependents into my factory) and pass them through to MyTask, or have MyTask create them, and have the factory pass in all the other prototype bean's dependents.That all feels like the equivalent of not using spring for singletons, only in this case it is for prototype scoped beans.
Mike Q
see edit regarding `@Configurable`
skaffman
A: 

I'm trying to do a very similar thing (Spring 3.0) to this so I'd be interested in hearing how you ended up solving this problem.

My current approach is to use getBean() and I've gone with the default singleton scope with my Spring-instatiated beans/pojos. So I have non-thread safe code for now but would like to improve it to be thread safe further down the track.

nickdos
I ended up just using BeanFactoryAware on the relevant beans and calling getBean() as necessary to manufacture my prototype beans. I'm sure an annotation solution is entirely feasible but I decided it would have been overkill for me as I only needed the functionality in about 5-10 places, of a very large app.
Mike Q