JavaEE 5 interceptors

É inegável que a não-tão-nova versão 5 da especificação JavaEE veio numa hora mais do que necessária. Todo mundo parecia — e com razão — evitá-la ao máximo, pois seu uso demandava muito tempo e caixas de calmante.

Hoje, criar e manter uma aplicação com EJBs é relativamente simples. E, por este motivo, a adoção da tecnologia passou a ser mais expressiva, mesmo em projetos menores.

Alguns aspectos da especificação, entretanto, ainda deixam a desejar. Um exemplo seria a parte de injeção de dependências, que é limitada apenas a componentes gerenciados pelo container. O que isso quer dizer? Isso quer dizer que, para você poder tirar proveito do esquema de injeção de dependências, todos os seus componentes precisam ser EJBs.

Mas, nesta versão, os EJBs não são POJOs? Sim, mas ter de expor classes simples como EJBs — só para ganhar essa “injetabilidade” de presente — não parece correto. E, da mesma forma, espalhar instanciações de objetos pelos vários componentes da aplicação definitivamente também não parece.

Por isso, mostrarei a seguir um exemplo de como melhorar a parte de injeção de dependências para que possamos injetar POJOs declarativamente em objetos gerenciados. Como o Spring é (de longe) o framework mais conhecido, eu o usarei para me ajudar nesta tarefa.

O exemplo

Para demonstrar o efeito desejado, veja o trecho de código a seguir:

public interface Calculator {
    int mult(int a, int b);
}
 
public class CalculatorImpl implements Calculator {
    public int mult(int a, int b) {
        return a*b;
    }
}
 
@Remote
public interface MultiplicationRuler {
    int[] rulerFor(int number, int from, int to);
}
 
@Stateless
public class MultiplicationRulerBean implements MultiplicationRuler {
 
    @InjectBean
    private Calculator calculator;
 
    public int[] rulerFor(int number, int from, int to) {
        int total = to-from+1;
        int[] ruler = new int[total];
 
        for (int i = 0; i < total; i++) {
            ruler[i] = calculator.mult(number, from++);
        }
        return ruler;
    }
}

O objetivo aqui é implementar um componente POJO que multiplica dois números. Este componente é usado por um segundo — este sim um EJB — que fornece um método que funciona mais ou menos igual aquelas réguas de tabuada; o método recebe o número desejado e dois outros números que representam os limites da tabuada. Por exemplo, a chamada abaixo retorna os resultados da tabuada de 10, começando no número 1 e indo até o 15:

MultiplicationRulerBean ruler = new MultiplicationRulerBean();
ruler.rulerFor(10,1,15);

Note, na classe MultiplicationRulerBean, que o atributo Calculator está anotado com @InjectBean. Esta anotação servirá para indicar que este campo representa uma dependência que deve ser resolvida injetando-se uma instância de Calculator.

Usando o Spring para obter dependências

Segue um exemplo de arquivo de contexto appcontext-services.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="calculatorBean" class="CalculatorImpl" />
</beans>

Como nossa única dependência externa é uma instância de Calculator, então o arquivo se resume a apenas uma entrada <bean>. Agora, precisamos criar uma classe que será usada para obter os objetos declarados no arquivo mostrado:

public class SpringBeanContext {
 
    private static final SpringBeanContext instance;
    static {
        instance = new SpringBeanContext();
    }
 
    private ApplicationContext context;
 
    private SpringBeanContext() {
        context = new ClassPathXmlApplicationContext("META-INF/spring/appcontext-*.xml");
    }
 
    public Object getBean(Class<?> clazz) {
        Map<?,?> beans = context.getBeansOfType(clazz);
        if (beans != null && beans.size() > 0) {
            return beans.values().iterator().next();
        }
        throw new NoSuchBeanDefinitionException("There's no bean of type " + clazz);
    }
 
    public static SpringBeanContext getInstance() {
        return instance; // retorna o singleton
    }
}

Como apenas precisamos de um objeto da classe SpringBeanContext, esta foi programada como sendo um Singleton. Podemos ver que, quando o Singleton é instanciado, os arquivos de contexto — que seguem a convenção appcontext-[modulo].xml — são carregados pelo Spring. O método getBean() foi criado para que possamos obter objetos gerenciados pelo Spring.

Definindo a anotação @InjectBean

Agora, precisamos criar uma anotação que utilizaremos para indicar as dependências de nossos EJBs:

@Retention(RetentionPolicy.RUNTIME)
public @interface InjectBean {}

A anotação @InjectBean, que não possui parâmetros e é visível em Runtime, servirá para indicar os atributos que representam dependências. O interceptor, que definiremos a seguir, irá procurar por essa anotação nos EJBs interceptados. Então, para cada campo anotado com essa anotação, o interceptor irá obter o objeto correspondente do contexto do Spring e injetá-lo.

