quinta-feira, 13 de agosto de 2009

Represando execução de métodos Java através de Reflection

A Reflection API é um poderoso recurso da JVM que torna possível invocação de métodos de objetos em memória através de forma programática.
Quando bem utilizado, esse recurso pode executar tarefas extremamente arrojadas.


Requisito:

Represar todos os métodos DAOs em uma sessão web, e executá-los todos de uma vez dentro de uma transação ao final da sessão
JVM 5 + Spring + Jetty 1.6


Solução:

Interceptando todas as chamadas dos métodos DAOs, armazeno uma sequência de invocações em uma Collection em um Statefull Session Bean, de forma que fiquem represados em memória.

StatefullBeanInterface:


addCallStack(Object target, String methodName, Object... args);



ExampleDAO:


@Transactional(propagation = Propagation.MANDATORY)
addUser(User user) {
// real persistence operation
}

addUser(StatefullBean s, User user) {
// interceptor operation
s.addCallStack(this, "addUser", user);
}


Ao final da sessão, o usuário confirma a execução dos métodos. Agora entra em ação a Reflection.


@Transactional(propagation = Propagation.REQUIRES_NEW)
public void execCallStack() throws CallStackException {

for (Object o : callStack.keySet()) {

List<MethodCall> call = callStack.get(o);

for (MethodCall method : call) {

Object[] args = method.getArgs();
Method[] allMethods = o.getClass().getDeclaredMethods();

for (Method m : allMethods) {

if (m.getName().equals(method.getMethodName()) && m.getGenericParameterTypes().length == args.length) {
try {
m.invoke(o, args);
break;
} catch (Exception e) {
throw new CallStackException(e);
}
}
}
}
}
}


Passo a passo:


@Transactional(propagation = Propagation.REQUIRES_NEW)
public void execCallStack()throws CallStackException {


Solicitamos ao container que crie uma nova transação ao iniciar a execução dos métodos DAO represados.
Os métodos são anotados com propagação de transação mandatória (Propagation.MANDATORY). Dessa forma, se forem invocados foram de uma transação, uma exceção será gerada.


for (Object o : callStack.keySet()) {
List<MethodCall> call = callStack.get(o);
for (MethodCall method : call) {
Object[] args = method.getArgs();


Estamos percorrendo a Collection com os objetos alvo da invocação, o nome dos métodos e seus respectivos argumentos.



Method[] allMethods = o.getClass().getDeclaredMethods();
for (Method m : allMethods) {


Aqui começa a reflection. getDeclaredMethods retorna todos os métodos de uma classe. A classe Method encapsula suas declarações, como argumentos, exceções, encapsulamento, entre outros.



if (m.getName().equals(method.getMethodName()) && m.getGenericParameterTypes().length == args.length) {


Procuramos o método correspondente através do nome e a quantidade de argumentos, para minimizar eventuais problemas com sobrecarga.
Checagens adicionais podem ser feitas, porém no caso acima não houve necessidade.


m.invoke(o, args);


Finalmente o método invoke executa o código. O primeiro argumento é uma instância Object que implemente a Classe alvo, e o segundo argumento é um vetor com os métodos.
A CallStackException é uma exceção Runtime criada especificamente para essa situação, para efetuar o rollback das operações em banco em caso de algum método falhar.

Mais informações:

http://java.sun.com/docs/books/tutorial/reflect/
http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html