简体   繁体   中英

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 Task s, but the tasks have to be prototype Spring beans that take a runtime value as a constructor parameter. How can I accomplish this?

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).

Your singleton bean can implement BeanFactoryAware and lookup beans from the containing spring factory.

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

public class MyBeanFactory implements BeanFactoryAware {

    private BeanFactory beanFactory;

    public void setBeanFactory(BeanFactory beanFactory) 
            throws BeansException    {
        this.beanFactory = beanFactory;     
    }

    public Task createTask(Task in) {
        return beanFactory.getBean("task",in);
    }

}
///////////////

import java.util.concurrent.Callable;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Scope;
import org.springframework.transaction.annotation.Transactional;

@Configurable // unless using xml based config
@Scope(value="prototype") // tell bean factory to create new instance each time
public class Task implements Callable<Object> {

    private Object in;

    public Task(Object in) {
        super();
        this.in = in;
    }

    @Transactional
    public Object call() throws Exception {
        //do real work
        return in;
    }   
}
///

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM