簡體   English   中英

Spring 自定義范圍生命周期 Bean 終止

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM