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

时间:2018-05-22 23:40:34

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

问题:我怎么能告诉Spring一组具有自定义作用域的bean应该都被认为是垃圾,以便同一个线程上的下一个请求不会重用它们的状态? / p>

我做了什么:我在Spring中实现了一个自定义作用域,以模仿请求作用域(HttpRequest)的生命周期,但是对于TcpRequests。它与here非常相似。

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

我尝试过的事情(也许是错误的):

  • 事件+监听器指示范围的开始和结束(这些是在收到消息时和发送响应之前发生的);在侦听器中,显式清除作用域,清除线程本地实现(scope.clear())使用的整个映射。清除作用域会导致下一次调用context.getBean()在测试中手动处理时返回一个新实例,但我在单例类中自动装配的bean不会获得新的bean - 它反复使用相同的bean

  • 监听器,它实现:BeanFactoryPostProcessor,BeanPostProcessor,BeanFactoryAware,DisposableBean,并尝试在所有Disposable bean实例上调用destroy();像this这样的东西,但仅适用于我的自定义范围。这似乎失败了,因为当我收到范围结束事件时,我正在调用customScope.clear()这一事实并没有明确地结束bean的生命周期。结束范围似乎并没有转化为"结束与此范围相关的所有bean"。

  • 我已经广泛阅读了Spring文档,并且很明显Spring并没有管理这些自定义bean的生命周期,因为它不知道它们应该何时或如何应用被摧毁,这意味着必须告诉它何时以及如何摧毁它们;我试图阅读并理解Spring提供的Session和Request范围,以便我可以模仿这个但是我遗漏了一些东西(再次,这些都不适用于我,因为这不是一个支持Web的应用程序而且我&# 39;我没有使用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");
    }
}

1 个答案:

答案 0 :(得分:0)

经过几个月的尝试,我终于偶然发现了一些文章,这些文章为我指明了正确的方向。具体来说,David Winterfeldt的博客文章中的参考文献帮助我理解了我先前阅读的SimpleThreadScope,并且充分意识到了Spring并未尝试在其生命周期完成后清除范围的事实,但是,他的文章证明了这一点。我所见过的所有先前实现的缺失链接。

具体来说,缺少的链接是在其实现中对ThreadScope类中ThreadScopeContextHolder的静态引用(在我上面建议的实现中,我称为TcpRequestScope;该答案的其余部分使用David Winterfeldt的术语,因为他的参考文档将被证明是最有用的,写下来的。)

在仔细检查自定义线程范围模块时,我发现我缺少ThreadScopeContextHolder,它包含对ThreadLocal的静态引用,该线程包含ThreadScopeAttributes对象,该对象保存了作用域内的对象。

David的实现与我的最后一个实现之间的一些细微差别是,在Spring Integration发送响应之后,由于我使用的是Spring Integration,因此我使用ChannelInterceptor来清除线程范围。在他的示例中,他扩展了线程,其中包括对到上下文持有者的调用,作为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的用法以及这可能会引起问题的方面。

来源: