CDI – ApplicationScoped mas configurado

Problema

Usando o CDI, quero produzir beans @ApplicationScoped .

Além disso, quero fornecer uma anotação de configuração para os pontos de injeção, por exemplo:

 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface Configuration { String value(); } 

Eu não quero escrever um produtor separado para cada possibilidade diferente de value .


Abordagem

A maneira usual seria criar um produtor e manipular as annotations do ponto de injeção:

 @Produces public Object create(InjectionPoint injectionPoint) { Configuration annotation = injectionPoint.getAnnotated().getAnnotation(Configuration .class); ... } 

Por consequência, o feijão não pode mais ter um escopo de aplicação, porque cada ponto de injeção pode ser possivelmente diferente (ponto de injeção de parâmetro para produtores não funciona para produtores anotados de @AplicationScoped ).

Então esta solução não funciona.


Questão

Eu precisaria de uma possibilidade de que os pontos de injeção com o mesmo valor obtenham a mesma instância de bean.

Existe um caminho CDI embutido? Ou eu preciso de alguma forma “lembrar” os feijões eu mesmo em uma lista, por exemplo, na class que contém o produtor?

O que eu preciso é basicamente uma instância de ApplicationScoped para cada value diferente.

O que você tenta alcançar não é um recurso para o CDI, mas graças à sua SPI e a uma extensão portátil você pode conseguir o que precisa.

Esta extensão analisará todos os pontos de injeção com um determinado tipo, obterá as annotations de @Configuration em cada um deles e criará um bean em applicationScoped para cada valor diferente do valor de membro value() na anotação.

Como você registrará vários beans com o mesmo tipo, você terá primeiro que transformar sua anotação em um qualificador

 @Qualifier @Target({TYPE, METHOD, PARAMETER, FIELD}) @Retention(RUNTIME) @Documented public @interface Configuration { String value(); } 

Abaixo da class a ser usada para criar suas instâncias de bean:

 @Vetoed public class ConfiguredService { private String value; protected ConfiguredService() { } public ConfiguredService(String value) { this.value = value; } public String getValue() { return value; } } 

Observe a anotação @Vetoed para se certificar de que o CDI não vai pegar essa class para criar um bean como faremos por nós mesmos. Esta class tem que ter um construtor padrão sem parâmetro para ser usado como class de um bean de passivação (no escopo da aplicação)

Então você precisa declarar a class do seu bean personalizado. Vê-lo como uma fábrica e detentor de metadados (escopo, qualificadores, etc …) do seu bean.

 public class ConfiguredServiceBean implements Bean, PassivationCapable { static Set types; private final Configuration configuration; private final Set qualifiers = new HashSet<>(); public ConfiguredServiceBean(Configuration configuration) { this.configuration = configuration; qualifiers.add(configuration); qualifiers.add(new AnnotationLiteral() { }); } @Override public Class getBeanClass() { return ConfiguredService.class; } @Override public Set getInjectionPoints() { return Collections.EMPTY_SET; } @Override public boolean isNullable() { return false; } @Override public Set getTypes() { return types; } @Override public Set getQualifiers() { return qualifiers; } @Override public Class getScope() { return ApplicationScoped.class; } @Override public String getName() { return null; } @Override public Set> getStereotypes() { return Collections.EMPTY_SET; } @Override public boolean isAlternative() { return false; } @Override public ConfiguredService create(CreationalContext creationalContext) { return new ConfiguredService(configuration.value()); } @Override public void destroy(ConfiguredService instance, CreationalContext creationalContext) { } @Override public String getId() { return getClass().toString() + configuration.value(); } } 

Observe que o qualificador é o único parâmetro, permitindo que vinculemos o conteúdo do qualificador à instância no método create() .

Finalmente, você criará a extensão que registrará seus beans de uma coleção de pontos de injeção.

 public class ConfigurationExtension implements Extension { private Set configurations = new HashSet<>(); public void retrieveTypes(@Observes ProcessInjectionPoint pip, BeanManager bm) { InjectionPoint ip = pip.getInjectionPoint(); if (ip.getAnnotated().isAnnotationPresent(Configuration.class)) configurations.add(ip.getAnnotated().getAnnotation(Configuration.class)); else pip.addDefinitionError(new IllegalStateException("Service should be configured")); } public void createBeans(@Observes AfterBeanDiscovery abd, BeanManager bm) { ConfiguredServiceBean.types = bm.createAnnotatedType(ConfiguredService.class).getTypeClosure(); for (Configuration configuration : configurations) { abd.addBean(new ConfiguredServiceBean(configuration)); } } } 

Essa extensão é ativada incluindo seu nome de class totalmente qualificado no arquivo de texto META-INF/services/javax.enterprise.inject.spi.Extension .

Há outra maneira de criar seu recurso com uma extensão, mas eu tentei dar-lhe um código trabalhando a partir do CDI 1.0 (exceto pela anotação @Vetoed ).

Você pode encontrar o código-fonte desta extensão no meu CDI Sandbox no Github .

O código é bastante simples, mas não hesite se tiver dúvidas.