简体   繁体   English

Spring 自定义范围生命周期 Bean 终止

[英]Spring Custom Scope Lifecycle Bean Termination

Question : How can I tell Spring that a set of beans with a custom scope should all be considered garbage, so that the next request on the same thread would not re-use their state?问题:我如何告诉 Spring 一组具有自定义范围的 bean 都应该被视为垃圾,以便同一线程上的下一个请求不会重用它们的状态?

What I've done : I've implemented a custom scope in Spring, to mimic the lifecycle of a request scope (HttpRequest) but for TcpRequests.我所做的:我在 Spring 中实现了一个自定义范围,以模拟请求范围 (HttpRequest) 的生命周期,但用于 TcpRequests。 It is very similar what is found here .它与这里发现的非常相似。

Many examples of custom scopes which I am finding are variants on prototype or singleton with no explicit termination of beans occurring, or, alternatively, they based around a thread local or ThreadScope but they do not describe telling Spring that the lifecycle has ended and that all beans should be destroyed.我发现的自定义范围的许多示例是原型或单例的变体,没有显式终止 bean,或者,它们基于本地线程或 ThreadScope,但它们没有描述告诉 Spring 生命周期已经结束并且所有豆类应该被销毁。

Things I have tried (perhaps incorrectly):我尝试过的事情(可能不正确):

  • Event + Listener to indicate the beginning and end of the scope (these occur when message is received and just before response is sent); Event + Listener 指示范围的开始和结束(这些发生在收到消息时和发送响应之前); in listener, the scope is explicitly cleared which clears the entire map used by the thread local implementation (scope.clear()).在侦听器中,显式清除了范围,从而清除了线程本地实现(scope.clear())使用的整个映射。 Clearing scope does result in the next call to context.getBean() returning a new instance when handled manually in tests, but my bean which is autowired in a singleton class does not get a new bean--it uses the same bean over and over.在测试中手动处理时,清除范围确实会导致对 context.getBean() 的下一次调用返回一个新实例,但是在单例类中自动装配的我的 bean 没有获得新的 bean——它一遍又一遍地使用同一个 bean .

  • Listener which implements: BeanFactoryPostProcessor, BeanPostProcessor, BeanFactoryAware, DisposableBean and attempt to call destroy() on all Disposable bean instances;实现的监听器:BeanFactoryPostProcessor、BeanPostProcessor、BeanFactoryAware、DisposableBean 并尝试在所有 Disposable bean 实例上调用 destroy(); something like this but for my custom scope only.这样的东西,但仅适用于我的自定义范围。 This seems to fail in that nothing is explicitly ending the lifecycle of the beans, despite the fact that I'm calling customScope.clear() when I receive the scope ending event;这似乎失败了,尽管我在收到范围结束事件时调用了 customScope.clear() ,但没有任何东西明确结束 bean 的生命周期; ending the scope doesn't seem to translate to "end all beans associated with this scope".结束范围似乎并不意味着“结束与此范围关联的所有 bean”。

  • I've read Spring documentation extensively and it seems to be clear that Spring doesn't manage the lifecycle of these custom beans in that it doesn't know when or how they should be destroyed, which means that it must be told when and how to destroy them;我已经广泛阅读了 Spring 文档,似乎很清楚 Spring 不管理这些自定义 bean 的生命周期,因为它不知道何时或如何销毁它们,这意味着必须告知它何时以及如何销毁摧毁他们; I've tried to read and understand the Session and Request scopes as provided by Spring so that I can mimic this but am missing something (again, these are not available to me since this is not a web-aware application and I'm not using HttpRequests and it is a non-trivial change in our application's structure)我试图阅读和理解 Spring 提供的 Session 和 Request 范围,以便我可以模仿这一点,但遗漏了一些东西(同样,这些对我来说不可用,因为这不是一个支持 web 的应用程序,我不是使用 HttpRequests,这是对我们应用程序结构的重大更改)

Is anyone out there able to point me in the right direction?有没有人能指出我正确的方向?

I have the following code examples:我有以下代码示例:

Xml Context Configuration : 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 (handles request) : 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 (scope definition) : 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 : 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 (object we use throughout request lifecycle) : 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");
    }
}

After many months and a few more attempts, I finally stumbled across some articles which pointed me in the right direction.经过几个月和几次尝试,我终于偶然发现了一些文章,这些文章为我指明了正确的方向。 Specifically, references in David Winterfeldt's blog post helped me understand the SimpleThreadScope which I had previously read, and was well aware of the fact that Spring makes no attempt to clear the scope after its lifecycle is complete, however, his article demonstrated the missing link for all previous implementations I had seen.具体来说,David Winterfeldt 的博客文章中的参考资料帮助我理解了我之前读过的SimpleThreadScope ,并且很清楚 Spring 在其生命周期完成后不会尝试清除范围,但是,他的文章展示了缺少的链接我见过的所有以前的实现。

Specifically, the missing links were static references to ThreadScopeContextHolder in ThreadScope class in his implementation (in my proposed implementation above I called mine TcpRequestScope; the rest of this answer uses David Winterfeldt's terms since his reference documentation will prove most useful, and he wrote it).具体来说,缺少的链接是在他的实现中对 ThreadScope 类中的 ThreadScopeContextHolder 的静态引用(在我上面提出的实现中,我称之为我的 TcpRequestScope;这个答案的其余部分使用 David Winterfeldt 的术语,因为他的参考文档将被证明是最有用的,并且他写了它) .

Upon closer inspection of the Custom Thread Scope Module I noticed I was missing the ThreadScopeContextHolder, which contained a static reference to a ThreadLocal, which contains a ThreadScopeAttributes object which is what holds in-scope objects.在仔细检查自定义线程作用域模块后,我注意到我缺少 ThreadScopeContextHolder,它包含对 ThreadLocal 的静态引用,其中包含一个 ThreadScopeAttributes 对象,该对象包含范围内的对象。

Some minor differences between David's implementation and my final one were, after Spring Integration sends its response, I use a ChannelInterceptor to clear the thread scope, since I'm using Spring Integration. David 的实现和我的最后一个实现之间的一些细微差别是,在 Spring Integration 发送响应之后,我使用 ChannelInterceptor 来清除线程范围,因为我使用的是 Spring Integration。 In his examples, he extended threads which included a call to the context holder as part of a finally block.在他的示例中,他扩展了线程,其中包括对上下文持有者的调用作为 finally 块的一部分。

How I'm clearing the scope attributes / beans:我如何清除范围属性/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();
}

Additionally, I added a method in the ThreadScopeContextHolder which clears the ThreadLocal:此外,我在 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();
    }

}

While I'm not absolutely certain that there will not be memory leaks due to the ThreadLocal usage, I believe this will work as expected since I am calling ThreadLocal.remove(), which will remove the only reference to the ThreadScopeAttributes object, and therefore open it up to garbage collection.虽然我不确定由于使用 ThreadLocal 不会导致内存泄漏,但我相信这将按预期工作,因为我正在调用 ThreadLocal.remove(),这将删除对 ThreadScopeAttributes 对象的唯一引用,因此打开它进行垃圾收集。

Any improvements are welcomed, especially in terms of usage of ThreadLocal and how this might cause problems down the road.欢迎任何改进,特别是在 ThreadLocal 的使用方面以及这可能会如何导致问题。

Sources:资料来源:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM