The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

Guia de Integração CDI

O ArC, o contêiner CDI no Quarkus, é inicializado no momento da construção. Para integrar-se ao contêiner, podem ser usadas Extensões Compatíveis com o CDI Build , bem como uma API de extensão específica do Quarkus. As extensões portáteis CDI não são e não podem ser suportadas. Este guia foca na API de extensões específicas do Quarkus.

O contêiner é inicializado em várias fases. De uma perspectiva de alto nível, essas fases são as seguintes:

  1. Inicialização

  2. Descoberta do Bean

  3. Registo de componentes sintéticos

  4. Validação

Na fase de inicialização , o trabalho preparatório está sendo realizado e os contextos personalizados são registrados. A descoberta de beans é o processo em que o contêiner analisa todas as classes da aplicação, identifica os beans e os conecta com base nos metadados fornecidos. Posteriormente, as extensões podem registrar componentes sintéticos . Os atributos desses componentes são totalmente controlados pelas extensões, ou seja, não são derivados de uma classe existente. Por fim, a implementação é validada . Por exemplo, o contêiner valida cada ponto de injeção na aplicação e falha a construção se não houver nenhum bean que satisfaça o tipo e os qualificadores exigidos.

Você pode ver mais informações sobre o bootstrap ativando log adicional. Basta executar a construção do Maven com -X ou --debug e procurar as linhas que contêm io.quarkus.arc. No modo de desenvolvimento, você pode usar quarkus.log.category."io.quarkus.arc.processor".level=DEBUG e dois endpoints especiais também são registrados automaticamente para fornecer algumas informações básicas de depuração no formato JSON.

As etapas de construção do Quarkus podem produzir e consumir vários itens de construção e se conectar a cada fase. Nas seções a seguir, descreveremos todos os itens de construção relevantes e cenários comuns.

1. Fontes de metadados

As classes e as anotações são a principal fonte de metadados no nível do bean. Os metadados iniciais são lidos do índice do arquivo do bean , um índice Jandex imutável que é criado a partir de várias fontes durante a descoberta de bean . No entanto, as extensões podem adicionar, remover ou transformar os metadados em determinados estágios do bootstrap. Além disso, as extensões também podem registrar componentes sintéticos . Esse é um aspecto importante a ser percebido ao integrar componentes CDI no Quarkus.

Dessa forma, as extensões podem transformar classes, que de outra forma seriam ignoradas, em beans e vice-versa. Por exemplo, uma classe que declara um método @Scheduled é sempre registrada como um bean, mesmo que não tenha sido anotada com uma anotação de definição de bean e, normalmente, seria ignorada.

2. Caso de uso - Minha classe não é reconhecida como um Bean

Uma UnsatisfiedResolutionException indica um problema durante a resolução de tipagem segura . Às vezes, um ponto de injeção não pode ser satisfeito, mesmo que haja uma classe no classpath que pareça ser elegível para injeção. Há vários motivos pelos quais uma classe não é reconhecida e também várias maneiras de corrigi-los. Na primeira etapa, devemos identificar o motivo .

2.1. Razão 1: Classe Não Está Descoberta

O Quarkus tem uma descoberta simplificada . Pode acontecer que a classe não faça parte do índice da aplicação. Por exemplo, as classes do módulo de tempo de execução de uma extensão do Quarkus não são indexadas automaticamente.

Solução : Use o AdditionalBeanBuildItem . Esse item de construção pode ser usado para especificar uma ou mais classes adicionais a serem analisadas durante a descoberta. As classes de bean adicionais são adicionadas de forma transparente ao índice da aplicação processado pelo contêiner.

Não é possível ativar/desativar condicionalmente beans adicionais através das anotações @IfBuildProfile, @UnlessBuildProfile, @IfBuildProperty e @UnlessBuildProperty, conforme descrito em Ativando Beans para o Perfil de Construção Quarkus e Ativando Beans para as Propriedades de Construção Quarkus. As extensões devem inspecionar a configuração ou o perfil atual e apenas produzir um AdditionalBeanBuildItem se for realmente necessário.
Exemplo de AdditionalBeanBuildItem
@BuildStep
AdditionalBeanBuildItem additionalBeans() {
     return new AdditionalBeanBuildItem(SmallRyeHealthReporter.class, HealthServlet.class)); (1)
}
1 AdditionalBeanBuildItem.Builder pode ser utilizado para casos de uso mais complexos.

As classes de bean adicionadas por meio do AdditionalBeanBuildItem são removíveis por padrão. Se o contêiner as considerar não utilizadas , elas serão simplesmente ignoradas. No entanto, é possível usar o método AdditionalBeanBuildItem.Builder.setUnremovable() para instruir o contêiner a nunca remover as classes de bean registradas por meio desse item de construção. Consulte também Removendo Beans Não Usados e Razão 3: A Classe Foi Descoberta e Tem uma Anotação que Define o Bean, mas Foi Removida para obter mais detalhes.

Também é possível definir o escopo padrão por meio de AdditionalBeanBuildItem.Builder#setDefaultScope() . O escopo padrão só é usado se não houver um escopo declarado na classe do bean.

Se não for especificado um escopo padrão, é utilizado o pseudo escopo @Dependent.

2.2. Motivo 2: A Classe É Descoberta mas Não Tem uma Anotação de Definição de Bean

No Quarkus, a aplicação é representada por um único bean archive com o modo de descoberta de bean anotado. Portanto, as classes de bean que não têm uma anotação de definição de bean são ignoradas. As anotações de definição de bean são declaradas no nível da classe e incluem escopos, estereótipos e @Interceptor .

Solução 1: Utilizar o AutoAddScopeBuildItem. Este item de construção pode ser utilizado para adicionar um escopo a uma classe que satisfaça determinadas condições.

Exemplo de AutoAddScopeBuildItem
@BuildStep
AutoAddScopeBuildItem autoAddScope() {
   return AutoAddScopeBuildItem.builder().containsAnnotations(SCHEDULED_NAME, SCHEDULES_NAME) (1)
      .defaultScope(BuiltinScope.SINGLETON) (2)
      .build();
}
1 Encontrar todas as classes anotadas com @Scheduled.
2 Adicione @Singleton como escopo padrão. As classes já anotadas com um escopo são ignoradas automaticamente.

Solução 2: Se precisar processar classes anotadas com uma anotação específica, é possível estender o conjunto de anotações que definem beans através do BeanDefiningAnnotationBuildItem.

Exemplo de BeanDefiningAnnotationBuildItem
@BuildStep
BeanDefiningAnnotationBuildItem additionalBeanDefiningAnnotation() {
   return new BeanDefiningAnnotationBuildItem(Annotations.GRAPHQL_API); (1)
}
1 Adicionar org.eclipse.microprofile.graphql.GraphQLApi ao conjunto de anotações de definição de bean.

As classes de beans adicionadas por meio do BeanDefiningAnnotationBuildItem são não removíveis por padrão, ou seja, os beans resultantes não devem ser removidos, mesmo que sejam considerados não utilizados. Entretanto, você pode alterar o comportamento padrão. Consulte também Removendo Beans Não Utilizados e Razão 3: A Classe Foi Descoberta e Tem uma Anotação que Define o Bean, mas Foi Removida para mais detalhes.

Também é possível especificar o escopo padrão. O escopo padrão é usado somente se não houver escopo declarado na classe do bean.

Se não for especificado um escopo padrão, é utilizado o pseudo escopo @Dependent.

2.3. Motivo 3: A Classe Foi Descoberta e Tem uma Anotação que Define o Bean, mas Foi Removida

Por padrão, o contêiner tenta remover todos os beans não utilizados durante a construção. Essa otimização permite a eliminação de código morto no nível do framework . Em alguns casos especiais, não é possível identificar corretamente um bean não utilizado. Em particular, o Quarkus ainda não é capaz de detectar o uso do método estático CDI.current() . As extensões podem eliminar possíveis falsos positivos produzindo um UnremovableBeanBuildItem .

Exemplo de UnremovableBeanBuildItem
@BuildStep
UnremovableBeanBuildItem unremovableBeans() {
   return UnremovableBeanBuildItem.targetWithAnnotation(STARTUP_NAME); (1)
}
1 Tornar todas as classes anotadas com @Startup não removíveis.

3. Caso de uso - Minha Anotação não É Reconhecida Como Um Qualificador ou Uma Ligação de Interceptador

