I've a web service that starts a series of action. All these actions started from the same request are sharing an actionContext that contains some locks and some other information.
Until now this actionContext object is injected by Spring in all the actions using a 'Request' scope.
Now I'm implementing a web socket service to be able to follow the evolution of these actions.
The webService will now have to spawn a thread that handles the actions execution and return the webSocket address to the calling application/user.
The action has been implemented using the @async annotation of spring and will run in a pool of thread as defined in the application context.
THE PROBLEM:
With this new functionality the 'request' scope doesn't work any more, since the spawned thread is not a request (Spring will block the execution).
What is the best solution to handle this problem?
Thanks for the help!
I decided to keep all as clean as possible and go for the TreadScope implementation!
ThreadScope
used to inject the same bean inside all the action that are running in the same thread.asyncAspect
that intercepts all the @async
calls, the asyncAspectAfter()
will go to clean up the threadLocal
variable.@async
annotated methods: since Spring runs the methods in a pool, the threads are reused and not destroyed. That means that the threadLocal
variable will persist in the thread. /**
* This scope works in conjunction with the {@link AsyncAspect} that goes to
* cleanup the threadScoped beans after an async run. This is required since in
* spring the async methods run in a pool of thread, so they could share some
* thread scoped beans.
*
*
* @author enrico.agnoli
*/
public class ThreadScope implements Scope {
/**
* This map contains for each bean name or ID the created object. The
* objects are created with a spring object factory. The map is ThreadLocal,
* so the bean are defined only in the current thread!
*/
private final ThreadLocal<Map<String, Object>> threadLocalObjectMap = new ThreadLocal<Map<String, Object>>() {
@Override
protected Map<String, Object> initialValue() {
return new HashMap<String, Object>();
};
};
/** {@inheritDoc} */
public Object get(final String beanName,
final ObjectFactory<?> theObjectFactory) {
Object object = threadLocalObjectMap.get().get(beanName);
if (null == object) {
object = theObjectFactory.getObject();
threadLocalObjectMap.get().put(beanName, object);
}
return object;
}
/** {@inheritDoc} */
public String getConversationId() {
// In this case, it returns the thread name.
return Thread.currentThread().getName();
}
/** {@inheritDoc} */
public void registerDestructionCallback(final String beanName,
final Runnable theCallback) {
// nothing to do ... this is optional and not required
}
/** {@inheritDoc} */
public Object remove(final String beanName) {
return threadLocalObjectMap.get().remove(beanName);
}
@Override
public Object resolveContextualObject(String key) {
// TODO Auto-generated method stub
return null;
}
/**
* Invoke this method to cleanUp the ThreadLocal beans. This call is
* required since in case of run in a thread pool, the thread will never be
* removed and the threadLocal variables would be shared between two
* different executions!
*/
public void cleanUpThreadScopedBeans() {
threadLocalObjectMap.remove();
}
}
/**
* This Async Aspect is used to cleanup the threadScoped beans after an async
* run. This is required since in spring the async methods run in a pool of
* thread, so they could share some thread scoped beans.<br>
* The Thread scope is defined in {@link ThreadScope}
*
* @author enrico.agnoli
*
*/
public class AsyncAspect {
@Autowired
ThreadScope threadScope;
private static final Logger log = LoggerFactory
.getLogger(AsyncAspect.class);
public void asyncAspectAfter() {
log.debug("CleanUp of the ThreadScoped beans");
threadScope.cleanUpThreadScopedBeans();
}
}
<!-- Here we define the Thread scope, a bean exists only inside the same thread -->
<bean id="ThreadScope" class="com.myCompany.myApp.ThreadScope" />
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<ref bean="ThreadScope"/>
</entry>
</map>
</property>
</bean>
<!-- Here we configure the aspect -->
<bean id="AsyncAspect" class="com.myCompany.myApp.AsyncAspect" />
<aop:config proxy-target-class="true">
<aop:aspect ref="AsyncAspect">
<aop:pointcut expression="@annotation(org.springframework.scheduling.annotation.Async)" id="asyncAspectId" />
<aop:after pointcut-ref="asyncAspectId" method="asyncAspectAfter" />
</aop:aspect>
</aop:config>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With