Hello World CDI Extension

No meu primeiro post eu vou mostrar como criar uma extensão do CDI onde iremos criar um projeto simples para demostrar o funcionamento desta poderosa feature do CDI.

Características de uma extensão CDI

Em linhas gerais uma extensão portável do CDI prove meios para:

  1. – a integração de outras tecnologias(ex: Spring, Wicket, ZK etc..) ao ecossistema do CDI independente da implementação do CDI;
  2. – inspecionar objetos do sistema em busca de metadados e manipular ou arrecadar informações desses objetos  (o nosso exemplo irá fazer algo do gênero);
  3. – extender e/ou adicionar novas funcionalidades ao CDI como:

3.1 – contexto de persistencia para o CDI;
3.2 – gerador de queries baseado em anotations;
3.3 – segurança: Seam Security ou CODI.
3.4 – novos escopos como os fornecidos pelo CODI (alias o Myfaces CODI ou CDI-Ext assim como o Seam 3 são agregados de extensions)

Outras caracteristicas de uma extension são:

  1.  – deve ser declarada na pasta META-INF/services em um arquivo chamado javax.enterprise.inject.spi.Extension.
  2.  – é ativada no momento em que o container CDI está iniciando e antes que a sua aplicação suba.
  3.  – pode ser injetada em qualquer classe gerenciada pelo CDI, de fato ela é um bean do CDI com escopo de aplicação.
  4.  – observa eventos disparados pelo container como afterBeanDiscovery, ProcessInjectionTarget, ProcessAnnotatedType dentre outros.
  5. – deve implementar a interface Extension que é apenas uma interface demarcadora(como Serializable).
  6. – os metodos observadores podem receber o BeanManager como paramatro (o CDI injeta ele automaticamente).

Recomendo a leitura do capitulo 16 da documentação do Weld que fala sobre as extensões portaveis e a thread no forum do weld que originou a criação desse post.

Bom, chega de lero lero vamos colocar a mão na massa e para isso nada mais apropriado do que o famoso Hello World!

Implementação Hello World Extension

O exemplo que iremos implementar irá fazer injeção de dependencia baseada no esteriótipo da classe,  basicamente iremos inferir através de anotações a nível de classe qual implementação de determinada interface deverá ser injetada,  por exemplo:

iremos substituir:

@Model
public class EnglishHelloBean {

 @Inject @English //injeção de dependencia será feita a nivel de classe
 private Hello hello; //interface que possui duas implementações(ingles e portugues)     
 
}

por:

@Model
@English
public class EnglishHelloBean {

 private Hello hello; //implementação será escolhida olhando para o esteriótipo da classe   
 
}

apesar de ser algo bem simples iremos conseguir entender como uma extensão do CDI é capaz de inspecionar nossas classes e adicionar novas funcionalidades ao CDI. Nossa primeira classe será a interface Hello:

public interface Hello {
  String sayHello();
}

seguida por suas duas implementações:

@English
public class EnglishHello implements Hello{
   @Override
   public String sayHello(){
     return "Hello World!";
    }
}

@Portuguese
public class PortugueseHello implements Hello{
  @Override
  public String sayHello(){
    return "Ola mundo!";
   }
}

notem que apereceram duas anotações, são qualifiers do CDI para que o container CDI saiba qual implementação utilizar quando injetarmos a interface Hello em alguma classe, o código das anotações segue abaixo:

 @Qualifier
 @Retention(RUNTIME)
 @Target({METHOD, FIELD,  TYPE})//indica que a anotação só pode ser utilizada em metodos, campos ou a nível de classe
 public @interface English {
 }

 @Qualifier
 @Retention(RUNTIME)
 @Target({METHOD, FIELD,  TYPE})
 public @interface Portuguese{
 }

nossa proxima classe será uma classe abstrata que servirá como templete para nossos beans:

public abstract class BaseHelloBean {
   protected Hello hello;//esse atributo será injetado pela classe filha

   public Hello getHello(){
     return hello;
    }
   public void setHello(Hello hello){
     this.hello = hello;
   }
}

a seguir os nossos dois beans que injetarão a interface hello e irão printar o nosso “hello world!” na tela.

@Model//essa anotação(esteriótipo) é o mesmo que @RequestScoped + @Named
public class EnglishHelloBean extends BaseHelloBean {
/**
 * injeta a implementação Inglesa da interface Hello
 */
@Override
@Inject 
public void setHello(@English Hello hello){ //nossa extensão irá eliminar esse metodo
         super.setHello(hello);
  }

}
@Model//essa anotação(esteriótipo) é o mesmo que @RequestScoped + @Named
public class PortugueseHelloBean extends BaseHelloBean {
/**
 * injeta a implementação Portuguesa da interface Hello
 */
@Override
@Inject 
public void setHello(@Portuguese Hello hello){ //nossa extensão irá eliminar esse metodo
         super.setHello(hello);
  }

}

 e a nossa tela tem apenas as linhas abaixo:

 #{portugueseHelloBean.hello.sayHello()}
 #{englishHelloBean.hello.sayHello()}

