CDI - como obter referência a todos os Beans em determinado escopo?

Estou desenvolvendo uma estratégia com CDI para injeção de dependência do EntityManager que trabalha com um interceptador que é responsável pela abertura de transações. A solução já está funcionando, mas ela tem um problema para quem trabalha com mais de uma PU (persistence unit) ao mesmo tempo: o interceptador só é capaz de abrir a transação para apenas uma das PU. Conceitualmente, já resolvi o problema, mas tecnicamente ainda me foge como conseguir a solução. Explicarei o que preciso fazer e como faço hoje (funciona, mas tem efeito colateral).

Eu preciso, no interceptador, ter acesso a todas as instâncias do EntityManager (cada instância possui um qualificador diferente, permitindo um EntityManager para cada PU) que estão no escopo de request (lembrando que tenho um método produtor que joga os EntityManagers no escopo de request toda vez que faço o uso do EntityManager injetado).

Eu consegui fazer desta forma, mas o problema é que o container instância todos os meus EntityManagers qualificados, achando que eu estou querendo fazer uso deles, mas na verdade eu quero é saber os que já estão no escopo de request. Eu não quero que o container instâncie quem não está presente no escopo naquele momento. Esse código resolve meu problema, mas, como disse, cria desnecessariamente o EntityManager.

Este é o código:

@Inject @Any private Instance<EntityManager> entityManagers;

Estou precisando resolver isso para poder publicar a solução aqui no GUJ. Eu já havia publicado uma solução usando CDI, mas ela dependia de um plugin do OWB. A nova solução não depende de nada.

Pelo que conheço do CDI não tem como você injetar sem instanciar. De qualquer forma você injeta os EntityManagers em um componente para poder pegar qual PU você quer, sendo assim você tem o EntityManager alí. Assim o CDI está fazendo correto, injetando o componente já instanciado para você. Esse é o padrão da injeção de dependencia, entregar um componente pronto para você.

Eu acho que a tua estatégia está errada. Você deve injetar é a EntityManagerFactory, que é application-scoped. Quando vocẽ achar a sua EntityManagerFactory, peça a ela um EntityManager.

Outra forma, se você quer mesmo trabalhar com entity-manager, é usar uma espécie de lazy entity manager. Sendo assim você injeta um proxy ao invés do EntityManager de verdade, e quando alguém fazer algum acesso real ao EntityManager, você inicializa o proxy com o entity manager verdadeiro.

Talvez você não conheça a especificação em seus pormenores, mas eu posso perfeitamente injetar um bean sem que uma nova instância seja criada, contanto que este bean já esteja presente no escopo que estou querendo acessar, não é à toa que a especificação possui a anotação @New, que sinaliza para o ponto de injeção que você deseja uma nova instância e não uma presente no context/escopo corrente (alvo).

Sim, fica evidente que o CDI está fazendo corretamente, o que parece não ter ficado claro nesse post é que eu quero ter apenas acesso às instâncias que já estão em um contexto.

Talvez minha explicação tem levado a você realizar essa inferência prematura, mas meu EntityManagerFactory está em escopo de aplicação. É a ele que o método produtor pede um entityManager.

Eu entendi o que você quis dizer, mas fiquei confuso, uma vez que a injeção gera uma proxy, a não ser que ela esteja em um escopo do tipo @Dependent. Bom, o que posso garantir é que meus entityManager já são proxies. Qualquer coisa, dê uma olhada no capítulo 4.9. Client proxies, onde está escrito assim:

Resumindo: o que eu preciso é ter acesso a todos os beans que estão em um determinado escopo. Você sabe como?

garcia-jj, consegui o comportamento que estava querendo, mas não obtive o resultado esperado. Por quê? Justamente pelo fato do EntityManager ser uma proxy.

Quando a anotação responsável pela abertura da transação procurar um ou mais entityManagers no escopo X, ela não encontrará nenhum, porque, sendo proxies, os entityManagers só serão instanciados assim que forem acessados dentro do método anotado. O interceptador não tem como saber com antecedência qual entityManager de qual PU será acessado. Então, até que eu arrume outra solução, o interceptador, mesmo que você só queria abrir transação para uma única PU, abrirá automaticamente para todas PU (uma solução possível é passar parâmetro para a anotação com as PUs que eu desejo abrir transação, mas acho que é fazer leaking de uma informação que o desenvolvedor não deveria trabalhar, algo que deveria ser transparente).

Abraço

O que você pode fazer é pegar uma instância do BeanManager e nele fazer uma busca pelos beans. Basicamente você pede ao BeanManager instâncias para para determinada classe. Não olhei agora na documentação, mas creio que você possa filtrar por escopos e tudo mais.

BeanManager manager = (BeanManager) jndiContext.lookup("java:comp/BeanManager"); List<...> beans = manager.getBeans(EntityManagerclass);

Ou seja, ela já está instanciada, sendo assim o que eu falei está correto. Você não pode mesmo injetar algo sem instanciar. Se ele já existe no contexto, ele já foi instanciado.

garcia-jj, preciso ter acesso ao BeanManager para resolver outro problema diferente deste postado aqui, mas sempre tenho como retorno null.

Se puder, de uma olhadinha em http://www.guj.com.br/java/236954-cdi---manual-lookup-do-beanmanager-sempre-null

Abraço