É provável que a classe de anotação não faça parte do índice da aplicação. Por exemplo, as classes do módulo de tempo de execução de uma extensão do Quarkus não são indexadas automaticamente.

Solução: Utilize o AdditionalBeanBuildItem conforme descrito em Razão 1: A Classe Não Foi Descoberta.

4. Caso de Utilização - Preciso Transformar Metadados de Anotações

Em alguns casos, é útil poder modificar os metadados da anotação. O Quarkus oferece uma alternativa eficiente para jakarta.enterprise.inject.spi.ProcessAnnotatedType and jakarta.enterprise.inject.build.compatible.spi.Enhancement. Com um AnnotationsTransformerBuildItem , é possível substituir as anotações existentes nas classes de bean.

Tenha em mente que os transformadores de anotação devem ser produzidos antes do início da descoberta de bean.

Por exemplo, você pode querer adicionar uma vinculação de interceptador a uma classe de bean específica. Você pode usar uma API conveniente, semelhante a um builder, para criar uma instância de transformador:

Exemplo de Builder

@BuildStep
AnnotationsTransformerBuildItem transform() {
   return new AnnotationsTransformerBuildItem(AnnotationsTransformer.appliedToClass() (1)
        .whenClass(c -> c.name().toString().equals("org.acme.Bar")) (2)
        .thenTransform(t -> t.add(MyInterceptorBinding.class))); (3)
}
1 O transformador só é aplicado às classes.
2 Aplicar a transformação apenas se o nome da classe for igual a org.acme.Bar.
3 Adicionar a anotação @MyInterceptorBinding.

O exemplo acima pode ser reescrito com uma classe anônima:

Exemplo de AnnotationsTransformerBuildItem
@BuildStep
AnnotationsTransformerBuildItem transform() {
   return new AnnotationsTransformerBuildItem(new AnnotationsTransformer() {

      public boolean appliesTo(org.jboss.jandex.AnnotationTarget.Kind kind) {
         return kind == org.jboss.jandex.AnnotationTarget.Kind.CLASS; (1)
      }

      public void transform(TransformationContext context) {
         if (context.getTarget().asClass().name().toString().equals("org.acme.Bar")) {
            context.transform().add(MyInterceptorBinding.class).done(); (2)
         }
      }
    });
}
1 O transformador só é aplicado às classes.
2 Se o nome da classe for igual a org.acme.Bar, adicione @MyInterceptorBinding. Não se esqueça de invocar Transformation#done().

As etapas de construção podem consultar as anotações transformadas para um determinado alvo de anotação através do TransformedAnnotationsBuildItem.

Exemplo de TransformedAnnotationsBuildItem
@BuildStep
void queryAnnotations(TransformedAnnotationsBuildItem transformedAnnotations, BuildProducer<MyBuildItem> myBuildItem) {
   ClassInfo myClazz = ...;
   if (transformedAnnotations.getAnnotations(myClazz).isEmpty()) { (1)
     myBuildItem.produce(new MyBuildItem());
   }
}
1 TransformedAnnotationsBuildItem.getAnnotations() devolverá um conjunto de anotações possivelmente transformadas.
Existem outros itens de construção especializados em transformação: Caso de Uso - Ligações de Interceptores Adicionais e Caso de Uso - Transformação de Pontos de Injeção.

4.1. Como Ativar o Log de Trace para Transformadores de Anotações

Pode definir o nível de TRACE para a categoria io.quarkus.arc.processor e tentar analisar a saída do log posteriormente.

Exemplo de application.properties
quarkus.log.category."io.quarkus.arc.processor".min-level=TRACE (1)
quarkus.log.category."io.quarkus.arc.processor".level=TRACE
1 Também é necessário ajustar o nível mínimo de log para a categoria relevante.

5. Caso de Uso - Inspecionar Beans, Observadores e Pontos de Injeção

5.1. Solução 1: BeanDiscoveryFinishedBuildItem

Os consumidores do BeanDiscoveryFinishedBuildItem podem inspecionar facilmente todos os beans, observadores e pontos de injeção baseados em classe registrados na aplicação. No entanto, os beans e observadores sintéticos não são incluídos porque esse item de construção é produzido antes que os componentes sintéticos sejam registrados.

