AOP con Spring framework

Introducción

¿Qué es AOP?

Aspect-Oriented Programming (AOP) es un paradigma de programación que nos permite separar “aspectos” o “intenciones” de nuestro software en diferentes módulos según su responsabilidad. De esta manera puede verse como otra herramienta para poder diferenciar en nuestro código las diferentes necesidades, encapsulando los diferentes conceptos como ser los requerimientos funcionales, seguridad, profiling de tiempos, transaccionabilidad contra una base de datos, logging, entre otros.

En Wikipedia ofrecen una definición bastante completa de los términos básicos utilizados en el mundo AOP:

  • Aspect (Aspecto) es una funcionalidad transversal (cross-cutting) que se va a implementar de forma modular y separada del resto del sistema. El ejemplo más común y simple de un aspecto es el logging (registro de sucesos) dentro del sistema, ya que necesariamente afecta a todas las partes del sistema que generan un suceso.
  • Join point (Punto de Cruce o de Unión) es un punto de ejecución dentro del sistema donde un aspecto puede ser conectado, como una llamada a un método, el lanzamiento de una excepción o la modificación de un campo. El código del aspecto será insertado en el flujo de ejecución de la aplicación para añadir su funcionalidad.
  • Advice (Consejo) es la implementación del aspecto, es decir, contiene el código que implementa la nueva funcionalidad. Se insertan en la aplicación en los Puntos de Cruce.
  • Pointcut (Puntos de Corte) define los Consejos que se aplicarán a cada Punto de Cruce. Se especifica mediante Expresiones Regulares o mediante patrones de nombres (de clases, métodos o campos), e incluso dinámicamente en tiempo de ejecución según el valor de ciertos parámetros.
  • Introduction (Introducción) permite añadir métodos o atributos a clases ya existentes. Un ejemplo en el que resultaría útil es la creación de un Consejo de Auditoría que mantenga la fecha de la última modificación de un objeto, mediante una variable y un método setUltimaModificacion(fecha), que podrían ser introducidos en todas las clases (o sólo en algunas) para proporcionarlas esta nueva funcionalidad.
  • Target (Destinatario) es la clase aconsejada, la clase que es objeto de un consejo. Sin AOP, esta clase debería contener su lógica, además de la lógica del aspecto.
  • Proxy (Resultante) es el objeto creado después de aplicar el Consejo al Objeto Destinatario. El resto de la aplicación únicamente tendrá que soportar al Objeto Destinatario (pre-AOP) y no al Objeto Resultante (post-AOP).
  • Weaving (Tejido) es el proceso de aplicar Aspectos a los Objetos Destinatarios para crear los nuevos Objetos Resultantes en los especificados Puntos de Cruce. Este proceso puede ocurrir a lo largo del ciclo de vida del Objeto Destinatario:
    • Aspectos en Tiempo de Compilación, que necesita un compilador especial.
    • Aspectos en Tiempo de Carga, los Aspectos se implementan cuando el Objeto Destinatario es cargado. Requiere un ClassLoader especial.
    • Aspectos en Tiempo de Ejecución.

AOP con Spring Framework

Spring Framework nos ofrece un potencial muy interesante, utilizando el framework de AOP AspectJ, y permitiéndonos usarlo declarando beans muy similares a los tradicionales, y ofreciendo la suficiente flexibilidad para la gran mayoría de los escenarios.
Podemos modificar o agregar comportamiento de nuestros beans en diferentes momentos de la invocación:

  • Antes de invocar a un metodo.
  • Luego de invocar un método, si retornó OK.
  • Luego de invocar un método, si lanzó excepción.
  • Reemplazar la ejecución de un método.

Ejemplos de uso

A continuación veremos algunos casos de uso típicos en los que AOP aplica como solución.

Transaccionabilidad contra una base de datos

Escribí otro post que se centra en este tema, y uno de los enfoques que plantea para resolver esta problemática es definir las transacciones de forma declarativa utilizando AOP.

Profiling

Supongamos que queremos medir y enviar a un log el tiempo que demora la invocación a un conjunto de métodos de nuestras clases. Una opción sería agregar un cronómetro en cada uno de los métodos a monitorear -tomando el tiempo al iniciar, y tomando nuevamente el tiempo en un finally antes de retornar-. Otra opción es crear un aspecto al cual le especificamos qué métodos serán los que tiene que cronometrar.

Definiremos entonces una clase totalmente aislada del resto de la lógica de nuestro sistema, encargada de iniciar un cronómetro, ejecutar el método, parar el cronómetro y enviar a System.out el tiempo insumido.

