And*_*ton 7 java spring destruction spring-boot custom-scope
问题:我如何告诉 Spring 一组具有自定义范围的 bean 都应该被视为垃圾,以便同一线程上的下一个请求不会重用它们的状态?
我所做的:我在 Spring 中实现了一个自定义范围,以模拟请求范围(HttpRequest)的生命周期,但对于 TcpRequests。它与此处找到的内容非常相似。
我发现的许多自定义范围的例子是原型或单例的变体,没有显式终止 bean,或者,它们基于本地线程或 ThreadScope,但它们没有描述告诉 Spring 生命周期已经结束并且所有豆类应该销毁。
我尝试过的事情(可能不正确):
事件 + 侦听器指示作用域的开始和结束(这些发生在收到消息时和发送响应之前);在侦听器中,范围被明确清除,从而清除线程本地实现(scope.clear())使用的整个映射。在测试中手动处理时,清除范围确实会导致对 context.getBean() 的下一次调用返回一个新实例,但是我在单例类中自动装配的 bean 没有得到一个新 bean——它一遍又一遍地使用相同的 bean .
实现:BeanFactoryPostProcessor、BeanPostProcessor、BeanFactoryAware、DisposableBean 并尝试在所有 Disposable bean 实例上调用 destroy() 的侦听器;类似这样的东西,但仅适用于我的自定义范围。这似乎失败了,因为没有明确结束 bean 的生命周期,尽管我在收到范围结束事件时调用 customScope.clear() ;结束范围似乎并不转化为“结束与此范围关联的所有 bean”。
我已经广泛阅读了 Spring 文档,似乎很明显 Spring 不管理这些自定义 bean 的生命周期,因为它不知道何时或如何销毁它们,这意味着必须告诉它何时以及如何销毁摧毁他们;我试图阅读和理解 Spring 提供的 Session 和 Request 范围,以便我可以模仿这一点,但我遗漏了一些东西(同样,这些对我来说不可用,因为这不是一个网络感知应用程序,我不是使用 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>
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
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();
}
}
Run Code Online (Sandbox Code Playgroud)
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();
}
}
Run Code Online (Sandbox Code Playgroud)
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");
}
}
Run Code Online (Sandbox Code Playgroud)
经过几个月和几次尝试,我终于偶然发现了一些文章,它们为我指明了正确的方向。具体来说,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();
}
Run Code Online (Sandbox Code Playgroud)
此外,我在 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();
}
}
Run Code Online (Sandbox Code Playgroud)
虽然我不能绝对确定不会由于 ThreadLocal 使用而导致内存泄漏,但我相信这会按预期工作,因为我正在调用 ThreadLocal.remove(),这将删除对 ThreadScopeAttributes 对象的唯一引用,因此将其打开以进行垃圾收集。
欢迎任何改进,特别是在 ThreadLocal 的使用以及这可能会导致问题方面。
资料来源:
| 归档时间: |
|
| 查看次数: |
1983 次 |
| 最近记录: |