Além disso, o resolvedor de beans retornado por BeanDiscoveryFinishedBuildItem#getBeanResolver() pode ser utilizado para aplicar as regras de resolução de tipagem segura, por exemplo, para descobrir se existe um bean que satisfaça uma determinada combinação de tipo e qualificadores necessários.

Exemplo de BeanDiscoveryFinishedBuildItem
@BuildStep
void doSomethingWithNamedBeans(BeanDiscoveryFinishedBuildItem beanDiscovery, BuildProducer<NamedBeansBuildItem> namedBeans) {
   List<BeanInfo> namedBeans = beanDiscovery.beanStream().withName().collect(toList())); (1)
   namedBeans.produce(new NamedBeansBuildItem(namedBeans));
}
1 A lista resultante não conterá beans sintéticos @Named.

5.2. Solução 2: SynthesisFinishedBuildItem

Os consumidores de SynthesisFinishedBuildItem podem inspecionar facilmente todos os beans, observadores e pontos de injeção registrados na aplicação. Os beans e observadores sintéticos estão incluídos porque este item de construção é produzido depois dos componentes sintéticos serem registrados.

Além disso, o resolvedor de beans retornado por SynthesisFinishedBuildItem#getBeanResolver() pode ser utilizado para aplicar as regras de resolução de tipagem segura, por exemplo, para descobrir se existe um bean que satisfaça uma determinada combinação de tipo e qualificadores necessários.

Exemplo de SynthesisFinishedBuildItem
@BuildStep
void doSomethingWithNamedBeans(SynthesisFinishedBuildItem synthesisFinished, BuildProducer<NamedBeansBuildItem> namedBeans) {
   List<BeanInfo> namedBeans = synthesisFinished.beanStream().withName().collect(toList())); (1)
   namedBeans.produce(new NamedBeansBuildItem(namedBeans));
}
1 A lista resultante conterá beans sintéticos @Named.

6. Caso de Uso - A Necessidade de Beans Sintéticos

Às vezes, é prático poder registrar um bean sintético . Os atributos de bean de um bean sintético não são derivados de uma classe, método ou campo Java. Em vez disso, todos os atributos são definidos por uma extensão. No CDI regular, isso poderia ser feito usando os métodos AfterBeanDiscovery.addBean() e SyntheticComponents.addBean().

Solução: Se for necessário registrar um bean sintético, utilize o SyntheticBeanBuildItem.

Exemplo 1 de SyntheticBeanBuildItem
@BuildStep
SyntheticBeanBuildItem syntheticBean() {
   return SyntheticBeanBuildItem.configure(String.class)
             .qualifiers(AnnotationInstance.builder(MyQualifier.class).build())
             .creator(mc -> mc.returnValue(mc.load("foo"))) (1)
             .done();
}
1 Gerar o bytecode da implementação jakarta.enterprise.context.spi.Contextual#create(CreationalContext<T>).

A saída de um configurador de bean é gravada como bytecode. Portanto, há algumas limitações na forma como uma instância de bean sintético é criada em tempo de execução. Você pode:

  1. Gerar o bytecode do método Contextual#create(CreationalContext<T>) diretamente através do ExtendedBeanConfigurator.creator(Consumer<MethodCreator>).

  2. Passar uma subclasse de io.quarkus.arc.BeanCreator através do ExtendedBeanConfigurator#creator(Class<? extends BeanCreator<U>>), e possivelmente especificar alguns parâmetros de tempo de construção através do ExtendedBeanConfigurator#param() e pontos de injeção sintéticos através do ExtendedBeanConfigurator#addInjectionPoint().

  3. Produzir a instância de tempo de execução por meio de um proxy retornado de um método @Recorder e defini-la por meio de ExtendedBeanConfigurator#runtimeValue(RuntimeValue<?>) , ExtendedBeanConfigurator#runtimeProxy(Object) , ExtendedBeanConfigurator#supplier(Supplier<?>) ou ExtendedBeanConfigurator#createWith(Function<SyntheticCreationalContext<?>, <?>) .

Exemplo 2 de SyntheticBeanBuildItem
@BuildStep
@Record(STATIC_INIT) (1)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
   return SyntheticBeanBuildItem.configure(Foo.class).scope(Singleton.class)
                .runtimeValue(recorder.createFoo()) (2)
                .done();
}
1 Por padrão, um bean sintético é inicializado durante STATIC_INIT.
2 A instância do bean é fornecida por um valor retornado por um método de gravação.

