Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring: how to instantiate a Spring bean that takes a runtime parameter?

I've got a singleton Spring bean that creates a couple of tasks (java.util.concurrent.Callable's) at runtime to do its work in parallel. Right now, the Callable's are defined as inner classes in the singleton bean, and the singleton bean creates them simply by instantiating them with new Task(in), where in is a parameter known only at runtime.

Now I want to extract the inner Task class to a regular top-level class because I want to make the Task's call() method transactional, so I need it to be a Spring bean.

I guess I need to give my singleton some kind of factory of Tasks, but the tasks have to be prototype Spring beans that take a runtime value as a constructor parameter. How can I accomplish this?

like image 938
Jan Van den bosch Avatar asked Feb 24 '12 11:02

Jan Van den bosch


2 Answers

Spring's bean factory and new are mutually exclusive. You can't call new and expect that object to be under Spring's control.

My suggestion is to inject those Tasks into the Singleton. Make them Spring beans, too.

You should recognize that the Task itself isn't going to be transaction, but its dependencies can be. Inject those into the Tasks and let Spring manage the transactions.

like image 90
duffymo Avatar answered Nov 11 '22 01:11

duffymo


Another approach might be to use Spring's @Configurable annotation with load-time weaving, this way you can use new (instead of a bean factory) to create wired Callable's at runtime:

@Configurable
public class WiredTask implements Callable<Result> {

    @Autowired
    private TaskExecutor executor;

    public WiredTask(String in) {
        this.in = in;
    }

    public Result call() {
        return executor.run(in);
    }
}

@Bean @Scope("prototype")
public class TaskExecutor() {

    @Transactional
    public Result run(String in) {
        ...
    }
}

// Example of how you might then use it in your singleton...
ExecutorService pool = Executors.newFixedThreadPool(3);
WiredTask task = new WiredTask("payload");
Future<Result> result = pool.submit(task);

See this article for more info. Note, you cannot use @Configurable and @Transactional in the same bean, hence the need for two classes. For that reason, this might not be the ideal solution if you have lots of different implementations of Callable (since you will need 2 classes for each one).

like image 34
seanhodges Avatar answered Nov 11 '22 01:11

seanhodges