Ejecutar una tarea asincrona en java con spring

A veces necesitamos ejecutar una tarea que lleva un tiempo considerable y que el usuario no necesariamente necesita ser informado en el momento, por ejemplo el envío de un correo de notificación (en algunos casos), el hacer un proceso x que lleva tiempo, etc…

La clase TaskExecutor de spring abstrae la de java.concurrent.Executor de manera tal que es mucho mas facil con spring, SimpleAsyncTaskExecutor (asincrono), SyncTaskExecutor (sincrono), SimpleThreadPoolTaskExecutor y otros mas.

Pongo un ejemplo del uso del SimpleAsyncTaskExecutor
[java]
private TaskExecutor taskExecutor;

public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}

taskExecutor.execute(new Runnable(){
public void run() {

}
});
[/java]
Aquí mostramos la configuración de nuestra clase usando un TaskExecutor en spring.
[xml]
<bean id="taskExecutor" class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>

<bean id="myBean" class="org.myproject.MyBean">
<property name="taskExecutor" ref="taskExecutor"/>
</bean>
[/xml]
Cuando usamos hibernate y tenemos relaciones «lazy» en nuestros objetos que son traidos con hibernate, esto nos puede traer problemas al usar simplemente el TaskExecutor, tenemos que hacer una especia de OpenSessionInThread para poder tener la session abierta y que se reutilice esa session a lo largo del codigo ejecutado.

Pongo un ejemplo de como seria esa implementación. El código es similar a la implementación del OpenSessionInViewFilter de spring.
[java]
public abstract class OpenSessionInThreadTask implements Runnable{

private ApplicationContext ctx;

public OpenSessionInThreadTask(ApplicationContext ctx) {
this.ctx = ctx;
}

protected abstract void runInternal();

public final void run(){
SessionFactory sessionFactory = lookupSessionFactory();
boolean participate = false;

// single session mode
if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
// Do not modify the Session: just set the participate flag.
participate = true;
}
else {
Session session = getSession(sessionFactory);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
}

try{
runInternal();
}

finally {
if (!participate) {
// single session mode
SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
closeSession(sessionHolder.getSession(), sessionFactory);
}
}
}

protected SessionFactory lookupSessionFactory() {
return ctx.getBean("sessionFactory", SessionFactory.class);
}

protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
FlushMode flushMode = FlushMode.MANUAL;
if (flushMode != null) {
session.setFlushMode(flushMode);
}
return session;
}

protected void closeSession(Session session, SessionFactory sessionFactory) {
SessionFactoryUtils.closeSession(session);
}

}
[/java]
Y por ultimo el uso de la clase OpenSessionInThreadTask, de hecho esta clase ya se encuentra en forza.
[java]
taskExecutor.execute(new OpenSessionInThreadTask(ctx){
public void runInternal() {
….
}
});
[/java]

Bueno espero que haya sido de su ayuda, no olviden dejar en los comentarios cualquier duda o aporte.