SeamFramework.orgCommunity Documentation
Quando você começa a desenvolver com CDI provavelmente estará lidando com apenas uma única implementação para cada bean type. Neste caso, é fácil entender como beans são selecionados para injeção. Conforme a complexidade de sua aplicação aumenta, várias ocorrências do mesmo bean type começam a aparecer, seja por existir várias implementações ou dois beans compartilharem uma mesma hierarquia (Java). É neste momento que você tem que começar a estudar as regras de especialização, herança e alternativos para lidar com dependências não satisfeitas ou ambíguas, ou para evitar que certos beans sejam chamados.
A especificação CDI reconhece dois cenários distintos e que um bean estende um outro:
O bean suplementar especializa o bean inicial em certos cenários de implantação. Nestas implantações, o bean suplementar substitui completamente o primeiro, realizando o mesmo papel no sistema.
O bean suplementar está simplesmente reutilizando a implementação Java, e de outro modo suporta nenhuma relação com o priemeiro bean. O bean inicial pode nem mesmo ter sido projetado para uso como um objeto contextual.
O segundo caso é o padrão assumido pela CDI. É possível ter dois beans no sistema com o mesmo bean type (interface ou classe pai). Conforme você aprendeu, você seleciona uma entre duas implementações usando qualificadores.
O primeiro caso é a exceção, e também requer mais cuidado. Em qualquer implantação, somente um bean pode realizar um dado papel em um momento. Isto significa que um bean precisa ser habilitado e outro desabilitado. Existem dois modificadores envolvidos: @Alternative
e @Specializes
. Vamos começar observando os alternativos e depois mostraremos as garantias que a especialização adiciona.
CDI permite que você sobrescreva a implementação de um bean type durante a implantação utilizando um alternativo. Por exemplo, o seguinte bean fornece uma implementação padrão para a interface PaymentProcessor
:
public class DefaultPaymentProcessor
implements PaymentProcessor {
...
}
Mas em nosso ambiente simulado, não queremos efetivamente enviar ordens de pagamento para o sistema externo, dessa forma sobrescrevemos esta implementação de PaymentProcessor
com um bean diferente:
public @Alternative
class StagingPaymentProcessor
implements PaymentProcessor {
...
}
or
public @Alternative
class StagingPaymentProcessor
extends DefaultPaymentProcessor {
...
}
Já vimos como podemos habilitar este alternativo registrando sua classe no descritor beans.xml
.
Mas suponha que temos muitos alternativos no ambiente simulado. Deveria ser mais conveniente habilitar todos eles de uma vez. então vamos tornar @Staging
um estereótipo @Alternative
e anotar os beans simuladores com este estereótipo. Você verá como este nível de indireção compensa. Primeiro, criamos o estereótipo:
@Alternative
@Stereotype
@Retention(RUNTIME)
@Target(TYPE)
public @interface Staging {}
Então substituímos a anotação @Alternative
em nosso bean por @Staging
:
@Staging
public class StagingPaymentProcessor
implements PaymentProcessor {
...
}
Finalmente, ativamos o estereótipo @Staging
no descritor beans.xml
:
<beans
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<alternatives>
<stereotype
>org.mycompany.myapp.Staging</stereotype>
</alternatives>
</beans
>
Agora, não importa quantos beans simuladores temos, eles serão habilitados todos de uma vez.
Quando habilitamos um alternativo, significa que a implementação padrão é desabilitada? Bem, não exatamente. Se a implementação padrão possui um qualificador, por exemplo @LargeTransaction
, e o alternativo não possui, você pode ainda injetar a implementação padrão.
@Inject @LargeTransaction PaymentProcessor paymentProcessor;
Então não teremos substituído completamente a implementação padrão nesta implantação do sistema. O único modo que um bean pode substituir completamente um outro bean em todos os pontos de injeção é se ele implementa todos os beans types e declara todos os qualificadores deste outro bean. No entanto, se o outro bean declara um método produtor ou um método observador, então mesmo isto não é suficiente para assegurar que o outro bean nunca seja chamado! Precisamos de algo a mais.
CDI fornece uma funcionalidade especial, chamada especialização, que ajuda o desenvolvedor a evitar essas armadilhas. Especialização é um meio de informar o sistema de sua intenção de substituir completamente e desabilitar uma implementação de um bean.
Quando o objetivo é substituir uma implementação por uma outra, para ajudar a prevenir erros do desenvolvedor, o primeiro bean pode:
estender diretamente a classe do outro bean, ou
substituir diretamente o método produtor, no caso em que o outro bean for um método produtor, e então
declarar explicitamente que ele especializa o outro bean.
@Alternative @Specializes
public class MockCreditCardPaymentProcessor
extends CreditCardPaymentProcessor {
...
}
Quando um bean está ativado e especializa um outro bean, este nunca é instanciado ou chamado pelo contêiner. Mesmo se o outro bean define um método produtor ou observador, o método nunca será chamado.
Então, como essa especialização funciona e o que tem a ver com herança?
Uma vez que estamos informando ao contêiner que nosso bean alternativo está destinado a ficar como um substituto da implementação padrão, a implementação alternativa automaticamente herda todos os qualificadores da implementação padrão. Desta maneira, em nosso exemplo, MockCreditCardPaymentProcessor
herda os qualificadores @Default
e @CreditCard
.
Adicionalmente, se a implementação padrão declara um nome EL para o bean usando @Named
, o nome é herdado pelo bean alternativo especializado.