SeamFramework.orgCommunity Documentation

Capítulo 16. Extensões portáveis

16.1. Criando uma Extension
16.2. Eventos do ciclo de vida do contêiner
16.3. O objeto BeanManager
16.4. A interface InjectionTarget
16.5. A interface Bean
16.6. Registrando um Bean
16.7. Envolvendo um AnnotatedType
16.8. Envolvendo um InjectionTarget
16.9. A interface Context

A CDI pretende ser uma plataforma para frameworks, extensões e integração com outras tecnologias. Portanto, a CDI expõe um conjunto de SPIs para utilização pelos desenvolvedores de extensões portáveis para CDI. Por exemplo, os seguintes tipos de extensões estavam previstos pelos projetistas da CDI:

Mais formalmente, de acordo com a especificação:

Uma extensão portável pode integrar com o contêiner:

  • Fornecendo seus próprios beans, interceptadores e decoradores ao contêiner

  • Injetando dependências em seus próprios objetos usando o serviço de injeção de dependência

  • Fornecendo uma implementação de contexto para um escopo personalizado

  • Aumentando ou sobrescrevendo os metadados das anotações com metadados de algum outro lugar

O primeiro passo na criação de uma extensão portável é escrever uma classe que implementa Extension. Esta interface indicadora não define qualquer método, mas é necessária para satisfazer os requisitos da arquitetura de provedores de serviço da Java SE.

class MyExtension implements Extension { ... }

Agora, precisamos registrar nossa extensão como um provedor de serviço criando um arquivo nomeado como META-INF/services/javax.enterprise.inject.spi.Extension, o qual contém o nome da classe de nossa extensão:

org.mydomain.extension.MyExtension

Uma extensão não é um bean, exatamente, já que é instanciada pela contêiner durante o processo de inicialização, antes de qualquer bean ou contexto existir. Entretanto, pode ser injetada em outros beans uma vez que o processo de inicialização estiver completo.

@Inject 
MyBean(MyExtension myExtension) {
   myExtension.doSomething();
}

E, como beans, extensões podem ter métodos observadores. Geralmente, os métodos observadores observam eventos do ciclo de vida do contêiner.

Durante o processo de inicialização, o contêiner dispara uma série de eventos, incluindo:

Extensões podem observar estes eventos:

class MyExtension implements Extension {

   
   void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd) {
      Logger.global.debug("beginning the scanning process");
   }
      
   <T
> void processAnnotatedType(@Observes ProcessAnnotatedType<T
> pat) {
      Logger.global.debug("scanning type: " + pat.getAnnotatedType().getJavaClass().getName());
   } 
   void afterBeanDiscovery(@Observes AfterBeanDiscovery abd) {
      Logger.global.debug("finished the scanning process");
   }
   
}

De fato, a extensão pode fazer muito mais que apenas observar. A extensão pode modificar o metamodelo do contêiner e mais. Aqui está um exemplo bem simples:

class MyExtension implements Extension {

      
   <T
> void processAnnotatedType(@Observes ProcessAnnotatedType<T
> pat) {
      //tell the container to ignore the type if it is annotated @Ignore
      if ( pat.getAnnotatedType().isAnnotionPresent(Ignore.class) ) pat.veto();   
   } 
   
}

O método observador pode injetar um BeanManager.

<T

> void processAnnotatedType(@Observes ProcessAnnotatedType<T
> pat, BeanManager beanManager) { ... }

O nervo central para extender CDI é o objeto BeanManager. A interface BeanManager nos permite, programaticamente, obter beans, interceptadores, decoradores, observadores e contextos.

public interface BeanManager {

   public Object getReference(Bean<?> bean, Type beanType, CreationalContext<?> ctx);
   public Object getInjectableReference(InjectionPoint ij, CreationalContext<?> ctx);
   public <T
> CreationalContext<T
> createCreationalContext(Contextual<T
> contextual);
   public Set<Bean<?>
> getBeans(Type beanType, Annotation... qualifiers);
   public Set<Bean<?>
> getBeans(String name);
   public Bean<?> getPassivationCapableBean(String id);
   public <X
> Bean<? extends X
> resolve(Set<Bean<? extends X
>
> beans);
   public void validate(InjectionPoint injectionPoint);
   public void fireEvent(Object event, Annotation... qualifiers);
   public <T
> Set<ObserverMethod<? super T
>
> resolveObserverMethods(T event, Annotation... qualifiers);
   public List<Decorator<?>
> resolveDecorators(Set<Type
> types, Annotation... qualifiers);
   public List<Interceptor<?>
> resolveInterceptors(InterceptionType type, Annotation... interceptorBindings);
   public boolean isScope(Class<? extends Annotation
> annotationType);
   public boolean isNormalScope(Class<? extends Annotation
> annotationType);
   public boolean isPassivatingScope(Class<? extends Annotation
> annotationType);
   public boolean isQualifier(Class<? extends Annotation
> annotationType);
   public boolean isInterceptorBinding(Class<? extends Annotation
> annotationType);
   public boolean isStereotype(Class<? extends Annotation
> annotationType);
   public Set<Annotation
> getInterceptorBindingDefinition(Class<? extends Annotation
> bindingType);
   public Set<Annotation
> getStereotypeDefinition(Class<? extends Annotation
> stereotype);
   public Context getContext(Class<? extends Annotation
> scopeType);
   public ELResolver getELResolver();
   public ExpressionFactory wrapExpressionFactory(ExpressionFactory expressionFactory);
   public <T
> AnnotatedType<T
> createAnnotatedType(Class<T
> type);
   public <T
> InjectionTarget<T
> createInjectionTarget(AnnotatedType<T
> type);
}

