Spring AOP stopwatch

Uno de los usos que podemos dar a la programacion orientada a aspectos (AOP) es medir el tiempo que tarda un metodo en ejecutarse. Vamos a ver como podemos usar el framework de Spring con AOP para medir el tiempo que tarda un metodo de una clase en ejecutarse

Lo primero sera crear una clase que haga una operacion sencilla

package es.rubenjgarcia.aop;

public class Foo {

	public void bar()
	{
		for (int i = 0;i<10000000;i++)
		{
			String foobar = "foobar";
			foobar.replaceAll("o", "i");
		}
	}
}

Este es el metodo al que vamos a medir el tiempo que tarda en ejecutarse. Lo siguiente es añadir las dependencias al proyecto Maven

<dependencies>
        <!-- Spring AOP -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-aop</artifactId>
		<version>3.2.8.RELEASE</version>
	</dependency>
        <!-- Necesario para Spring AOP -->
	<dependency>
		<groupId>org.aspectj</groupId>
		<artifactId>aspectjweaver</artifactId>
		<version>1.7.3</version>
	</dependency>
        <!-- Dependencia para la inicializacion del contexto Spring-->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>3.2.8.RELEASE</version>
	</dependency>
        <!-- Para ejecutar tests de Spring -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>
		<version>3.2.8.RELEASE</version>
		<scope>test</scope>
	</dependency>
        <!-- Junit para probar nuestro proyecto -->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.11</version>
		<scope>test</scope>
	</dependency>
</dependencies>

Con esto ya tenemos todas las dependencias necesarias, ahora empecemos a crear el codigo que hara el trabajo

package es.rubenjgarcia.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.util.StopWatch;

@Aspect
public class FooAspect {

	@Around("execution(* es.rubenjgarcia.aop.Foo.*(..))")
	public Object measureMethod(ProceedingJoinPoint pjp) throws Throwable
	{
		StopWatch sw = new StopWatch();
		Object retVal;
		try
		{
			sw.start(pjp.getTarget()+"."+pjp.getSignature());
			retVal = pjp.proceed();
		}
		catch (Throwable e)
		{
			throw e;
		}
		finally
		{
			sw.stop();
			System.out.println(sw.prettyPrint());
		}
		return retVal;
	}
}

Lo primero que vemos en la clase es la anotacion @Aspect que indica que la clase controla aspectos. El unico metodo que hemos definido measureMethod tiene una anotacion, @Around. Esta anotacion sirve para decirle que queremos que este metodo se ejecute «alrededor» de nuestro metodo, es decir, que tenemos el control del metodo antes de su ejecucion y podemos ejecutarlo cuando queramos o incluso no ejecutarlo, ademas de poder devolver otro valor en vez de el que nos devuelve la ejecucion de nuestro metodo

Esta anotacion siempre va acompañada de la definicion que vemos entre parentesis, aqui es donde indicamos los metodos que queremos que sean interceptados. En el caso del ejemplo le hemos dicho que queremos capturar los metodos de cualquier tipo (* – public, protected, …) de la clase es.rubenjgarcia.aop.Foo con cualquier nombre (*) y con cualquier argumento ( (..) ). Para mas informacion sobre como parametrizar esto puedes mirar aqui

El metodo recibe un argumento de tipo ProceedingJoinPoint. Este objecto contiene la informacion del metodo que se va a ejecutar asi como la posibilidad de ejecutarlo

Para medir el tiempo creamos un StopWatch y lo iniciamos dandole el nombre de nuestra clase y el nombre del metodo

Para ejecutar el metodo llamamos al metodo proceed del objeto ProceedingJoinPoint que hemos recibido en nuestro metodo. Por ultimo paramos el reloj y sacamos por pantalla el resultado

Lo siguiente es crear las clases de configuracion de Spring. Al usar Spring AOP solo podemos usar estos aspectos con clases que maneje Spring. Para esto vamos a crear una clase de configuracion de nuestro contexto

package es.rubenjgarcia.aop;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class ConfigBean {

    @Bean
    public Foo foo() {
        return new Foo();
    }
	
    @Bean
    public FooAspect fooAspect() {
        return new FooAspect();
    }
}

El proposito de esta clase es configurar todo el contexto de Spring. En vez de usar una configuracion basada en XML, a partir de la version 3 de Spring se pueden usar anotaciones para configurar el contexto. Para configurar el contexto hay que anotar una clase con la anotacion @Configuration

Usando la anotacion @EnableAspectJAutoProxy le indicamos a Spring que habilite AOP para nuestra aplicacion

La anotacion @Bean es la que inyecta nuestros beans en el contexto para poder usarlo mas adelante

Con esto todo nuestro proyecto ya esta listo para ser probado. Solo necesitamos crear un test que pruebe si todo ha ido correctamente

package es.rubenjgarcia.aop.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import es.rubenjgarcia.aop.ConfigBean;
import es.rubenjgarcia.aop.Foo;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ConfigBean.class)
public class TestAOP {

	@Autowired
	private Foo foo;
	
	@Test
	public void test()
	{
	    foo.bar();
	}
}

Debemos indicar que queremos ejecutar nuestro test unitario con la clase SpringJUnit4ClassRunner para que Spring se inicialice. Ademas hay que indicar como vamos a configurar el contexto, en este caso usamos la clase ConfigBean que hemos creado indicandolo en la anotacion @ContextConfiguration

La anotacion @Autowired que tiene nuestro atributo foo es la que se encarga de inyectar el bean Foo que vamos a usar. Si ejecutamos el test lo que veremos sera algo parecido a esto

StopWatch '': running time (millis) = 4885
-----------------------------------------
ms     %     Task name
-----------------------------------------
04885  100%  es.rubenjgarcia.aop.Foo@4a5f2db0.void es.rubenjgarcia.aop.Foo.bar()

Si quieres descargarte el codigo puedes hacerlo desde mi cuenta en Github pulsando en el siguiente boton