Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring request scope in an async service? Implement ThreadScope over threadLocal variable, plus an AsyncAspect to clean up

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?

  • Implement my Thread scope to handle the actionContext and inject it correctly in all the actions?
  • Pass the actionContext everywhere manually (in my opinion it doesn't look nice)
  • Implement a webService that instantiate a webSocket and request the caller to call this as first and then pass its reference to the real webService?

Thanks for the help!

like image 390
enrico Avatar asked Oct 24 '12 14:10

enrico


1 Answers

I decided to keep all as clean as possible and go for the TreadScope implementation!

My solution is composed by:

  • the ThreadScope used to inject the same bean inside all the action that are running in the same thread.
  • an aspect asyncAspect that intercepts all the @async calls, the asyncAspectAfter() will go to clean up the threadLocal variable.
    This is requested by the spring handling of the @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.

ThreadScope

/**
 * 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();
    }
}

AsyncAspect

/**
 * 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();
    }

}

ApplicationContext

<!-- 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>
like image 105
enrico Avatar answered Oct 29 '22 15:10

enrico