O Interceptor

Finalmente chegamos à classe que é responsável por fazer o trabalho pesado:

public class SpringBeanInterceptor {
 
    @PostConstruct
    public void injectSpringBean(InvocationContext context) throws Exception {
        Object target = context.getTarget();
        for (Field field : getAnnotatedFields(target.getClass())) {
            injectBean(target, field);
        }
        context.proceed();
    }
 
    protected List<Field> getAnnotatedFields(Class<?> clazz) {
        List<Field> fields = new LinkedList<Field>();
        for (Field field: clazz.getDeclaredFields()) {
            if (field.getAnnotation(InjectBean.class) != null) {
                fields.add(field);
            }
        }
        return fields;
    }
 
    protected void injectBean(Object target, Field field) throws Exception {
        Object bean = null;
        bean = SpringBeanContext.getInstance().getBean(field.getType());
        if (!field.isAccessible()) {
            field.setAccessible(true);
        }
        field.set(target, bean);
    }
}

Nada de outro mundo, não? Perceba que usamos a anotação @PostConstruct, o que indica que o método anotado deve ser executado para um EJB após sua criação pelo container. Todo método interceptador deve retornar void e esperar um parâmetro do tipo InvocationContext. Neste método, utilizamos um pouco de Reflection para injetar o objeto fornecido pelo Spring. Por fim, para que outros possíveis interceptors (e o próprio método sendo interceptado) possam ser invocados, chamamos InvocationContext.proceed().

Para finalizar o exemplo, nos resta associar este interceptor com o EJB da aplicação. Uma forma simples de fazer isso seria através do uso da anotação @Interceptors nas classes e métodos desejados. Por exemplo:

@Stateless
@Interceptors(SpringBeanInterceptor.class)
public class MultiplicationRulerBean implements MultiplicationRuler {
 
    @InjectBean
    private Calculator calculator;
 
    // ...
}

Claro que, para este pequeno exemplo, esta abordagem poderia ser utilizada sem nenhum problema. Porém, definir o interceptor dessa forma é muito trabalhoso em situações onde temos muitos EJBs.

Para que possamos evitar o uso repetido da anotação @Interceptors, podemos fazer a configuração via XML, o que nos permite associar um interceptor a vários EJBs através do uso de curingas. Para isso, basta modificar o arquivo ejb-jar.xml:

<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar>
	<interceptors>
		<interceptor>
			<description>Spring-aware interceptor</description>
			<interceptor-class>com.destaquenet.tutorial.interceptor.SpringBeanInterceptor</interceptor-class>
		</interceptor>
	</interceptors>
 
	<assembly-descriptor>
		<interceptor-binding>
			<ejb-name>*</ejb-name>
			<interceptor-class>com.destaquenet.tutorial.interceptor.SpringBeanInterceptor</interceptor-class>
		</interceptor-binding>
	</assembly-descriptor>
</ejb-jar>

Neste caso, estamos associando o interceptor automaticamente a todos os EJBs, nos livrando de ter que configurar manualmente a associação do interceptor com possíveis novos EJBs que a aplicação venha a ter.

Conclusão

Venho observando que frameworks como o Spring e a especificação JavaEE estão trilhando caminhos diretamente opostos; enquando os primeiros estão crescendo descontroladamente e ficando cada vez mais complexos, o segundo está tirando a gordura e ficando mais simples de usar. Claro que cada desenvolvedor tem seus motivos para escolher um ou outro, mas eu particularmente já não vejo tanta vantagem em se adotar o Spring ao EJB. Por outro lado, penso ser mais interessante a junção dessas tecnologias de modo que cada uma contribua com o seu melhor.

Como vimos, a especificação JavaEE 5 — que foi projetada para ser estensível e fácil de usar — conta com componentes denominados interceptors, que nos fornecem um recurso à la AOP, nos permitindo executar código em pontos bem definidos durante a execução da aplicação.

Eu nem preciso dizer que os interceptors podem ser usados para outras coisas além da mostrada aqui. Apenas para citar um exemplo, o framework Seam usa interceptors para implementar o que seus autores chamam de Bijeção — que permite que um componente qualquer possa receber/ejetar dependências de/para um contexto.

Os conceitos apresentados aqui podem ser adaptados para uso com outros frameworks de injeção de dependências, como Guice e PicoContainer.

Veja também:

Tags: , , , , , , , , ,

Um comentário para “JavaEE 5 interceptors”

  1. “mais interessante a junção dessas tecnologias”…Disse tudo!

Deixe um comentário