异步服务中的Spring请求范围?在threadLocal变量上实现ThreadScope,并使用AsyncAspect进行清理

enr*_*ico 5 java spring asynchronous web-services aspectj

我有一个启动一系列操作的Web服务.从同一请求开始的所有这些操作都共享一个包含一些锁和一些其他信息的actionContext.

到目前为止,Spring会在使用" 请求 "范围的所有操作中注入此actionContext对象.

现在我正在实现一个Web套接字服务,以便能够跟踪这些操作的演变.
webService现在必须生成一个处理动作执行的线程,并将webSocket地址返回给调用的应用程序/用户.

该操作已使用spring的@async注释实现,并将在应用程序上下文中定义的线程池中运行.

问题:
使用这个新功能,' request '范围不再起作用,因为生成的线程不是请求(Spring将阻止执行).

处理这个问题的最佳解决方案是什么?

  • 实现我的Thread范围来处理actionContext并在所有操作中正确地注入它?
  • 手动传递actionContext(在我看来它看起来不太好)
  • 实现一个实例化webSocket的webService,并请求调用者首先调用它,然后将其引用传递给真正的webService?

谢谢您的帮助!

enr*_*ico 6

我决定尽可能保持干净,然后去实施TreadScope!

我的解决方案由:

  • ThreadScope用于注入了在同一个线程中运行的所有动作中的同一个bean.
  • asyncAspect截取所有@async调用的方面,asyncAspectAfter()将去清理threadLocal变量.
    这是通过@async注释方法的spring处理来请求的:因为Spring在池中运行方法,所以线程被重用而不被销毁.这意味着threadLocal变量将在线程中持久存在.

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

的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>
Run Code Online (Sandbox Code Playgroud)