e o resultado é

Ola mundo!
Hello World!

Até agora nada de novo, apenas utilizamos a injeção de dependencia padrão do CDI, agora a nossa  Extensão irá modificar esse comportamento que, apesar de produzir o mesmo resultado, irá eliminar os metodos setHello dos dois beans. Segue a implementação: (os comentarios serão no próprio código pois não consegui colocar o número de linhas aqui)

package com.cdi.extension.helloextension;
 import javax.enterprise.event.Observes;
 import javax.enterprise.inject.spi.*;
 import javax.enterprise.util.AnnotationLiteral;
 import org.jboss.solder.literal.InjectLiteral;
 import org.jboss.solder.reflection.annotated.AnnotatedTypeBuilder;
/**
 *
 * @author rafael-pestano
 */
 public class HelloExtension implements Extension {

   //classe utilitária do seam solder para adicionarmos anotações nos campos
   private AnnotatedTypeBuilder<BaseHelloBean> builder = null;

/**
 * nossa extensão observa o evento de 'processar tipos anotados' nas classe do tipo BaseHelloBean, 
 * ou seja, irá entrar nesse metodo se existir uma classe do tipo HelloBaseBean que esteja anotada com algum qualifier
 */
 void processAnnotatedType(@Observes ProcessAnnotatedType<BaseHelloBean> pat) {
      final AnnotatedType<BaseHelloBean> at = pat.getAnnotatedType();
      //verificamos se a anotação @Portuguese está presente na classe
      if (pat.getAnnotatedType().isAnnotationPresent(Portuguese.class)) 
         //adiciona a anotação @Inject e @Portuguese no campo Hello
         injectLanguage(at, new PortugueseImpl());
      } else if(pat.getAnnotatedType().isAnnotationPresent(English.class)){
         //adiciona a anotação @Inject e @English no campo Hello
         injectLanguage(at, new EnglishImpl());
      }
      if(builder != null){
        pat.setAnnotatedType(builder.create());//substitui tipo anotatdo pelo tipo que modificamos
      }
  }

/**
 * metodo responsavel por injetar as anotações @Inject e @English/@Portuguese
 * no campo Hello
 */
 private void injectLanguage(AnnotatedType<BaseHelloBean> at, AnnotationLiteral language){
     for (AnnotatedField<? super BaseHelloBean> annotatedField : at.getFields()) {
        //varre campos da classe em busta de um campo do tipo Hello
        if (annotatedField.getBaseType().equals(Hello.class)) {
         builder = new AnnotatedTypeBuilder().readFromType(at);
         builder.addToField(annotatedField, language);//adiciona anotação @Portuguese/English
         builder.addToField(annotatedField, new InjectLiteral());//adiciona anotação @Inject
       }
     }
 }

para injetarmos os qualifiers corretamente necessitamos de suas implementações:

import javax.enterprise.util.AnnotationLiteral;
/**
 *
 * @author rmpestano Mar 30, 2012 at 6:14:04 PM
 */
public class EnglishImpl extends AnnotationLiteral<English> implements English{
}
import javax.enterprise.util.AnnotationLiteral;
/**
 *
 * @author rmpestano Mar 30, 2012 at 6:14:04 PM
 */
 public class PortugueseImpl extends AnnotationLiteral<Portuguese> implements Portuguese{
}

após adicionar a extensão não precisamos mais dos metodos setHello, apenas da anotação de idioma a nivel de classe:

@Model
@Portuguese
public class PortugueseHelloBean extends BaseHelloBean{

}

e o Hello é injetado baseado na anotação da classe.

Em suma é uma técnica poderosa que nos permite adicionar ou extender funcionalidades do CDI independente da implementação do mesmo.

Pretendo implementar  uma extensão parecida no conventions framework para eliminar a obrigatoriedade de se implementar o metodo setService nos managedBeans.

O fonte do exemplo está disponivel no github em:https://github.com/rmpestano/hello-extension

para rodar o projeto localmente será necessário:

  1. maven 2.1.1 ou superior;
  2. container Java EE 6 (testado com JBoss 7.1 e Glassfish 3.1.2)
  3. gerar o war com o comando mvn clean install
  4. fazer deploy num servidor Java EE que tenha uma implementação do CDI
Referências
Advertisements

3 thoughts on “Hello World CDI Extension

  1. Gostei,
    mas sera que consigo fazer isso utilizando EJBs?
    Por exemplo, 2 EJBs implementando a mesma interface para acesso remoto?

    Obs: Sem utilizar o seam.

    Parabéns pelo post.

    Like

  2. Pingback: cdi-custom-scope | rpestano

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s