如何通过 BeanManager 创建和销毁 CDI (Weld) 托管 Bean?
我正在尝试使用 BeanManager 而不是 Instance .select().get() 创建 CDI 托管 bean 的实例。
建议将此作为解决我在使用 ApplicationScoped beans 及其依赖项的垃圾收集时遇到的问题的解决方法 - 请参阅 CDI 应用程序和依赖范围可以共同影响垃圾收集? 了解背景信息和建议的解决方法。
如果您对 ApplicationScoped bean 使用 Instance 编程查找方法,则 Instance 对象以及从中获取的任何 bean 最终都依赖于 ApplicationScoped bean,因此共享它的生命周期。但是,如果您使用 BeanManager 创建 Bean,您就拥有 Bean 实例本身的句柄,并且显然可以显式销毁它,我理解这意味着它将被 GC 处理。
我当前的方法是在 BeanManagerUtil 类中创建 bean,并返回 Bean、实例和 CreationalContext 的复合对象:
public class BeanManagerUtil {
@Inject private BeanManager beanManager;
@SuppressWarnings("unchecked")
public <T> DestructibleBeanInstance<T> getDestructibleBeanInstance(final Class<T> type,
final Annotation... qualifiers) {
DestructibleBeanInstance<T> result = null;
Bean<T> bean = (Bean<T>) beanManager.resolve(beanManager.getBeans(type, qualifiers));
if (bean != null) {
CreationalContext<T> creationalContext = beanManager.createCreationalContext(bean);
if (creationalContext != null) {
T instance = bean.create(creationalContext);
result = new DestructibleBeanInstance<T>(instance, bean, creationalContext);
}
}
return result;
}
}
public class DestructibleBeanInstance<T> {
private T instance;
private Bean<T> bean;
private CreationalContext<T> context;
public DestructibleBeanInstance(T instance, Bean<T> bean, CreationalContext<T> context) {
this.instance = instance;
this.bean = bean;
this.context = context;
}
public T getInstance() {
return instance;
}
public void destroy() {
bean.destroy(instance, context);
}
}
由此,在调用代码中,我可以获取实际实例,将其放入映射中以供以后检索,并正常使用:
private Map<Worker, DestructibleBeanInstance<Worker>> beansByTheirWorkers =
new HashMap<Worker, DestructibleBeanInstance<Worker>>();
...
DestructibleBeanInstance<Worker> destructible =
beanUtils.getDestructibleBeanInstance(Worker.class, workerBindingQualifier);
Worker worker = destructible.getInstance();
...
当我完成它时,我可以查找可破坏的包装器并在其上调用 destroy() ,并且应该清理 bean 及其依赖项:
DestructibleBeanInstance<JamWorker> workerBean =
beansByTheirWorkers.remove(worker);
workerBean.destroy();
worker = null;
但是,在运行几个工作程序并离开我的 JBoss 之后(7.1.0.Alpha1-SNAPSHOT)大约 20 分钟,我可以看到 GC 发生,
2011.002: [GC
Desired survivor size 15794176 bytes, new threshold 1 (max 15)
1884205K->1568621K(3128704K), 0.0091281 secs]
但 JMAP 直方图仍然显示旧工作线程及其依赖实例挂在周围,未GCed。我缺少什么?
通过调试,我可以看到创建的bean的上下文字段具有正确的Worker类型的上下文,没有incompleteInstances和parentDependentInstances。它有许多 dependentInstances,这些实例与工作人员字段中的预期相同。
Worker 上的这些字段之一实际上是一个实例,当我将此字段与通过编程实例查找检索到的 Worker 的字段进行比较时,它们的 CreationalContext 构成略有不同。通过 Instance 查找的 Worker 上的 Instance 字段将 Worker 本身放在 incompleteInstances 下,而从 BeanManager 检索的 Worker 上的 Instance 字段则没有。它们都有相同的parentDependentInstances 和 dependentInstances。
这表明我没有正确镜像实例的检索。这是否会导致没有破坏?
最后,在调试时,我可以看到在 DestructibleBeanInstance.destroy() 中调用 bean.destroy(),这会传递到 ManagedBean.destroy,并且我可以看到依赖对象作为 .release() 的一部分被销毁。然而他们仍然没有被垃圾收集!
任何对此的帮助将非常感激!谢谢。
I'm trying to create instances of CDI managed beans using the BeanManager rather than Instance .select().get().
This was suggested as a workaround to an issue I've been having with ApplicationScoped beans and garbage collection of their dependents - see CDI Application and Dependent scopes can conspire to impact garbage collection? for background and this suggested workaround.
If you use the Instance programmatic lookup method on an ApplicationScoped bean, the Instance object and any beans you get from it are all ultimately dependent on the ApplicationScoped bean, and therefore share it's lifecycle. If you create beans with the BeanManager, however, you have a handle on the Bean instance itself, and apparently can explicitly destroy it, which I understand means it will be GCed.
My current approach is to create the bean within a BeanManagerUtil class, and return a composite object of Bean, instance, and CreationalContext:
public class BeanManagerUtil {
@Inject private BeanManager beanManager;
@SuppressWarnings("unchecked")
public <T> DestructibleBeanInstance<T> getDestructibleBeanInstance(final Class<T> type,
final Annotation... qualifiers) {
DestructibleBeanInstance<T> result = null;
Bean<T> bean = (Bean<T>) beanManager.resolve(beanManager.getBeans(type, qualifiers));
if (bean != null) {
CreationalContext<T> creationalContext = beanManager.createCreationalContext(bean);
if (creationalContext != null) {
T instance = bean.create(creationalContext);
result = new DestructibleBeanInstance<T>(instance, bean, creationalContext);
}
}
return result;
}
}
public class DestructibleBeanInstance<T> {
private T instance;
private Bean<T> bean;
private CreationalContext<T> context;
public DestructibleBeanInstance(T instance, Bean<T> bean, CreationalContext<T> context) {
this.instance = instance;
this.bean = bean;
this.context = context;
}
public T getInstance() {
return instance;
}
public void destroy() {
bean.destroy(instance, context);
}
}
From this, in the calling code, I can then get the actual instance, put it in a map for later retrieval, and use as normal:
private Map<Worker, DestructibleBeanInstance<Worker>> beansByTheirWorkers =
new HashMap<Worker, DestructibleBeanInstance<Worker>>();
...
DestructibleBeanInstance<Worker> destructible =
beanUtils.getDestructibleBeanInstance(Worker.class, workerBindingQualifier);
Worker worker = destructible.getInstance();
...
When I'm done with it, I can lookup the destructible wrapper and call destroy() on it, and the bean and its dependents should be cleaned up:
DestructibleBeanInstance<JamWorker> workerBean =
beansByTheirWorkers.remove(worker);
workerBean.destroy();
worker = null;
However, after running several workers and leaving my JBoss (7.1.0.Alpha1-SNAPSHOT) for 20 minutes or so, I can see GC occurring
2011.002: [GC
Desired survivor size 15794176 bytes, new threshold 1 (max 15)
1884205K->1568621K(3128704K), 0.0091281 secs]
Yet a JMAP histogram still shows the old workers and their dependent instances hanging around, unGCed. What am I missing?
Through debugging, I can see that the context field of the bean created has the contextual of the correct Worker type, no incompleteInstances and no parentDependentInstances. It has a number of dependentInstances, which are as expected from the fields on the worker.
One of these fields on the Worker is actually an Instance, and when I compare this field with that of a Worker retrieved via programmatic Instance lookup, they have a slightly different CreationalContext makeup. The Instance field on the Worker looked up via Instance has the worker itself under incompleteInstances, whereas the Instance field on the Worker retrieved from the BeanManager doesn't. They both have identical parentDependentInstances and dependentInstances.
This suggests to me that I haven't mirrored the retrieval of the instance correctly. Could this be contributing to the lack of destruction?
Finally, when debugging, I can see bean.destroy() being called in my DestructibleBeanInstance.destroy(), and this goes through to ManagedBean.destroy, and I can see dependent objects being destroyed as part of the .release(). However they still don't get garbage collected!
Any help on this would be very much appreciated! Thanks.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我会更改您粘贴的代码中的一些内容。
您可以通过调用
DestructibleBeanInstance
中已有的 CreationalContext 上的 release 方法,让一切按照您想要的方式正常工作,假设其中没有其他Bean
CreationalContext 会弄乱您的应用程序。首先尝试一下,看看它是否会把事情搞砸。I'd change a couple of things in the code you pasted.
BeanManager.createCreationContext(null)
which will give you essentially a dependent scope which you can release when you're done by callingCreationalContext.release()
.You may be able to get everything to work correctly the way you want by calling the release method on the CreationalContext you already have in the
DestructibleBeanInstance
, assuming there's no otherBeans
in that CreationalContext that would mess up your application. Try that first and see if it messes things up.仅当注入 bean 以外的类时才应传入 null。在你的例子中,你正在注入一个豆子。不过,我仍然希望 GC 在这种情况下能够工作,所以您能否在 Weld 问题跟踪器中提交 JIRA,其中包含测试用例和重现步骤?
Passing in null should only be done when you injecting some class other than a bean. In your case, you are injecting a bean. However I would still expect GC to work in this case, so could you file a JIRA in the Weld issue tracker with a test case and steps to reproduce?
解决问题的更好方法可能是使用动态代理来处理 bean 销毁。以编程方式获取 bean 类实例的代码如下:
这样用户代码就更简单了。它不必负责破坏。
此方法的局限性在于,不支持接收到的 beanType 不是接口且解析的 bean 类为
@Dependent
的情况。但很容易工作。只需使用一个接口即可。我测试了这段代码(使用 JBoss 7.1.1),它也适用于依赖的有状态会话 bean。
A nicer way solve your problem could be to use a dynamic proxy to handle the bean destruction. The code to obtain a bean class instance programaticaly would be:
This way the user code is simpler. It doesnt have to take care of the destruction.
The limitation of this approuch is that the case when the received beanType isn't an interface and the resolved bean class is
@Dependent
is not supported. But is easy to work arround. Just use an interface.I tested this code (with JBoss 7.1.1) and it works also for dependent stateful session beans.