Páginas

martes, 28 de agosto de 2012

Tracear tiempos de ejecución con AOP

En ocasiones las aplicaciones que desarrollamos deben realizar pruebas de rendimiento para determinar cómo responden y detectar los posibles errores que podemos llegar a tener en un entorno estresado. Muy común entre estas pruebas de rendimiento es que se nos pida el estudio de los tiempos de acceso a base de datos y el tiempo que nuestros métodos requieren para devolver los datos que se les pide para lo que solemos vernos obligados a introducir código en todos nuestros métodos.

Generar un log con el tiempo que un método tarda en ejecutarse es muy simple. Tan sólo necesitamos un par de líneas de código:


public List<User> getAllUsers() throws DaoException { Date d = new Date(); List<User> users= null; try { // code log.debug("method time: " + (new Date().getTime() - d.getTime()) + " ms"); return users; } catch (Exception e) { log.error("Error al insertar un organismo - " + e.toString()); throw new DaoException(e, "error getting users..."); } }

Sin embargo, ¿qué sucede si tenemos un centenar de métodos para el acceso a base de datos? ¿Debemos ir uno a uno introduciendo esas líneas de código? Fácil pero laborioso.

Para simplificarnos la vida y evitar el copy/paste utilizaremos AOP en colaboración con Spring. Con AOP podremos logar los tiempos de todos de cada uno de nuestros métodos sin necesidad de ir método a método copiando y pegando las líneas de código anteriores.

Lo primero que necesitamos es importar las librerías necesarias para ello (nos centraremos sólo en las librerías AOP dejando a un lado el resto de librerías, tales como las librerías de Spring)


<!-- AOP --> <dependency> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.12</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.12</version> </dependency>


Seguidamente, creaemos nuestros aspecto y un "advice" de tipo "around" que será el que calcule el tiempo de ejecución de nuestros métodos:


package es.whatabout.service.db.aop; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; @Aspect @Component public class Performance { private final Log log = LogFactory.getLog(Performance.class); /** * http://tomasjurman.blogspot.com.es/2010/01/spring-aop-with-annotation.html * * @param joinPoint * @return object * @throws Throwable */ @Around("execution(* es.whatabout.dao.*.* (..))") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { StopWatch clock = new StopWatch("performance_dao"); try { clock.start(); log.debug("Comenzando metodo: " + joinPoint.getSignature().getName()); return joinPoint.proceed(); } finally { clock.stop(); log.debug("Consumo: " + clock.prettyPrint()); } } }


No olvidemos declarar nuestra clase como un Aspecto y un Componente. Destacar también, en en la declaración del punto de corte hemos indicado que queremos que el advice se ejecute para todas las clases y métodos (*.*) del paquete es.whatabout.dao sean cuales sean cuales sean su parámetros ( (...) ) o retorno (*)

Por último, sólo queda configurar Spring correctamente de la siguiente forma:


<sws:annotation-driven /> <aop:aspectj-autoproxy proxy-target-class="true" /> <!-- enable component scanning (beware that this does not enable mapper scanning!) --> <context:component-scan base-package="es.whatabout.*" /> <!-- enable autowire --> <context:annotation-config />


Limpio y rápido. Si además, mañana queremos eliminar por completo cualquier log sobre tiempos, sólo debemos cambiar la configuración.

Tras el proxy

Trabajar tras un proxy siempre es un problema. Desde mi punto de vista todos son desventajas ya que pierdo más tiempo luchando con éste que el tiempo que éste me pueda salvar evitando que entre en páginas "no autorizadas" pero está claro que a veces no hay opción.

Cuando estamos, como es en mi caso, trabajando tras un proxy y necesitamos que nuestro código se conecte a servicios que se encuentras más allá del proxy, nos vemos obligados a configurar nuestro código para entenderse con proxys. Esta tarea es bastante simple, ya que según la documentación solo es necesario definir algunas variables del sistema:

  • http.proxyHost: pombre host del servidor proxy
  • http.proxyPort: puerto, siendo su valor por defecto 80
  • http.nonProxyHosts: listado de host que deben ser accedidos directamente, saltándose el proxy.

En este punto, podemos ver que ni siquiera es necesario modificar una sóla línea de código sino sólo proporciar dichas variables en el arrache de nuestra aplicación o servidor si trabajamos con una aplicación Web.

Sin embargo, la configuración no queda ahí sino que se complica un poco para los casos en los que necesitemos autenticarnos contra el proxy. Para estos casos es necesario trabajar con la clase Authenticator que como se puede leer en la documentación, representa un objeto que sabe cómo obtener autentificación para una conexión de red. Una instancia concreta de esta clase (según nuestras necesidades) debe registrase en el sistema llamando al método setDefault(Authenticator) para qué este esté disponible.

Así pues, lo primero que debemos hacer es crear nuestra instancia extendiendo de la original:

package es.whatabout.common.security; import java.net.Authenticator; import java.net.PasswordAuthentication; /** * * @author whatabout.es * */ public class ProxyAuthenticator extends Authenticator { private String username, password; public ProxyAuthenticator(String username, String password) { this.username = username; this.password = password; } protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password.toCharArray()); } }

A continuación debemos, como dijimos antes, registrar nuestro autenticador dentro de nuestro código antes de realizar las conexiones que necesiten autenticarse:

Authenticator.setDefault(new ProxyAuthenticator("user", "pass"));


Como vemos, trabajar tras un proxy no es dependiente de la tecnología, o de framework. Pero veamos un ejemplo de cómo se configuraría con Spring IoC un proxy que requiera autenticación.

Primero, declaramos el bean:

<bean name="simpleAuthenticator" class="es.whatabout.common.security.ProxyAuthenticator" autowire-candidate="true"> <constructor-arg index="0" value="#{systemProperties['user'] == null ? 'user' : systemProperties['user']}" /> <constructor-arg index="1" value="#{systemProperties['pass'] == null ? 'pass' : systemProperties['pass']}" /> </bean>


El nombre de usuario y la contraseña podría proporcionarse directamente en la declaración pero eso no sería muy portable además de verte obligado a escribir tu usuario y contraseña en un código que puede compartirse en un repositorio. Y puesto que ya son varias las variables de sistema que necesitamos, ¿por qué no proporcionar el usuario y la contraseña como variables del sistema? Otra particularidad que se puede apreciar en la declaración del bean es que se proporciona unos valores por defecto para el usuario y contraseña. Eso es necesario ya que si nuestra aplicación, según el entorno, no se encontrara tras un proxy y no proporcionáramos unos valores a través de las variables del sistema, la instanciación del bean daría error.

Segundo, utilizamos nuestra clase (inyectada previamente):


if (proxyAuthenticator != null) Authenticator.setDefault(proxyAuthenticator);


Finalmente, ejecutemos nuestra aplicación en local utilizando Maven y el plugin disponible para Jetty

mvn jetty:run -DproxySet=true -DproxyHost=proxy.indra.es -DproxyPort=8080 -Duser=XXX -Dpass=XXX 

¿Que pasa si la aplicación se desplegara en un entrono si proxy? ¿Hay que hacer algún cambio? No. Simplemente, no serían necesarias las variables del sistema:

mvn jetty:run

Si trabajamos con Maven, como en el ejemplo, también podríamos configurar estas variables de sistema en el plugin de Jetty y definiras en un perfil de forma que no nos veamos obligados a escribir las variables de sistemas cada vez que queramos ejecutar nuestra aplicación tras un proxy.