Estendendo o container JavaEE com 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;
-
}
-
}
-
-
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 serve para indicar que este campo representa uma dependência, e que esta dependência deve ser resolvida injetando-se uma instância de Calculator no campo anotado.
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");
-
}
-
-
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 instanciados pelo Spring. Então, para que possamos recuperar objetos desse contexto, temos o método getBean().
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 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 serviço sujo:
-
-
public class SpringBeanInterceptor {
-
-
@PostConstruct
-
injectBean(target, field);
-
}
-
context.proceed();
-
}
-
-
protected List<Field> getAnnotatedFields(Class<?> clazz) {
-
List<Field> fields = new LinkedList<Field>();
-
if (field.getAnnotation(InjectBean.class) != null) {
-
fields.add(field);
-
}
-
}
-
return fields;
-
}
-
-
Object bean = null;
-
bean = SpringBeanContext.getInstance().getBean(field.getType());
-
if (!field.isAccessible()) {
-
field.setAccessible(true);
-
}
-
field.set(target, bean);
-
}
-
}
-
Nada de outro mundo, huh? 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 reflexão 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. O que acaba sendo chato é que, quando precisamos interceptar um número maior de classes e/ou métodos, esta solução acaba sendo trabalhosa.
Para resolver este problema, 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>br.org.ourjug.service.interceptor.SpringBeanInterceptor</interceptor-class>
-
</interceptor>
-
</interceptors>
-
-
<assembly-descriptor>
-
<interceptor-binding>
-
<ejb-name>*</ejb-name>
-
<interceptor-class>br.org.ourjug.service.interceptor.SpringBeanInterceptor</interceptor-class>
-
</interceptor-binding>
-
</assembly-descriptor>
-
</ejb-jar>
-
Neste caso, estamos associando o Interceptor a todos os EJBs, nos livrando de ter que configurar manualmente a associação do Interceptor com possíveis novos EJBs que o sistema 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 eles 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.
Foto por: oskay
Tags: annotations, aop, container, design, ejb, ioc, java, javaee 5, reflection, spring

1 de outubro de 2007 às 11:58 pm
Fala Daniel,
Muito boa observação.
Apenas gostaria de acrescentar que a “simplicidade” de um framework não está no quanto você digita de código para fazer ele funcionar, mas sim em que tecnologias/conceitos você precisa dominar para fazer a mesma coisa.
Se você olhar bem, não há muita diferença entre Spring e EJB. Nunca houve, e possivelmente nunca haverá. Há sim um grande Hype em torno dos dois, hype feito por duas comunidades com focos diferentes. No entanto ambos fazem a mesma coisa com o mesmo conceito, apenas com sintaxes diferentes. Simplicidade não é isso.
Simplicidade é quando o framework faz coisas que o programador não precisa saber. Ou, mesmo, quando o framework faz APENAS o que o programador quer que ele faça, sem que o programador necessite se adaptar a ele.
[]s
2 de outubro de 2007 às 12:31 am
Fala ae
O foco desse post nem foi relacionado ao quanto se digita e tal, mas você tocou num ponto importante. Só para exemplificar, uma coisa que acontece comigo é o lance do Rails… todo mundo fala que é muito fácil, que até um cachorro consegue fazer sites Web 2.0 e blá-blá-blá. Mas, pelo menos eu, quando tento entender algo além do “feijão-com-arroz” dos CRUDs, eu fico patinando e não saio do lugar. Talvez eu seja burro demais, mas eu acho o treco difícil. Bom, na verdade isso não importa, pois gostei mais do Seaside, mesmo!
Não sei dizer se o Spring sempre foi gordo como é hoje, mas a coisa tá feia: é Spring MVC, Spring PQP, Spring OMFG etc. Mas eu vou aproveitar para fazer alguns questionamentos: se não fosse pelo Spring, será mesmo que o JavaEE 5 estaria do jeito que está (melhor)? Ou ainda, será que existiria o tal do JavaEE 5?
2 de outubro de 2007 às 12:51 am
Olá Daniel,
Sim, o Spring fez o pessoal do EJB se mexer. Mas foi só no começo. Está acontecendo a mesma coisa com o Ruby hoje. No entanto, quero salientar que tentar simplificar algo complexo é mais difícil do que parece (Normalmente não funciona). É muito mais fácil escrever algo simples do zero.
Você não é burro não. Só está muito acostumado ao Java
e esse mundo de padrões e sopas de letrinhas
[]s
2 de outubro de 2007 às 11:02 am
É, a influência do Spring no EJB já não é mais a mesma… o problema é que os caras não podem simplesmente escrever e re-implementar a spec do zero.
Imagine só, a compatibilidade retroativa iria para o brejo. Além disso, todos os App Servers teriam de se adequar a nova spec, o que é bem difícil.
Se a maioria dos App Servers ainda não fornecem uma implementação compatível com a não-tão-nova versão 5, imagine o que aconteceria — ou quanto tempo demoraria — para que essa nova versão “totalmente reformulada” fosse suportada. (até pq, só a reformulação da spec levaria anos, vc. sabe como a coisa no JCP funciona…)
[]s