É possível marcar um bean sintético para ser inicializado durante RUNTIME_INIT. Consulte as Três Fases do Bootstrap e a Filosofia do Quarkus para obter mais informações sobre a diferença entre STATIC_INIT e RUNTIME_INIT .

Exemplo de RUNTIME_INIT SyntheticBeanBuildItem
@BuildStep
@Record(RUNTIME_INIT) (1)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
   return SyntheticBeanBuildItem.configure(Foo.class).scope(Singleton.class)
                .setRuntimeInit() (2)
                .runtimeValue(recorder.createFoo())
                .done();
}
1 O gravador deve ser executado na fase ExecutionTime.RUNTIME_INIT.
2 A instância do bean é inicializada durante RUNTIME_INIT.

Os beans sintéticos inicializados durante RUNTIME_INIT não devem ser acessados durante STATIC_INIT. As etapas de construção de RUNTIME_INIT que acessam um bean sintético inicializado em tempo de execução devem consumir o SyntheticBeansRuntimeInitBuildItem:

@BuildStep
@Record(RUNTIME_INIT)
@Consume(SyntheticBeansRuntimeInitBuildItem.class) (1)
void accessFoo(TestRecorder recorder) {
   recorder.foo(); (2)
}
1 Este passo de construção deve ser executado após a conclusão de syntheticBean().
2 Este método de gravação resulta numa invocação da instância do bean Foo e, por isso, temos de nos certificar de que a etapa de construção é executada depois de todos os beans sintéticos serem inicializados.
Também é possível utilizar o BeanRegistrationPhaseBuildItem para registrar um bean sintético. No entanto, recomendamos aos autores de extensões que utilizem SyntheticBeanBuildItem, que é mais idiomático para o Quarkus.

6.1. Pontos de Injeção Sintéticos

Um bean sintético pode registrar um ponto de injeção sintético por meio do método ExtendedBeanConfigurator#addInjectionPoint() . Esse ponto de injeção é validado no momento da construção e considerado na detecção de beans não utilizados . A referência injetada pode ser acessada por meio dos métodos SyntheticCreationalContext#getInjectedReference() em tempo de execução.

Ponto de Injeção Sintético - Exemplo de Etapa de Construção
import org.jboss.jandex.ClassType;
import org.jboss.jandex.DotName;

@BuildStep
@Record(RUNTIME_INIT) (1)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
   return SyntheticBeanBuildItem.configure(Foo.class)
                .scope(Singleton.class)
                .addInjectionPoint(ClassType.create(DotName.createSimple(Bar.class))) (2)
                .createWith(recorder.createFoo()) (3)
                .done();
}
1 A instância do bean é inicializada durante RUNTIME_INIT.
2 Foi adicionado um ponto de injeção sintético com o tipo obrigatório Bar; este é equivalente a @Inject Bar.
3 A instância do bean é criada com uma função retornada de um método de gravação.
Ponto de Injeção Sintético - Exemplo de Gravador
@Recorder
public class TestRecorder {

   public Function<SyntheticCreationalContext<Foo>, Foo> createFoo() {
     return (context) -> {
        return new Foo(context.getInjectedReference(Bar.class)); (1)
     };
   }
}
1 Passa uma referência contextual de Bar ao construtor de Foo.

7. Caso de Uso - Observadores Sintéticos

À semelhança dos beans sintéticos, os atributos de um método de observador sintético não são derivados de um método Java. Em vez disso, todos os atributos são definidos por uma extensão.

Solução: Se for necessário registrar um observador sintético, utilize o ObserverRegistrationPhaseBuildItem.

