SeamFramework.orgCommunity Documentation

Capítulo 13. Especialização, herança e alternativos

13.1. Utilizando estereótipos alternativos
13.2. Um pequeno problema com alternativos
13.3. Utilizando a especialização

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:

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.