如何通过BeanManager创建和销毁CDI(焊接)托管Bean?

我正在尝试使用BeanManager而不是Instance .select()。get()创建CDI托管bean的实例。

这被建议作为我已经使用ApplicationScoped bean和他们的家属的垃圾收集的问题的解决方法 – 请参阅CDI应用程序和依赖范围可以合谋影响垃圾收集? 为背景和这建议的解决方法。

如果在ApplicationScoped bean上使用Instance编程查找方法,Instance对象和从中获取的任何bean最终都依赖于ApplicationScoped bean,因此共享它的生命周期。 但是,如果使用BeanManager创建bean,则会在Bean实例本身上设置句柄,并且显然可以明确地销毁它,我理解这意味着它将被GCed。

我目前的方法是在BeanManagerUtil类中创建bean,并返回Bean,实例和CreationalContext的复合对象:

public class BeanManagerUtil { @Inject private BeanManager beanManager; @SuppressWarnings("unchecked") public  DestructibleBeanInstance getDestructibleBeanInstance(final Class type, final Annotation... qualifiers) { DestructibleBeanInstance result = null; Bean bean = (Bean) beanManager.resolve(beanManager.getBeans(type, qualifiers)); if (bean != null) { CreationalContext creationalContext = beanManager.createCreationalContext(bean); if (creationalContext != null) { T instance = bean.create(creationalContext); result = new DestructibleBeanInstance(instance, bean, creationalContext); } } return result; } } public class DestructibleBeanInstance { private T instance; private Bean bean; private CreationalContext context; public DestructibleBeanInstance(T instance, Bean bean, CreationalContext 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> beansByTheirWorkers = new HashMap<Worker, DestructibleBeanInstance>(); ... DestructibleBeanInstance destructible = beanUtils.getDestructibleBeanInstance(Worker.class, workerBindingQualifier); Worker worker = destructible.getInstance(); ... 

当我完成它时,我可以查找可破坏的包装器并在其上调用destroy(),并且应该清理bean及其依赖项:

 DestructibleBeanInstance 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直方图仍然显示旧工人及其依赖的实例,unGCed。 我错过了什么?

通过调试,我可以看到创建的bean的上下文字段具有正确的Worker类型的上下文,没有incompleteInstances,也没有parentDependentInstances。 它有许多dependentInstances,它们与worker上的字段一样。

Worker上的其中一个字段实际上是一个实例,当我将此字段与通过编程实例查找检索到的Worker进行比较时,它们的CreationalContext组成略有不同。 通过Instance查找的Worker上的Instance字段将worker本身置于incompleteInstances下,而从BeanManager中检索的Worker上的Instance字段则没有。 它们都具有相同的parentDependentInstances和dependentInstances。

这告诉我,我没有正确反映实例的检索。 这会导致缺乏破坏吗?

最后,在调试时,我可以看到在我的DestructibleBeanInstance.destroy()中调用了bean.destroy(),这会进入ManagedBean.destroy,我可以看到依赖对象被作为.release()的一部分被销毁。 但他们仍然没有收集垃圾!

任何有关这方面的帮助将非常感谢! 谢谢。

我会在你粘贴的代码中改变一些东西。

  1. 使该类成为常规java类,无需注入并传入BeanManager。 有点像这样的东西搞乱了。 这不太可能,但可能。
  2. 使用BeanManager.createCreationContext(null)创建一个新的CreationalContext,它将为您提供一个依赖的作用域,当您通过调用CreationalContext.release()完成时可以释放该作用域。

您可以通过在DestructibleBeanInstance已经拥有的CreationalContext上调用release方法,使所有内容按照您想要的方式正常工作,假设CreationalContext中没有其他Beans会弄乱您的应用程序。 首先尝试一下,看看它是否会混乱。

只有在注入除bean之外的某些类时,才能传入null。 在你的情况下,你正在注入一个bean。 但是我仍然期望GC能够在这种情况下工作,那么您是否可以在Weld问题跟踪器中使用测试用例和重现步骤提交JIRA?

解决问题的更好方法可能是使用动态代理来处理bean破坏。 获取bean类实例programaticaly的代码是:

 public static  B getBeanClassInstance(BeanManager beanManager, Class beanType, Annotation... qualifiers) { final B result; Set> beans = beanManager.getBeans(beanType, qualifiers); if (beans.isEmpty()) result = null; else { final Bean bean = (Bean) beanManager.resolve(beans); if (bean == null) result = null; else { final CreationalContext cc = beanManager.createCreationalContext(bean); final B reference = (B) beanManager.getReference(bean, beanType, cc); Class scope = bean.getScope(); if (scope.equals(Dependent.class)) { if (beanType.isInterface()) { result = (B) Proxy.newProxyInstance(bean.getBeanClass().getClassLoader(), new Class[] { beanType, Finalizable.class }, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("finalize")) { bean.destroy(reference, cc); } try { return method.invoke(reference, args); } catch (InvocationTargetException e) { throw e.getCause(); } } }); } else throw new IllegalArgumentException("If the resolved bean is dependent scoped then the received beanType should be an interface in order to manage the destruction of the created dependent bean class instance."); } else result = reference; } } return result; } interface Finalizable { void finalize() throws Throwable; } 

这样用户代码就更简单了。 它不需要照顾破坏。 此approuch的限制是不支持接收的beanType不是接口且解析的bean类是@Dependent情况。 但是很容易在周围工作。 只需使用一个界面。 我测试了这段代码(使用JBoss 7.1.1),它也适用于依赖的有状态会话bean。