Uma etapa de construção que consome o ObserverRegistrationPhaseBuildItem deve sempre produzir um ObserverConfiguratorBuildItem ou, pelo menos, injetar um BuildProducer para este item de construção, caso contrário, pode ser ignorado ou processado no momento errado (por exemplo, após a fase de bootstrap correta do CDI).
Exemplo de ObserverRegistrationPhaseBuildItem
@BuildStep
void syntheticObserver(ObserverRegistrationPhaseBuildItem observerRegistrationPhase,
            BuildProducer<MyBuildItem> myBuildItem,
            BuildProducer<ObserverConfiguratorBuildItem> observerConfigurationRegistry) {
   observerConfigurationRegistry.produce(new ObserverConfiguratorBuildItem(observerRegistrationPhase.getContext()
       .configure()
       .beanClass(DotName.createSimple(MyBuildStep.class.getName()))
       .observedType(String.class)
       .notify(mc -> {
           // do some gizmo bytecode generation...
       })));
   myBuildItem.produce(new MyBuildItem());
}

A saída de um ObserverConfigurator é gravada como bytecode. Portanto, há algumas limitações na forma como um observador sintético é chamado no tempo de execução. Atualmente, você deve gerar o bytecode do corpo do método diretamente.

8. Caso de Uso - Tenho uma Classe de Bean Gerada

Sem problemas. Você pode gerar o bytecode de uma classe de bean manualmente e, então, tudo o que precisa fazer é produzir um GeneratedBeanBuildItem em vez de GeneratedClassBuildItem .

Exemplo de GeneratedBeanBuildItem
@BuildStep
void generatedBean(BuildProducer<GeneratedBeanBuildItem> generatedBeans) {
    ClassOutput beansClassOutput = new GeneratedBeanGizmoAdaptor(generatedBeans); (1)
    ClassCreator beanClassCreator = ClassCreator.builder().classOutput(beansClassOutput)
                .className("org.acme.MyBean")
                .build();
    beanClassCreator.addAnnotation(Singleton.class);
    beanClassCreator.close(); (2)
}
1 io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor facilita a produção de GeneratedBeanBuildItems a partir de constructos Gizmo.
2 A classe de bean resultante é algo como public class @Singleton MyBean { }.

9. Caso de Uso - Preciso Validar a Implantação

Por vezes, as extensões precisam de inspecionar os beans, os observadores e os pontos de injeção e, em seguida, efetuar validações adicionais e falhar a construção se algo estiver errado.

Solução: Se uma extensão precisa validar a implantação, deve utilizar o ValidationPhaseBuildItem.

Uma etapa de construção que consome o ValidationPhaseBuildItem deve sempre produzir um ValidationErrorBuildItem ou, pelo menos, injetar um BuildProducer para este item de construção, caso contrário, pode ser ignorado ou processado no momento errado (por exemplo, após a fase de bootstrap correta do CDI).
@BuildStep
void validate(ValidationPhaseBuildItem validationPhase,
            BuildProducer<MyBuildItem> myBuildItem,
            BuildProducer<ValidationErrorBuildItem> errors) {
   if (someCondition) {
     errors.produce(new ValidationErrorBuildItem(new IllegalStateException()));
     myBuildItem.produce(new MyBuildItem());
   }
}
Você pode filtrar facilmente todos os beans registrados através do prático BeanStream retornado pelo método ValidationPhaseBuildItem.getContext().beans().

10. Caso de Uso - Registrar um Contexto CDI Personalizado

Às vezes, as extensões precisam estender o conjunto de contextos CDI integrados.

Solução: Se for necessário registrar um contexto personalizado, utilize o ContextRegistrationPhaseBuildItem.

Uma etapa de construção que consome o ContextRegistrationPhaseBuildItem deve sempre produzir um ContextConfiguratorBuildItem ou, pelo menos, injetar um BuildProducer para este item de construção, caso contrário, pode ser ignorado ou processado no momento errado (por exemplo, após a fase de bootstrap correta do CDI).

Exemplo de ContextRegistrationPhaseBuildItem

@BuildStep
ContextConfiguratorBuildItem registerContext(ContextRegistrationPhaseBuildItem phase) {
      return new ContextConfiguratorBuildItem(phase.getContext().configure(TransactionScoped.class).normal().contextClass(TransactionContext.class));
}

Além disso, cada extensão que registra um contexto CDI personalizado através do ContextRegistrationPhaseBuildItem deve também produzir o CustomScopeBuildItem de modo a contribuir com o nome da anotação de escopo personalizado para o conjunto de anotações que definem o bean.

Exemplo de CustomScopeBuildItem

