[英]Spring Custom Scope Lifecycle Bean Termination
問題:我如何告訴 Spring 一組具有自定義范圍的 bean 都應該被視為垃圾,以便同一線程上的下一個請求不會重用它們的狀態?
我所做的:我在 Spring 中實現了一個自定義范圍,以模擬請求范圍 (HttpRequest) 的生命周期,但用於 TcpRequests。 它與這里發現的非常相似。
我發現的自定義范圍的許多示例是原型或單例的變體,沒有顯式終止 bean,或者,它們基於本地線程或 ThreadScope,但它們沒有描述告訴 Spring 生命周期已經結束並且所有豆類應該被銷毀。
我嘗試過的事情(可能不正確):
Event + Listener 指示范圍的開始和結束(這些發生在收到消息時和發送響應之前); 在偵聽器中,顯式清除了范圍,從而清除了線程本地實現(scope.clear())使用的整個映射。 在測試中手動處理時,清除范圍確實會導致對 context.getBean() 的下一次調用返回一個新實例,但是在單例類中自動裝配的我的 bean 沒有獲得新的 bean——它一遍又一遍地使用同一個 bean .
實現的監聽器:BeanFactoryPostProcessor、BeanPostProcessor、BeanFactoryAware、DisposableBean 並嘗試在所有 Disposable bean 實例上調用 destroy(); 像這樣的東西,但僅適用於我的自定義范圍。 這似乎失敗了,盡管我在收到范圍結束事件時調用了 customScope.clear() ,但沒有任何東西明確結束 bean 的生命周期; 結束范圍似乎並不意味着“結束與此范圍關聯的所有 bean”。
我已經廣泛閱讀了 Spring 文檔,似乎很清楚 Spring 不管理這些自定義 bean 的生命周期,因為它不知道何時或如何銷毀它們,這意味着必須告知它何時以及如何銷毀摧毀他們; 我試圖閱讀和理解 Spring 提供的 Session 和 Request 范圍,以便我可以模仿這一點,但遺漏了一些東西(同樣,這些對我來說不可用,因為這不是一個支持 web 的應用程序,我不是使用 HttpRequests,這是對我們應用程序結構的重大更改)
有沒有人能指出我正確的方向?
我有以下代碼示例:
XML 上下文配置:
<int-ip:tcp-connection-factory id="serverConnectionFactory" type="server" port="19000"
serializer="javaSerializer" deserializer="javaDeserializer"/>
<int-ip:tcp-inbound-gateway id="inGateway" connection-factory="serverConnectionFactory"
request-channel="incomingServerChannel" error-channel="errorChannel"/>
<int:channel id="incomingServerChannel" />
<int:chain input-channel="incomingServerChannel">
<int:service-activator ref="transactionController"/>
</int:chain>
TransactionController(處理請求) :
@Component("transactionController")
public class TransactionController {
@Autowired
private RequestWrapper requestWrapper;
@ServiceActivator
public String handle(final Message<?> requestMessage) {
// object is passed around through various phases of application
// object is changed, things are added, and finally, a response is generated based upon this data
tcpRequestCompletePublisher.publishEvent(requestWrapper, "Request lifecycle complete.");
return response;
}
}
TcpRequestScope(范圍定義) :
@Component
public class TcpRequestScope implements Scope {
private final ThreadLocal<ConcurrentHashMap<String, Object>> scopedObjects =
new InheritableThreadLocal<ConcurrentHashMap<String, Object>>({
@Override
protected ConcurrentHashMap<String, Object> initialValue(){
return new ConcurrentHashMap<>();
}
};
private final Map<String, Runnable> destructionCallbacks =
Collections.synchronizedMap(new HashMap<String, Runnable>());
@Override
public Object get(final String name, final ObjectFactory<?> objectFactory) {
final Map<String, Object> scope = this.scopedObjects.get();
Object object = scope.get(name);
if (object == null) {
object = objectFactory.getObject();
scope.put(name, object);
}
return object;
}
@Override
public Object remove(final String name) {
final Map<String, Object> scope = this.scopedObjects.get();
return scope.remove(name);
}
@Override
public void registerDestructionCallback(final String name, final Runnable callback) {
destructionCallbacks.put(name, callback);
}
@Override
public Object resolveContextualObject(final String key) {
return null;
}
@Override
public String getConversationId() {
return String.valueOf(Thread.currentThread().getId());
}
public void clear() {
final Map<String, Object> scope = this.scopedObjects.get();
scope.clear();
}
}
TcpRequestCompleteListener :
@Component
public class TcpRequestCompleteListener implements ApplicationListener<TcpRequestCompleteEvent> {
@Autowired
private TcpRequestScope tcpRequestScope;
@Override
public void onApplicationEvent(final TcpRequestCompleteEvent event) {
// do some processing
// clear all scope related data (so next thread gets clean slate)
tcpRequestScope.clear();
}
}
RequestWrapper(我們在整個請求生命周期中使用的對象) :
@Component
@Scope(scopeName = "tcpRequestScope", proxyMode =
ScopedProxyMode.TARGET_CLASS)
public class RequestWrapper implements Serializable, DisposableBean {
// we have many fields here which we add to and build up during processing of request
// actual request message contents will be placed into this class and used throughout processing
@Override
public void destroy() throws Exception {
System.out.print("Destroying RequestWrapper bean");
}
}
經過幾個月和幾次嘗試,我終於偶然發現了一些文章,這些文章為我指明了正確的方向。 具體來說,David Winterfeldt 的博客文章中的參考資料幫助我理解了我之前讀過的SimpleThreadScope ,並且很清楚 Spring 在其生命周期完成后不會嘗試清除范圍,但是,他的文章展示了缺少的鏈接我見過的所有以前的實現。
具體來說,缺少的鏈接是在他的實現中對 ThreadScope 類中的 ThreadScopeContextHolder 的靜態引用(在我上面提出的實現中,我稱之為我的 TcpRequestScope;這個答案的其余部分使用 David Winterfeldt 的術語,因為他的參考文檔將被證明是最有用的,並且他寫了它) .
在仔細檢查自定義線程作用域模塊后,我注意到我缺少 ThreadScopeContextHolder,它包含對 ThreadLocal 的靜態引用,其中包含一個 ThreadScopeAttributes 對象,該對象包含范圍內的對象。
David 的實現和我的最后一個實現之間的一些細微差別是,在 Spring Integration 發送響應之后,我使用 ChannelInterceptor 來清除線程范圍,因為我使用的是 Spring Integration。 在他的示例中,他擴展了線程,其中包括對上下文持有者的調用作為 finally 塊的一部分。
我如何清除范圍屬性/bean:
public class ThreadScopeInterceptor extends ChannelInterceptorAdapter {
@Override
public void afterSendCompletion(final Message<?> message, final MessageChannel channel, final boolean sent,
@Nullable final Exception exception) {
// explicitly clear scope variables
ThreadScopeContextHolder.clearThreadScopeState();
}
此外,我在 ThreadScopeContextHolder 中添加了一個清除 ThreadLocal 的方法:
public class ThreadScopeContextHolder {
// see: reference document for complete ThreadScopeContextHolder class
/**
* Clears all tcpRequest scoped beans which are stored on the current thread's ThreadLocal instance by calling
* {@link ThreadLocal#remove()}.
*/
public static void clearThreadScopeState() {
threadScopeAttributesHolder.remove();
}
}
雖然我不確定由於使用 ThreadLocal 不會導致內存泄漏,但我相信這將按預期工作,因為我正在調用 ThreadLocal.remove(),這將刪除對 ThreadScopeAttributes 對象的唯一引用,因此打開它進行垃圾收集。
歡迎任何改進,特別是在 ThreadLocal 的使用方面以及這可能會如何導致問題。
資料來源:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.