2011-12-07 95 views
7

我正在嘗試使用BeanManager而不是實例.select()。get()來創建CDI託管bean的實例。如何通過BeanManager創建和銷燬CDI(焊接)託管Bean?

這是建議作爲解決方案,我已與ApplicationScoped豆和他們的家屬的垃圾回收問題 - 請參閱CDI Application and Dependent scopes can conspire to impact garbage collection?背景和此建議的解決方法。

如果您在ApplicationScoped bean上使用實例編程查找方法,那麼Instance對象和從它獲取的所有bean最終都依賴於ApplicationScoped bean,因此共享它的生命週期。但是,如果使用BeanManager創建Bean,則您有一個Bean實例本身的句柄,顯然可以明確地銷燬它,我知道這意味着它將被GCed。

我目前的做法是創建一個BeanManagerUtil類中的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(),和豆和應該清理其家屬:

DestructibleBeanInstance<JamWorker> workerBean = 
     beansByTheirWorkers.remove(worker); 
workerBean.destroy(); 
worker = null; 

然而,運行幾個工人,離開我的JBoss(7.1.0.Alpha1-快照)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字段在incompleteInstances下具有worker本身,而從BeanManager中檢索的Worker上的Instance字段沒有。它們都有相同的parentDependentInstances和dependentInstances。

這表明我沒有正確反映實例的檢索。這是否可以促成沒有破壞?

最後,在調試時,我可以看到在我的DestructibleBeanInstance.destroy()中調用了bean.destroy(),並且這通過ManagedBean.destroy,我可以看到依賴對象被作爲.release的一部分銷燬()。但是他們仍然沒有收集垃圾!

對此的任何幫助將非常感謝!謝謝。

回答

2

我會改變你粘貼的代碼中的一些東西。

  1. 使該類成爲一個普通的java類,沒有注入並傳入BeanManager。有些東西可能會搞砸了。這不太可能,但可能。
  2. 使用BeanManager.createCreationContext(null)創建一個新的CreationalContext,它將給你一個基本的依賴範圍,當你完成調用CreationalContext.release()時你可以釋放它。

您可以得到一切通過調用CreationalContext釋放方法,你已經在DestructibleBeanInstance正常工作,你所希望的方式,假設有在CreationalContext沒有其他Beans那會弄亂你的應用程序。先嚐試一下,看看它是否會讓事情變得糟糕。

+0

再次感謝Jason。我做了你所建議的更改,但仍未看到任何垃圾回收。然而,當我等待_full_ GC時,兩種方法都會導致收集對象 - 成功!以前在完整的GC中情況並非如此。 如果你有時間,請你能解釋'.createCreationContext(null)'和'.createCreationContext(bean)'之間的區別嗎?我已經在文檔中看到前者編寫擴展,但是我認爲這是爲了當bean類型的版本(如果你明白我的意思)不存在的時候?再次感謝您的幫助。 –

+0

根據規範,它爲您提供了一個非上下文的bean實例,所以除了創建它的那部分代碼之外,不應該有任何其他引用它的東西。我問皮特繆爾一些額外的見解,但我還沒有聽到。 – LightGuard

2

只能在注入bean以外的其他類時才傳入null。就你而言,你正在注入一個bean。然而,我仍然希望GC能夠在這種情況下工作,那麼你可以在Weld問題跟蹤器中提交一個JIRA和一個測試用例以及重現步驟嗎?

1

解決您的問題的更好的方法可能是使用動態代理來處理bean銷燬。獲得bean類實例程序的代碼是:

public static <B> B getBeanClassInstance(BeanManager beanManager, Class<B> beanType, Annotation... qualifiers) { 
    final B result; 
    Set<Bean<?>> beans = beanManager.getBeans(beanType, qualifiers); 
    if (beans.isEmpty()) 
     result = null; 
    else { 
     final Bean<B> bean = (Bean<B>) beanManager.resolve(beans); 
     if (bean == null) 
      result = null; 
     else { 
      final CreationalContext<B> cc = beanManager.createCreationalContext(bean); 
      final B reference = (B) beanManager.getReference(bean, beanType, cc); 
      Class<? extends Annotation> 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。

+0

非常好的主意,謝謝@Readren –