@BuildStep
CustomScopeBuildItem customScope() {
   return new CustomScopeBuildItem(DotName.createSimple(TransactionScoped.class.getName()));
}

10.1. E Se Eu Precisar Conhecer Todos os Escopos Utilizados na Aplicação?

Solução: Você pode injetar o CustomScopeAnnotationsBuildItem numa etapa de construção e utilizar os métodos convenientes, como CustomScopeAnnotationsBuildItem.isScopeDeclaredOn().

11. Caso de Uso - Ligações de Interceptadores Adicionais

Em casos raros, pode ser útil registrar programaticamente uma anotação existente que não esteja anotada com @jakarta.interceptor.InterceptorBinding como uma vinculação de interceptador. Isso é semelhante ao que o CDI consegue fazer por meio de BeforeBeanDiscovery#addInterceptorBinding() . Vamos usar o InterceptorBindingRegistrarBuildItem para fazer isso.

Exemplo de InterceptorBindingRegistrarBuildItem
@BuildStep
InterceptorBindingRegistrarBuildItem addInterceptorBindings() {
    return new InterceptorBindingRegistrarBuildItem(new InterceptorBindingRegistrar() {
        @Override
        public List<InterceptorBinding> getAdditionalBindings() {
            return List.of(InterceptorBinding.of(NotAnInterceptorBinding.class));
        }
    });
}

12. Caso de Uso - Qualificadores Adicionais

Às vezes, pode ser útil registrar uma anotação existente que não esteja anotada com @jakarta.inject.Qualifier como um qualificador CDI. Isso é semelhante ao que a CDI faz por meio do BeforeBeanDiscovery#addQualifier() . Vamos usar o QualifierRegistrarBuildItem para fazer isso.

Exemplo de QualifierRegistrarBuildItem
@BuildStep
QualifierRegistrarBuildItem addQualifiers() {
    return new QualifierRegistrarBuildItem(new QualifierRegistrar() {
        @Override
        public Map<DotName, Set<String>> getAdditionalQualifiers() {
            return Collections.singletonMap(DotName.createSimple(NotAQualifier.class.getName()),
                                        Collections.emptySet());
        }
    });
}

13. Caso de Uso - Estereótipos Adicionais

Às vezes, é útil registrar uma anotação existente que não esteja anotada com @jakarta.enterprise.inject.Stereotype como um estereótipo CDI. Isso é semelhante ao que a CDI faz por meio do BeforeBeanDiscovery#addStereotype() . Vamos usar o StereotypeRegistrarBuildItem para fazer isso.

Exemplo de StereotypeRegistrarBuildItem
@BuildStep
StereotypeRegistrarBuildItem addStereotypes() {
    return new StereotypeRegistrarBuildItem(new StereotypeRegistrar() {
        @Override
        public Set<DotName> getAdditionalStereotypes() {
            return Collections.singleton(DotName.createSimple(NotAStereotype.class.getName()));
        }
    });
}

Se a anotação de estereótipo recém registrada não tiver as meta-anotações apropriadas, como as vinculações de escopo ou de interceptador, utilize uma transformação de anotação para as adicionar.

14. Caso de Uso - Transformação de Pontos de Injeção

De vez em quando, é útil poder alterar os qualificadores de um ponto de injeção de forma programática. Você pode fazer exatamente isso com InjectionPointTransformerBuildItem . O exemplo a seguir mostra como aplicar a transformação a pontos de injeção com o tipo Foo que contém o qualificador MyQualifier :

Exemplo de InjectionPointTransformerBuildItem
@BuildStep
InjectionPointTransformerBuildItem transformer() {
    return new InjectionPointTransformerBuildItem(new InjectionPointsTransformer() {

        public boolean appliesTo(Type requiredType) {
            return requiredType.name().equals(DotName.createSimple(Foo.class.getName()));
        }

        public void transform(TransformationContext context) {
            if (context.getQualifiers().stream()
                    .anyMatch(a -> a.name().equals(DotName.createSimple(MyQualifier.class.getName())))) {
                context.transform()
                        .removeAll()
                        .add(DotName.createSimple(MyOtherQualifier.class.getName()))
                        .done();
            }
        }
    });
}
Em teoria, é possível utilizar um AnnotationsTransformer para alcançar o mesmo objetivo. No entanto, existem algumas diferenças que tornam o InjectionPointsTransformer mais adequado para esta tarefa específica: (1) os transformadores de anotação são aplicados a todas as classes durante a descoberta de bean, enquanto o InjectionPointsTransformer só é aplicado aos pontos de injeção descobertos após a descoberta de bean; (2) com InjectionPointsTransformer você não precisa tratar vários tipos de pontos de injeção (campo, parâmetros de métodos inicializadores, etc.).

