La siguiente tecnología incluida en Java EE 7 de la que vamos a hablar son los interceptores, los cuales nos permites realizar alguna acción antes de que los métodos sean llamados. Aunque está mal usar la palabra definida en la definición, es un modo de interceptar invocaciones a métodos para de esta forma poder realizar alguna tarea previa a su ejecución o posterior a ella. También se pueden encadenar interceptores para realizar varias tareas diferentes. Un uso muy común suele ser para realizar tareas como generar logs de los métodos ejecutados.
Existen cuatro tipos de interceptores:
- Interceptores a nivel del constructor: @AroundConstruct
- Interceptores a nivel de método: @AroundInvoke
- Interceptores asociados a timeout de métodos: @AroundTimeout (solo se usan en EJB con servicios de timer)
- Interceptores a nivel de ciclo de vida: @PostConstruct y @PreDestroy
Para el primer ejemplo vamos a mostrar uno a nivel de método que es el más simple e intuitivo (no significa que los demás sean complejos):
public class MyService { @Inject private Logger logger; public void actionOne(MyObject obj) { ... } public void actionTwo(MyObject obj) { ... } @AroundInvoke private Object logMethod(InvocationContext ic) throws Exception { logger.entering(ic.getTarget().toString(), ic.getMethod().getName()); try { return ic.proceed(); } finally { logger.exiting(ic.getTarget().toString(), ic.getMethod().getName(); } } }
La primera cosa en la que tenemos que prestar atención es en la utilización de la anotación para marcar cual es el método que se va a utilizar como interceptor, la segunda, e igualmente importante, es que el prototipo del método utilizado como interceptor debe ser:
[public|private|protected] Object <methodName>(Invocationcontext ic) throws Exception;
Y dicho prototipo ni puede ser static o final.
Básicamente cuando alguna clase invoque alguno de los métodos de nuestra clase, esta llamada será interceptada por nuestro interceptor. La secuencia de ejecución para una llamda al método “actionOne” será:
- Llamada a “actionOne”
- Interceptación. Llamada a “logMethod”.
- Escritura del log: “entering”.
- Ejecución del método invocado: “proceed”.
- Escritura del log: “exiting”.
Mostrar un ejemplo del resto de tipos de interceptores no tiene mucho sentido, ya que no es más que seleccinar la anotación adecuada y colocarla de igual modo que hemos hecho con “@AroundInvoke”.
Lo que si que va a resultar interesante, y seguro que más de uno ya ha pensado es, ¿y tengo que crear un interceptor por cada clase para poder escribir en el log (nuestro caso particular)? Pues bien, la respuesta es no. Podemos crear un interceptor genérico y aplicarlos a las clases o métodos que deseemos. Para ello vamos a crear este interceptor genérico y luego ver como usarlo con los siguientes ejemplo:
Interceptor genérico:
public class LoggingInterceptor { @Inject private Logger logger; @AroundConstruct private void init(InvocationContext) throws Exception { logger.fine("My logging interceptor constructor: Entering"); try { ic.proceed(); } finally { logger.fine("My logging interceptor constructor: Exiting"); } } @AroundInvoke public Object logMethod(InvocationContext) throws Exception { logger.entering(ic.getTarget().toString(), ic.getMethod().getName()); try { return ic.proceed(); } finally { logger.exiting(ic.getTarget().toString(), ic.getMethod().getName(); } } }
Usándolo en una clase a nivel de método, tendríamos el siguiente resultado:
public class MyService { @Interceptors(LoggingInterceptor.class) public void actionOne(MyObject obj) { ... } public void actionTwo(MyObject obj) { ... } }
Con esto nuestro inerceptor sería llamada cada vez que el método “actionOne” sea invocado, pero no al llamar al método “actionTwo”.
También podemo asignar el interceptor a nivel de clase, donde todos los método llamados de esa clase invocarían a nuestro interceptor:
@Interceptors(LoggingInterceptor.class) public class MyService { public void actionOne(MyObject obj) { ... } public void actionTwo(MyObject obj) { ... } }
E incluso si tenemos muchos métodos en una clase y queremos que alguno de ellos no invoque a nuestro interceptor, pero los demás sí, podemos excluir este método:
@Interceptors(LoggingInterceptor.class) public class MyService { public void actionOne(MyObject obj) { ... } public void actionTwo(MyObject obj) { ... } @ExcludeClassInterceptors public void actionTree(MyObject obj) { ... } }
Como añadido a esto, y para potenciar aún más el uso de interceptores, estás anotaciones nos permiten mucha más versatilidad aún, como la de invocar varios y diferentes interceptores según necesitemos, pudiendo hacer cosas como estas:
@Interceptors({Int1.class, Int2.class}) public class MyService { public void actionOne(MyObject obj) { ... } @Interceptors({Int3.class, Int4.class}) public void actionTwo(MyObject obj) { ... } @ExcludeClassInterceptors public void actionTree(MyObject obj) { ... } }
Aunque no lo parezca, el orden en el que aparecen los interceptores en la anotación es importante, ya que será este el orden en el que se invocarán.
Pero si pensabais que aquí habíamos terminado, estáis equivocados. Aún nos queda una forma más de declarar nuestros interceptores, por suerte, es una forma muy similar a la que se definen los “Qualifiers” que vimos en el artículo anterior. Se suele conocer como “Interceptor Binding”, y aquí si que lo siento, porque no he encontrado ninguna traducción que me haya convencido. Decir antes de empezar, que para poder utilizar este tipo necesitamos de forma obligatoria tener el soporte para CDI activado. De esta forma podremos hacer cosas como estas:
Interceptor Binding:
@InterceptorBinding @Target({METHOD, TYPE}) @Retention(RUNTIME) public @interface Loggable { }
Interceptor:
@Interceptor @Loggable public class LoggingInterceptor { @Inject private Logger logger; @AroundInvoke public Object logMethod(InvocationContext ic) throws Exception { ... } }
Nuestro servicio:
@Loggable public class MyService { public void actionOne(MyObject obj) { ... } public void actionTwo(MyObject obj) { ... } @ExcludeClassInterceptors public void actionTree(MyObject obj) { ... } }
Se puede utilizar tanto a nivel de clase como de método.
En este punto tenemos que añadir que estos interceptores están deshabilitados por defecto, con lo cual tenemos que añadir en nuestro “bean.xml” explicitamente que queremos usarlos. Esto sería añadiendo al fichero el siguiente nodo:
<interceptors> <class>org.example.LoggingInterceptor</class> </interceptors>
Y ya por fín, lo último. Utilizando este último tipo de interceptores perdemos la posibilidad de ordenar estos según el orden de ejecución que deberían seguir. Para paliar esta desventaja se ha introducido el concepto de prioridad, pudiendo asignar un valor numérico positivo a cada interceptor. Cuanto más bajo sea el valor, antes se ejecutará el interceptor.
En nuestro ejemplo, el código del interceptor quedaría algo como:
@Interceptor @Loggable @Priority(100) public class LoggingInterceptor { @Inject private Logger logger; @AroundInvoke public Object logMethod(InvocationContext ic) throws Exception { ... } }
Bueno, hasta aquí hemos llegado. Espero que os haya sido de utilidad y que os haya quedado claro toda esta parte de los interceptores. Nos vemos.