Qualquer bean ou outro componente Java EE que suporte injeção pode obter uma instância do BeanManager via injeção:

@Inject BeanManager beanManager;

Os componentes Java EE podem obter uma instância de BeanManager a partir de JNDI procurando pelo nome java:comp/BeanManager. Qualquer operação de BeanManager pode ser chamada a qualquer momento durante a execução da aplicação.

Vamos estudar algumas das interfaces expostas pelo BeanManager.

A primeira coisa que um desenvolvedor de framework vai procurar na extensão portável SPI é uma maneira de injetar beans CDI em objetos que não estão sob o controle de CDI. A interface InjectionTarget torna isto muito fácil.

//get the BeanManager from JNDI

BeanManager beanManager = (BeanManager) new InitialContext().lookup("java:comp/BeanManager");
//CDI uses an AnnotatedType object to read the annotations of a class
AnnotatedType<SomeFrameworkComponent
> type = beanManager.createAnnotatedType(SomeFrameworkComponent.class);
//The extension uses an InjectionTarget to delegate instantiation, dependency injection 
//and lifecycle callbacks to the CDI container
InjectionTarget<SomeFrameworkComponent
> it = beanManager.createInjectionTarget(type);
//each instance needs its own CDI CreationalContext
CreationalContext ctx = beanManager.createCreationalContext(null);
//instantiate the framework component and inject its dependencies
SomeFrameworkComponent instance = it.produce(ctx);  //call the constructor
it.inject(instance, ctx);  //call initializer methods and perform field injection
it.postConstruct(instance);  //call the @PostConstruct method
...
//destroy the framework component instance and clean up dependent objects
it.preDestroy(instance);  //call the @PreDestroy method
it.dispose(instance);  //it is now safe to discard the instance
ctx.release();  //clean up dependent objects

Instâncias da interface Bean representam beans. Existe uma instância de Bean registrada com o objeto BeanManager para todos os beans da aplicação. Há ainda objetos Bean representando interceptadores, decorados e métodos produtores.

The Bean interface exposes all the interesting things we discussed in Seção 2.1, “A anatomia de um bean”.

public interface Bean<T

> extends Contextual<T
> {
   public Set<Type
> getTypes();
   public Set<Annotation
> getQualifiers();
   public Class<? extends Annotation
> getScope();
   public String getName();
   public Set<Class<? extends Annotation
>
> getStereotypes();
   public Class<?> getBeanClass();
   public boolean isAlternative();
   public boolean isNullable();
   public Set<InjectionPoint
> getInjectionPoints();
}

Há uma maneira fácil de descobrir quais beans existem na aplicação:

Set<Bean<?>

> allBeans = beanManager.getBeans(Obect.class, new AnnotationLiteral<Any
>() {});

A interface Bean torna possível a uma extensão portável fornecer suporte a novos tipos de beans, além daqueles definidos pela especificação CDI. Por exemplo, poderíamos usar a interface Bean para permitir que os objetos gerenciados por um outro framework possam ser injetados nos beans.

O tipo mais comum de extensão portável em CDI é para registro de beans no contêiner.

Neste exemplo, tornaremos uma classe do framework, SecurityManager disponível para injeção. Para tornar as coisas um pouco mais interessantes, vamos delegar de volta ao InjectionTarget do contêiner para realizar a instanciação e injeção das instâncias de SecurityManager.