15. Caso de Uso - Anotações e Injeção de Recursos

O ResourceAnnotationBuildItem pode ser usado para especificar anotações de recursos que possibilitam a resolução de pontos de injeção não CDI, como os recursos do Jakarta EE. Um integrador também deve fornecer uma implementação correspondente do provedor de serviços io.quarkus.arc.ResourceReferenceProvider.

Exemplo de ResourceAnnotationBuildItem
@BuildStep
void setupResourceInjection(BuildProducer<ResourceAnnotationBuildItem> resourceAnnotations, BuildProducer<GeneratedResourceBuildItem> resources) {
    resources.produce(new GeneratedResourceBuildItem("META-INF/services/io.quarkus.arc.ResourceReferenceProvider",
        MyResourceReferenceProvider.class.getName().getBytes()));
    resourceAnnotations.produce(new ResourceAnnotationBuildItem(DotName.createSimple(MyAnnotation.class.getName())));
}

16. Metadados de Tempo de Construção Disponíveis

Qualquer uma das extensões acima que opere com BuildExtension.BuildContext pode aproveitar determinados metadados de tempo de construção gerados durante a construção. As chaves integradas, localizadas em io.quarkus.arc.processor.BuildExtension.Key são:

ANNOTATION_STORE

Contém um AnnotationStore que mantém informações sobre todas as anotações AnnotationTarget após a aplicação de transformadores de anotações

INJECTION_POINTS

Collection<InjectionPointInfo> contendo todos os pontos de injeção

BEANS

Collection<BeanInfo> contendo todos os beans

REMOVED_BEANS

Collection<BeanInfo> contendo todos os beans removidos; para mais informações, ver Removendo beans não utilizados

OBSERVERS

Collection<ObserverInfo> contendo todos os observadores

SCOPES

Collection<ScopeInfo> contendo todos os escopos, incluindo os personalizados

QUALIFIERS

Map<DotName, ClassInfo> contendo todos os qualificadores

INTERCEPTOR_BINDINGS

Map<DotName, ClassInfo> contendo todas os vínculos de interceptadores

STEREOTYPES

Map<DotName, StereotypeInfo> contendo todos os estereótipos

Para obtê-los, basta consultar o objeto de contexto da extensão para uma determinada chave. Observe que esses metadados são disponibilizados à medida que a construção avança, o que significa que as extensões só podem aproveitar os metadados que foram criados antes das extensões serem invocadas. Se sua extensão tentar recuperar metadados que ainda não foram produzidos, será retornado null . Aqui está um resumo de quais extensões podem acessar quais metadados:

AnnotationsTransformer

Não deve depender de quaisquer metadados, uma vez que pode ser utilizado em qualquer momento de qualquer fase do bootstrap

ContextRegistrar

Tem acesso a ANNOTATION_STORE, QUALIFIERS, INTERCEPTOR_BINDINGS, STEREOTYPES

InjectionPointsTransformer

Tem acesso a ANNOTATION_STORE, QUALIFIERS, INTERCEPTOR_BINDINGS, STEREOTYPES

ObserverTransformer

Tem acesso a ANNOTATION_STORE, QUALIFIERS, INTERCEPTOR_BINDINGS, STEREOTYPES

BeanRegistrar

Tem acesso a ANNOTATION_STORE, QUALIFIERS, INTERCEPTOR_BINDINGS, STEREOTYPES, BEANS (apenas beans baseados em classes), OBSERVERS (apenas observadores baseados em classes), INJECTION_POINTS

ObserverRegistrar

Tem acesso a ANNOTATION_STORE, QUALIFIERS, INTERCEPTOR_BINDINGS, STEREOTYPES, BEANS, OBSERVERS (apenas observadores baseados em classes), INJECTION_POINTS

BeanDeploymentValidator

Tem acesso a todos os metadados de construção