【问题标题】:Spring Custom Scope Lifecycle Bean TerminationSpring 自定义范围生命周期 Bean 终止
【发布时间】:2018-05-22 23:40:34
【问题描述】:

问题:我如何告诉 Spring 一组具有自定义范围的 bean 都应该被视为垃圾,以便同一线程上的下一个请求不会重用它们的状态?

我做了什么:我在 Spring 中实现了一个自定义范围,以模拟请求范围 (HttpRequest) 的生命周期,但用于 TcpRequests。与here 发现的内容非常相似。

我发现的自定义范围的许多示例是原型或单例的变体,没有显式终止 bean,或者,它们基于本地线程或 ThreadScope,但它们没有描述告诉 Spring 生命周期已经结束并且所有的 bean 都应该被销毁。

我尝试过的事情(可能不正确):

  • Event + Listener 指示范围的开始和结束(这些发生在收到消息时和发送响应之前);在侦听器中,显式清除了范围,从而清除了线程本地实现(scope.clear())使用的整个映射。在测试中手动处理时,清除范围确实会导致对 context.getBean() 的下一次调用返回一个新实例,但是在单例类中自动装配的我的 bean 没有获得新的 bean——它一遍又一遍地使用同一个 bean .

  • Listener 实现:BeanFactoryPostProcessor、BeanPostProcessor、BeanFactoryAware、DisposableBean 并尝试在所有 Disposable bean 实例上调用 destroy(); this 之类的东西,但仅适用于我的自定义范围。这似乎失败了,尽管我在收到范围结束事件时调用了 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");
    }
}

【问题讨论】:

  • 很高兴看到一些代码来更好地了解为什么需要创建自定义范围。可能有不同的方法来获取特定于线程/请求/servlet 调用的上下文
  • @stringy05 感谢您的回复。我用比以前更多的上下文更新了这个问题。从消息被路由到“控制器”开始,它应该被认为是这个“请求”的开始,最后控制器将正式发送表示“请求”结束的事件。两者之间的所有内容都将共享同一个bean(requestWrapper),并且所述系统接收到的下一个“请求”将获得这些bean的新实例。我希望这有助于进一步阐明用例。对于不能更简洁,我深表歉意。
  • 所以请求包装器意味着有一些关于请求的上下文?也许源IP地址/端口或什么?注入它的问题正是您面临的问题 - 您的 @TransactionController 只会有一个实例,因此每个人都可能会改变同一个对象。即使使用 ThreadLocal,属性也绑定到 Thread 生命周期,而不是请求。如果它是请求上下文,我会在 ServiceActivator 方法或 @MessageMapping 上注入 @Header 注释,这非常灵活。
  • @stringy05 requestWrapper 将包含请求正文,这不仅仅是标题和上下文,它是我们必须做出的许多计算和决策的基础。我的理解是,通过使用 ScopedProxyMode.TARGET_CLASS 它会按预期解决,并且唯一自动连接的是代理。
  • 是的,网络感知并不是真正的问题,而是请求的范围 - 即使它是原始 TCP,也可能存在一些边界(即 2 \n,一些奇怪的字节编码),您可以使用它来利用开箱即用的弹簧集成工具。只要您可以控制 Thread 生命周期,将代理范围限定为 Thread 应该可以工作,但它可能会发明框架中已经存在的东西。 (我意识到我在争论实现选择,而不是回答问题:))

标签: java spring spring-boot destruction custom-scope


【解决方案1】:

经过几个月和几次尝试,我终于偶然发现了一些文章,它们为我指明了正确的方向。具体来说,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 的使用方面以及这可能会如何导致问题。

来源:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-03
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-21
    相关资源
    最近更新 更多