public class SecurityManagerExtension implements Extension {

    
    void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager bm) {
            
        //use this to read annotations of the class
        AnnotatedType<SecurityManager
> at = bm.createAnnotatedType(SecurityManager.class); 
        //use this to instantiate the class and inject dependencies
        final InjectionTarget<SecurityManager
> it = bm.createInjectionTarget(at); 
        abd.addBean( new Bean<SecurityManager
>() {
            @Override
            public Class<?> getBeanClass() {
                return SecurityManager.class;
            }
            @Override
            public Set<InjectionPoint
> getInjectionPoints() {
                return it.getInjectionPoints();
            }
            @Override
            public String getName() {
                return "securityManager";
            }
            @Override
            public Set<Annotation
> getQualifiers() {
                Set<Annotation
> qualifiers = new HashSet<Annotation
>();
                qualifiers.add( new AnnotationLiteral<Default
>() {} );
                qualifiers.add( new AnnotationLiteral<Any
>() {} );
                return qualifiers;
            }
            @Override
            public Class<? extends Annotation
> getScope() {
                return SessionScoped.class;
            }
            @Override
            public Set<Class<? extends Annotation
>
> getStereotypes() {
                return Collections.emptySet();
            }
            @Override
            public Set<Type
> getTypes() {
                Set<Type
> types = new HashSet<Type
>();
                types.add(SecurityManager.class);
                types.add(Object.class);
                return types;
            }
            @Override
            public boolean isAlternative() {
                return false;
            }
            @Override
            public boolean isNullable() {
                return false;
            }
            @Override
            public SecurityManager create(CreationalContext<SecurityManager
> ctx) {
                SecurityManager instance = it.produce(ctx);
                it.inject(instance, ctx);
                it.postConstruct(instance);
                return instance;
            }
            @Override
            public void destroy(SecurityManager instance, 
                                CreationalContext<SecurityManager
> ctx) {
                it.preDestroy(instance);
                it.dispose(instance);
                ctx.release();
            }
            
        } );
    }
    
}

Mas uma extensão portável também pode se misturar com beans que são descobertos automaticamente pelo contêiner.

Uma das coisas mais interessantes que uma classe de extensão pode fazer é processar as anotações de uma classe de bean antes do contêiner construir seu metamodelo.

Vamos começar com um exemplo de uma extensão que fornece suporte ao uso de @Named a nível de pacote. O nome em nível de pacote é utilizado para qualificar os nomes EL de todos os beans definidos neste pacote. A extensão portável utiliza o evento ProcessAnnotatedType para envolver o objeto AnnotatedType e sobrescrever o value() da anotação @Named.

public class QualifiedNameExtension implements Extension {


    <X
> void processAnnotatedType(@Observes ProcessAnnotatedType<X
> pat) {
        //wrap this to override the annotations of the class
        final AnnotatedType<X
> at = pat.getAnnotatedType();
        
        AnnotatedType<X
> wrapped = new AnnotatedType<X
>() {
            @Override
            public Set<AnnotatedConstructor<X
>
> getConstructors() {
                return at.getConstructors();
            }
            @Override
            public Set<AnnotatedField<? super X
>
> getFields() {
                return at.getFields();
            }
            @Override
            public Class<X
> getJavaClass() {
                return at.getJavaClass();
            }
            @Override
            public Set<AnnotatedMethod<? super X
>
> getMethods() {
                return at.getMethods();
            }
            @Override
            public <extends Annotation
> T getAnnotation(final Class<T
> annType) {
                if ( Named.class.equals(annType) ) {
                    class NamedLiteral 
                            extends AnnotationLiteral<Named
> 
                            implements Named {
                        @Override
                        public String value() {
                            Package pkg = at.getClass().getPackage();
                            String unqualifiedName = at.getAnnotation(Named.class).value();
                            final String qualifiedName;
                            if ( pkg.isAnnotationPresent(Named.class) ) {
                                qualifiedName = pkg.getAnnotation(Named.class).value() 
                                      + '.' + unqualifiedName;
                            }
                            else {
                                qualifiedName = unqualifiedName;
                            }
                            return qualifiedName;
                        }
                    }
                    return (T) new NamedLiteral();
                }
                else {
                    return at.getAnnotation(annType);
                }
            }
            @Override
            public Set<Annotation
> getAnnotations() {
                return at.getAnnotations();
            }
            @Override
            public Type getBaseType() {
                return at.getBaseType();
            }
            @Override
            public Set<Type
> getTypeClosure() {
                return at.getTypeClosure();
            }
            @Override
            public boolean isAnnotationPresent(Class<? extends Annotation
> annType) {
                return at.isAnnotationPresent(annType);
            }
            
        };
        
        pat.setAnnotatedType(wrapped);
    }
    
}

Aqui está um segundo exemplo, o qual adiciona a anotação @Alternative a qualquer classe que implementa uma certa interface Service.

class ServiceAlternativeExtension implements Extension {

      
   <T
> void processAnnotatedType(@Observes ProcessAnnotatedType<T
> pat) {
   
      final AnnotatedType<T
> type = pat.getAnnotatedType();
      
      if ( Service.class.isAssignableFrom( type.getJavaClass() ) ) {
      
         //if the class implements Service, make it an @Alternative
         AnnotatedType<T
> wrapped = new AnnotatedType<T
>() {
         
            @Override
            public boolean isAnnotationPresent(Class<? extends Annotation
> annotationType) {
               return annotationType.equals(Alternative.class) ? 
                  true : type.isAnnotationPresent(annotationType);
            }
            
            //remaining methods of AnnotatedType
            ...
         }
         
         pat.setAnnotatedType(wrapped);
      }
   } 
   
}

O AnnotatedType não é a única coisa que pode ser embrulhada por uma extensão.

A interface InjectionTarget expõe operações para produzir e eliminar uma instância de um componente, injetando suas dependências e invocando suas callbacks do ciclo de vida. Uma extensão portável pode embrulhar InjectionTarget para qualquer componente Java EE que suporte injeção, permitindo que ela intercepte qualquer uma destas operações ao serem invocadas pelo contêiner.

Aqui está uma extensão CDI portável que lê valores de arquivos de propriedades e configura campos de componentes Java EE, incluindo servlets, EJBs, managed beans, interceptadores e mais outros. Neste exemplo, as propriedades de uma classe org.mydomain.blog.Blogger vão em um recurso nomeado como org/mydomain/blog/Blogger.properties, e o nome de uma propriedade deve casar com o nome do campo a ser configurado. Assim Blogger.properties deve conter:

firstName=Gavin
lastName=King

A extensão portável funciona ao envolver a InjectionTarget do contêiner e definindo os valores dos campos a partir do método inject().

public class ConfigExtension implements Extension {


        <X
> void processInjectionTarget(@Observes ProcessInjectionTarget<X
> pit) {
                
                //wrap this to intercept the component lifecycle
            final InjectionTarget<X
> it = pit.getInjectionTarget();
            
        final Map<Field, Object
> configuredValues = new HashMap<Field, Object
>();
        
        //use this to read annotations of the class and its members
        AnnotatedType<X
> at = pit.getAnnotatedType();
        
        //read the properties file
        String propsFileName = at.getClass().getSimpleName() + ".properties";
        InputStream stream = at.getJavaClass().getResourceAsStream(propsFileName);
        if (stream!=null) {
            
            try {
                Properties props = new Properties();
                props.load(stream);
                for (Map.Entry<Object, Object
> property : props.entrySet()) {
                    String fieldName = property.getKey().toString();
                    Object value = property.getValue();
                    try {
                        Field field = at.getJavaClass().getField(fieldName);
                        field.setAccessible(true);
                        if ( field.getType().isAssignableFrom( value.getClass() ) ) {
                            configuredValues.put(field, value);
                        }
                        else {
                            //TODO: do type conversion automatically
                            pit.addDefinitionError( new InjectionException(
                                   "field is not of type String: " + field ) );
                        }
                    }
                    catch (NoSuchFieldException nsfe) {
                        pit.addDefinitionError(nsfe);
                    }
                    finally {
                        stream.close();
                    }
                }
            }
            catch (IOException ioe) {
                pit.addDefinitionError(ioe);
            }
        }
        
        InjectionTarget<X
> wrapped = new InjectionTarget<X
>() {
            @Override
            public void inject(X instance, CreationalContext<X
> ctx) {
                it.inject(instance, ctx);
                
                //set the values onto the new instance of the component
                for (Map.Entry<Field, Object
> configuredValue: configuredValues.entrySet()) {
                    try {
                        configuredValue.getKey().set(instance, configuredValue.getValue());
                    }
                    catch (Exception e) {
                        throw new InjectionException(e);
                    }
                }
            }
            @Override
            public void postConstruct(X instance) {
                it.postConstruct(instance);
            }
            @Override
            public void preDestroy(X instance) {
                it.dispose(instance);
            }
            @Override
            public void dispose(X instance) {
                it.dispose(instance);
            }
            @Override
            public Set<InjectionPoint
> getInjectionPoints() {
                return it.getInjectionPoints();
            }
            @Override
            public X produce(CreationalContext<X
> ctx) {
                return it.produce(ctx);
            }
            
        };
        
        pit.setInjectionTarget(wrapped);
        
    }
    
}

Há muito mais sobre extensão portável SPI do que temos discutido aqui. Verifique a especificação CDI ou seu Javadoc para mais informações. Por agora, apenas mencionaremos mais um ponto de extensão.

A interface Context suporta a adição de novos escopos a CDI, ou extensões dos escopos existentes para novos ambientes.

public interface Context {

   public Class<? extends Annotation
> getScope();
   public <T
> T get(Contextual<T
> contextual, CreationalContext<T
> creationalContext);
   public <T
> T get(Contextual<T
> contextual);
   boolean isActive();
}

Por exemplo, poderíamos implementar Context para adicionar um escopo de processo de negócios a CDI, ou para adicionar suporte ao escopo de conversação a uma aplicação que utiliza o Wicket.