user2023141
user2023141

Reputation: 1223

Spring JPA threads: IllegalArgumentException Exception

I've annotated a Thread class as follow:

@Transactional(propagation = Propagation.REQUIRED)
@Component
@Scope("prototype")
public class ThreadRetrieveStockInfoEuronext implements Runnable {
   ...
}

The Thread is wired into a Controller class, and called via Global.poolMultiple.execute(threadRetrieveStockInfoEuronext);

public class RetrievelController {

    @Autowired
    private ThreadRetrieveStockInfoEuronext threadRetrieveStockInfoEuronext;

    @RequestMapping(value = "/.retrieveStockInfo.htm", method = RequestMethod.POST)
    public ModelAndView submitRetrieveStockInfo(@ModelAttribute("retrieveStockInfoCommand") RetrieveStockInfoCommand command, BindingResult result, HttpServletRequest request) throws Exception {

        Global.poolMultiple.execute(threadRetrieveStockInfoEuronext);
        return new ModelAndView(".retrieveStockInfo", "retrieveStockInfoCommand", command);
    }

The Global Class:

@SuppressWarnings("unchecked")
@Component
public class Global {

    // create ExecutorService to manage threads
    public static ExecutorService poolMultiple = Executors.newFixedThreadPool(10);
    public static ExecutorService poolSingle = Executors.newFixedThreadPool(1);
...
}

When running the application, the following exception occurs:

java.lang.IllegalArgumentException: Can not set com.chartinvest.admin.thread.ThreadRetrieveStockInfoEuronext field

com.chartinvest.controller.RetrievelController.threadRetrieveStockInfoEuronext to com.sun.proxy.$Proxy52

Upvotes: 1

Views: 195

Answers (1)

JB Nizet
JB Nizet

Reputation: 692231

That's because your Runnable implementation is a Spring component, annotated with @Transactional, and that declarative transactions are achieved, in Spring, by wrapping the actual instance of the class inside a JDK interface-based proxy that handles the transaction. In consequence, the actual object that is autowired is not an instance of ThreadRetrieveStockInfoEuronext, but an instance of its interface, Runnable, that delegates to an instance of ThreadRetrieveStockInfoEuronext.

The usual fix to this problem is to autowire the interface instead of autowiring the concrete type. But in this case, this class shouldn't be a Spring component in the first place, and it shouldn't be transactional either. BTW, making it a prototype gives you the illusion that a new instance will be created each time submitRetrieveStockInfo() is called, but that's incorrect: a single instance is created and autowired into the RetrievelController singleton, and the same runnable is thus used for all the requests to the controller.

Just make ThreadRetrieveStockInfoEuronext a simple class, instantiate it using new, passing a Spring bean as argument, and make its run() method call a transactional method of that Spring bean:

@Transactional
@Component
public class ThreadRetrieveStockInfoEuronextImpl implements ThreadRetrieveStockInfoEuronext {
    @Override 
    void doSomething() { ... }
}

public class RetrievelController {

    @Autowired
    private ThreadRetrieveStockInfoEuronext threadRetrieveStockInfoEuronext;

    @RequestMapping(value = "/.retrieveStockInfo.htm", method = RequestMethod.POST)
    public ModelAndView submitRetrieveStockInfo(@ModelAttribute("retrieveStockInfoCommand") RetrieveStockInfoCommand command, BindingResult result, HttpServletRequest request) throws Exception {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                threadRetrieveStockInfoEuronext.doSomething();
            }
        };

        Global.poolMultiple.execute(runnable);
        return new ModelAndView(".retrieveStockInfo", "retrieveStockInfoCommand", command);
    }

Upvotes: 3

Related Questions