Durante la
última entrada de la serie relacionada con la programación orientada a aspectos analizamos el modo en el que se puede alterar el comportamiento de un sistema, analizando las categorías de advices y realizando una comparativa con los métodos de una clase. A lo largo de esta entrada realizaremos un análisis en profundidad de los
advices.
Before advice
Este tipo de advices se ejecutan antes de la ejecución del joint point sobre el que actúan. En el siguiente ejemplo:
before():execution(@Secured * * (..)){
// asegurarse de que el usuario puede realizar la operación
}
el advice realiza una comprobación de seguridad antes de que se produzca la ejecución de cualquier método anotado con Secured.
En caso de que el advice dispare un excepción, el joint point no se ejecutará. Este tipo de advices son comúnmente utilizados en aspectos tales como seguridad o trazabilidad.
After advice
Se ejecutan después de la ejecución del joint point sobre el que actúan.Dentro de esta categoría, AspectJ ofrece tres tipos de advices:
- Ejecución del advice independientemente del resultado de la ejecución del joint point.
- Ejecución del advice única y exclusivamente si el joint point ha finalizado correctamente.
- Ejecución del advice después que el joint point haya disparado una excepción.
Veamos en detalle cada uno de los tres tipos anteriores:
1.- Advice After
Este tipo de advices se ejecutan independientemente del resultado de la ejecución del joint point sobre el que actúan.Habitualmente se conoce a este tipo de advices como after finally puesto que su semántica es similar a la de un bloque finally.
El siguiente advice:
after(): call(@Logging * ServiceManager.*(..)){
// registrar el resultado de la operación
}
registra el resultado de todas las operaciones de la clase
ServiceManager que estén marcadas con la anotación
Logging, independientemente si retornan correctamente o terminan su ejecución de forma inesperada mediante el disparo de una excepción.
2.-Advice After Returning
En muchas ocasiones,será necesario ejecutar el código de nuestro advice única y exclusivamente cuando la ejecución del joint point haya terminado de forma correcta. Continuando con el ejemplo anterior:
after () returning: call(@Logging * ServiceManager.*(..)){
// registrar el resultado de la operación
}
se seguirá registrando el resultado de las operaciones, siempre y cuando, la ejecución haya terminado correctamente, sin el disparo de ninguna excepción.
AspectJ ofrece una pequeña variante para este tipo de advices:
after() returning (ReturnType returnObject)
gracias a la cual se permite recuperar el objeto retornado por la ejecución del joint point dentro del advice. Veamos un pequeño ejemplo ilustrativo:
after() returning (java.sql.Connection connection):
call(java.sql.Connection DriverManager.getConnection(..)){
System.out.println("Se ha recuperado la conexión "
+ connection);
}
Es importante tener claro que no se puede retornar un objeto nuevo (sí puede ser modificado pero no retornar uno nuevo).
3.- Advice After Exception
Este tipo de advices son similares a los descritos en el apartado anterior. En este caso, el advice se ejecutará única y exclusivamente cuando el joint point dispare una excepción. Presentan la siguiente estructura:
after() throwing:execution (* ServiceManager+.*(..))
El advice del ejemplo anterior se ejecutará siempre y cuando algún método de la clase
ServiceManager (o alguna de sus subclases) dispare una excepción. En el supuesto de que la ejecución del joint point termine correctamente, este tipo de advices no serán ejecutados.
Al igual que los advices del apartado anterior,
AspectJ ofrece un modo de recuperar la excepción que ha sido disparada por el joint point de manera que esté disponible en el cuerpo del advice. Siguiendo una sintaxis similar a la anterior, tendríamos:
after() throwing (ExceptionType exceptionObject):
Un after throwing advice nunca podrá tragarse la excepción, por lo que seguirá subiendo por la pila de llamadas hasta llegar al objeto que realizó la invocación del joint point.
Around advice
Este clase de advices engloban al joint point, pudiendo ejecutar la lógica del mismo un número indefinido de veces. Incluso pueden omitir la ejecución del propio joint point. Algunos de los usos principales de este tipo de advices son los siguientes:
- Ejecución de lógica adicional antes y después de la ejecución de un joint point, como por ejemplo, acciones de profiling.
- Omitir la ejecución original, y realizar otra en su lugar, como por ejemplo, operaciones con cachés.
- Envolver la operación con el objetivo de aplicar una política de gestión de excepciones. Un ejemplo de este uso sería la gestión de transacciones.
Este advice ofrece una potencia superior a todos los advices vistos hasta el momento, puesto que podrían sustituir a los anteriores. De todos modos, se considera una buena práctica utilizar el advice más sencillo que cumpla las necesidades de la tarea que necesita ser llevada a cabo.
Si desde el around advice se desea llevar a cabo la ejecución del joint point, será necesario hacer uso de la palabra reservada
proceed() dentro del cuerpo del advice. Recuérdese que, puesto que la invocación de
proceed() ejecuta el joint point, deberán pasarse el mismo número de argumentos que han sido recolectados por el advice.
Asimismo, puesto que la invocación de
proceed() supone la ejecución del joint point, el valor de retorno será el retornado por éste último.
A continuación se adjunta un pequeño ejemplo de utilización de advices de este tipo:
void around(User user,int credits)
throws InsufficientCreditsException:
call(* User.pay*(int)) && target(user) & & args(credits){
try
{
proceed(user,credits);
}catch(InsufficientCreditsException ex){
if(!processException()){
throw ex;
}
}
Analicemos en detalle la construcción anterior:
- El pointcut selecciona cualquier llamada a los métodos de la clase User cuyo nombre comience por pay y disparen una excepción de tipo InsufficientCreditsException.
- La segunda parte del pointcut recolecta el contexto del joint point: el usuario sobre el que se está realizando la llamada y el número de créditosque se están pasando como argumento del método que se está ejecutando.
- En el cuerpo del advice, se engloba la ejecución del método con un bloque de gestión de excepciones, para realizar una protección adicional en caso de que se produzca una excepción. En el caso de que la protección adicional no sea correcta, la excepción será disparada de nuevo.
Todos los around advices deben declarar un valor de retorno (pudiendo ser void). Habitualmente el tipo de retorno de éstos se corresponde con el tipo de retorno de los joint points sobre los que está actuando.
En algunas ocasiones, todos los joint points sobre los que actúa el advice no presentan el mismo tipo de retorno, como puede ocurrir cuando estamos añadiendo soporte transaccional a diferentes operaciones. En estas situaciones el tipo de retorno que debe declarar el advice será Object. AspectJ acomodará el valor de retorno de acuerdo a las siguientes reglas:
- Si se está retornando un tipo primitivo, AspectJ realizará el boxing/unboxing correspondiente. Esta característica es similar a la incluida a partir de Java 5, pero AspectJ no precisa de dicha versión de Java para realizar la operación.
- En el caso en el que el tipo de retorno no sea primitivo, AspectJ realizará los casts oportunos antes de retornar el valor.
Muchas ocasiones es necesario acceder a los objetos que conforman la ejecución del joint point para que el advice pueda llevar a cabo la lógica correspondiente. Por tanto, los pointcuts, necesitan exponer el contexto disponible en la ejecución del joint point de modo que pueda estar disponible en el cuerpo del advice. Dicho contexto puede definirse de dos modos diferentes:
- Objetos (incluyendo los tipos primitivos) que conforman el joint point.
- Anotaciones asociadas al joint point.
La siguiente tabla describe el conjunto de pointcuts que AspectJ ofrece para recuperar el contexto en los joint points.
Table 1.14. Pointcuts para recuperar el contexto en un joint point
Pointcut | Contexto recuperado |
this(obj) | Objeto this en el joint point que se está
ejecutando |
target(obj) | Objetivo de la llamada en el joint
point que se está ejecutando. En el caso de un
joint point de una
llamada a un método, el target será el objeto que realiza la llamada.
Para la ejecución de un método, el target será el objeto this. En los accesos a campos, el target será el objeto que se está
accediendo. En el resto de joint
points no existe un target disponible |
args(obj1,obj2,...) | Objetos que representa los argumentos en el joint point. Para las
llamadas/ejecuciones de métodos/constructores, recupera los
argumentos de los mismos. En el caso de los manejadores de
excepciones, recupera la excepción producida. Para los accesos
en modo escritura a un campo, recupera el nuevo valor del
campo. |
@this(annot) | Anotación asociada con el tipo del objeto this del joint point |
@target(annot) | Anotación asociada con el tipo del objeto target del joint point |
@args(annot1, annot2,
...) | Anotación asociada con el tipo de los argumentos del joint point |
@within(annot) | Anotación asociada con el tipo "enclosing" del joint point |
@withincode(annot) | Anotación asociada con el método "enclosing" del joint point |
annotation(annot) | Anotación asociada con el asunto actual del joint point. |
Durante la próxima entrada analizaremos el concepto de Aspecto.
Hasta pronto!