SeamFramework.orgCommunity Documentation
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:
integração com motores de gerenciamento de processos de negócios (Business Process Management),
integração com frameworks de terceiros, tais como Spring, Seam, GWT ou Wicket, e
novas tecnologias baseadas no modelo de programação 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:
BeforeBeanDiscovery
ProcessAnnotatedType
ProcessInjectionTarget
e ProcessProducer
ProcessBean
e ProcessObserverMethod
AfterBeanDiscovery
AfterDeploymentValidation
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.
Nós recomendamos que frameworks deixem CDI assumir o trabalho de efetivamente instanciar os objetos controladod pelo framework. Deste modo, os objetos controlados pelo framework podem tirar vantagem da injeção pelo construtor. No entanto, se o framework requer o uso de um construtor com uma assinatura especial, o próprio framework precisará instanciar o objeto, e então somente injeção por método e campo serão suportados.
//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 <T 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.