package demo.aop;

import org.
aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

public class AopProfiler {

	public Object doProfile(ProceedingJoinPoint call) throws Throwable {

		StopWatch clock = new StopWatch("Started profiling");
		
		try {
			clock.start(call.toShortString());
			return call.proceed();
		} finally {
			clock.stop();
			System.out.println(clock.prettyPrint());
		}
	}
}

Utilizando Spring y AspectJ, la única restricción impuesta a la clase que define el aspecto es que tenga un método que recibe un objeto de tipo ProceedingJoinPoint y que lance Throwable; no son importantes ni el nombre de la clase ni el del método.

La configuración de los beans en Spring quedaría de la siguiente forma:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:aop="http://www.
springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

	<bean name="aopProfiler" class="demo.aop.AopProfiler" />

	<aop:config>
		<aop:aspect ref="aopProfiler">
			<aop:pointcut id="pointcut" expression="execution(public * demo.dao.PersonDAOImpl.getById(..))" />
			<aop:around method="doProfile" pointcut-ref="pointcut" />
		</aop:aspect>
	</aop:config>
</beans>

Lo destacable de esta configuración son los namespaces que agregamos en el elemento para soportar los beans especiales para la configuración de AOP. El bean llamado “aopProfiler” no tiene nada de especial, simplemente es una referencia a la clase antes definida. La magia ocurre en el bean , en donde definimos el aspecto. define el pointcut del aspecto. Este pointcut en particular significa que el aspecto se va a ejecutar cada vez que ejecutemos el método getById de la clase demo.dao.PersonDAOImpl, sin importar qué parametros le pasemos. El elemento describe el comportamiento del aspecto; cuando se detecte una invocación a este método, el framework desviará la llamada al método doProfile de la clase definida en el bean aopProfiler. Cabe notar que parte de la implementación de este aspecto es invocar al método original (invocando el método org.aspectj.lang.ProceedingJoinPoint.proceed()), pero no es mandatorio.

Logging

Otra aplicación de AOP interesante podría ser registrar las invocaciones a cada método de un conjunto determinado de paquetes, clases o métodos de nuestra aplicación. Adaptando el ejemplo anterior no sería muy difícil implementar un aspecto de logging.
Podríamos darle una funcialidad extra, registrando además los parámetros de la invocación; el método org.aspectj.lang.ProceedingJoinPoint.getArgs() nos abre esta posibilidad, retornando un Object[] con los parámetros de invocación.

Seguridad

El control de acceso a nuestros objetos es otra aplicación típica en las que AOP encaja de forma muy natural. En este caso no daremos ejemplos puntuales de implementación, sino que nos referiremos a un producto ya maduro y centrado especialmente en este punto: Spring Security.

Una de las funcionalidades disponibles en Spring Security es la configuración de restricciones de acceso a los métodos de nuestra lógica de negocios. Esta configuración de las restricciones se puede hacer mediante annotations como por ejemplo @Secured de la siguiente forma:

npublic interface BankService {

	@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
	public Account readAccount(Long id);

	@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
	public Account[] findAccounts();

	@Secured("ROLE_TELLER")
	public Account post(Account account, double amount);
}

Otra forma posible -la que nos interesa destacar- es definiendo pointcuts específicos para este fin, de la forma:

<global-method-security>
	<protect-pointcut expression="execution(* com.mycompany.*Service.*(..))" access="ROLE_USER"/>
</global-method-security>

Este enfoque es particularmente potente; este ejemplo en particular nos permite definir en 3 líneas una restricción de acceso a todos los métodos de las clases llamadas *Service del paquete com.mycompany.

Por información más detallada, referirse a la documentación y ejemplos de Spring Security.

Conclusiones

Si bien se puede argumentar que el uso de las herramientas de AOP pueden impactar en la performance de una aplicación (aunque la performance no fue considerada en este post), nos abre un abanico de posiblidades en lo que tiene que ver con mejorar la claridad de nuestro código, dado que separamos la lógica de negocios de otros aspectos de nuestra aplicación como ser transaccionabilidad, logging, verificación de permisos de acceso, entre otros.
Gracias a esta separación de responsabilidades nuestra lógica que más simple de verificar, ya que es más fácil escribir POJOs -y sus correspondientes tests- que encapsulen las funciones para la que fueron concebidas.

Referencias

Etiquetado , , . Bookmark the permalink.

Una respuesta a AOP con Spring framework

  1. Santiago says:

    Muy buena info!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *