Tutorial de ejemplo de Spring AOP: Aspecto, asesoramiento, punto de corte, punto de unión, anotaciones, configuración XML

Spring Framework se desarrolla sobre dos conceptos centrales: inyección de dependencia y programación orientada a aspectos (Spring AOP).

AOP de primavera

Ya hemos visto cómo funciona la inyección de dependencias en Spring , hoy analizaremos los conceptos básicos de la programación orientada a aspectos y cómo podemos implementarla utilizando Spring Framework.

Descripción general de AOP de primavera

La mayoría de las aplicaciones empresariales tienen algunas preocupaciones transversales comunes que son aplicables a diferentes tipos de objetos y módulos. Algunas de las preocupaciones transversales comunes son el registro, la gestión de transacciones, la validación de datos, etc. En la programación orientada a objetos, la modularidad de la aplicación se logra mediante clases, mientras que en la programación orientada a aspectos, la modularidad de la aplicación se logra mediante aspectos y se configuran para abarcar diferentes clases. Spring AOP elimina la dependencia directa de las tareas transversales de las clases que no podemos lograr a través del modelo de programación orientada a objetos normal. Por ejemplo, podemos tener una clase separada para el registro, pero nuevamente las clases funcionales tendrán que llamar a estos métodos para lograr el registro en toda la aplicación.

Conceptos básicos de la programación orientada a aspectos

Antes de sumergirnos en la implementación de Spring AOP, debemos comprender los conceptos centrales de AOP.

  1. Aspecto : un aspecto es una clase que implementa cuestiones de aplicación empresarial que afectan a varias clases, como la gestión de transacciones. Los aspectos pueden ser una clase normal configurada a través de la configuración XML de Spring o podemos utilizar la integración de Spring AspectJ para definir una clase como aspecto mediante @Aspectanotaciones.
  2. Punto de unión : un punto de unión es un punto específico en la aplicación, como la ejecución de un método, el manejo de excepciones, el cambio de valores de variables de objetos, etc. En Spring AOP, un punto de unión es siempre la ejecución de un método.
  3. Consejos : los consejos son acciones que se toman para un punto de unión en particular. En términos de programación, son métodos que se ejecutan cuando se alcanza un determinado punto de unión con un punto de corte coincidente en la aplicación. Puede pensar en los consejos como interceptores de Struts2 o filtros de servlets .
  4. Pointcut : Pointcut son expresiones que se corresponden con los puntos de unión para determinar si es necesario ejecutar un consejo o no. Pointcut utiliza diferentes tipos de expresiones que se corresponden con los puntos de unión y el marco Spring utiliza el lenguaje de expresión Pointcut de AspectJ.
  5. Objeto de destino : Son los objetos sobre los que se aplican los consejos. Spring AOP se implementa utilizando proxies en tiempo de ejecución, por lo que este objeto siempre es un objeto proxy. Esto significa que se crea una subclase en tiempo de ejecución donde se anula el método de destino y se incluyen los consejos según su configuración.
  6. Proxy AOP : la implementación de Spring AOP utiliza un proxy dinámico JDK para crear las clases Proxy con clases de destino e invocaciones de asesoramiento, que se denominan clases proxy AOP. También podemos utilizar el proxy CGLIB agregándolo como dependencia en el proyecto Spring AOP.
  7. Tejido : es el proceso de vincular aspectos con otros objetos para crear los objetos proxy recomendados. Esto se puede hacer en tiempo de compilación, tiempo de carga o en tiempo de ejecución. Spring AOP realiza el tejido en tiempo de ejecución.

Tipos de asesoramiento de AOP

En función de la estrategia de ejecución del asesoramiento los mismos son de los siguientes tipos.

  1. Antes del aviso : estos avisos se ejecutan antes de la ejecución de los métodos de punto de unión. Podemos usar @Beforeuna anotación para marcar un tipo de aviso como Antes del aviso.
  2. Después (finalmente) del consejo : un consejo que se ejecuta después de que el método del punto de unión termina de ejecutarse, ya sea de manera normal o lanzando una excepción. Podemos crear un consejo posterior mediante @Afteranotaciones.
  3. Después de devolver el consejo : a veces queremos que los métodos de consejo se ejecuten solo si el método de punto de unión se ejecuta normalmente. Podemos usar @AfterReturninguna anotación para marcar un método como después de devolver el consejo.
  4. Después de lanzar un aviso : este aviso se ejecuta solo cuando el método de punto de unión lanza una excepción. Podemos usarlo para revertir la transacción de manera declarativa. Usamos @AfterThrowinganotaciones para este tipo de aviso.
  5. Consejos sobre el método de punto de unión: este es el consejo más importante y poderoso. Este consejo rodea al método de punto de unión y también podemos elegir si ejecutar el método de punto de unión o no. Podemos escribir código de consejos que se ejecute antes y después de la ejecución del método de punto de unión. Es responsabilidad de los consejos sobre el método invocar el método de punto de unión y devolver valores si el método devuelve algo. Usamos @Aroundanotaciones para crear métodos de consejos sobre el método.

Los puntos mencionados anteriormente pueden sonar confusos, pero cuando veamos la implementación de Spring AOP, las cosas estarán más claras. Comencemos creando un proyecto Spring simple con implementaciones de AOP. Spring brinda soporte para usar anotaciones de AspectJ para crear aspectos y lo usaremos para simplificar. Todas las anotaciones de AOP anteriores se definen en el org.aspectj.lang.annotationpaquete Spring Tool Suite. Spring Tool Suite brinda información útil sobre los aspectos, por lo que le sugiero que lo use. Si no está familiarizado con STS, le recomiendo que eche un vistazo al Tutorial de Spring MVC donde explico cómo usarlo.

Ejemplo de AOP de primavera

Crea un nuevo proyecto Maven de Simple Spring para que todas las bibliotecas de Spring Core estén incluidas en los archivos pom.xml y no necesitemos incluirlas explícitamente. Nuestro proyecto final se verá como la imagen de abajo, analizaremos en detalle los componentes de Spring Core y las implementaciones de Aspect.

Dependencias de Spring AOP AspectJ

El marco Spring proporciona soporte AOP de forma predeterminada, pero como usamos anotaciones de AspectJ para configurar aspectos y consejos, necesitaríamos incluirlas en el archivo pom.xml.

project  xmlns_xsi="https://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"modelVersion4.0.0/modelVersiongroupIdorg.springframework.samples/groupIdartifactIdSpringAOPExample/artifactIdversion0.0.1-SNAPSHOT/versionproperties!-- Generic properties --java.version1.6/java.versionproject.build.sourceEncodingUTF-8/project.build.sourceEncodingproject.reporting.outputEncodingUTF-8/project.reporting.outputEncoding!-- Spring --spring-framework.version4.0.2.RELEASE/spring-framework.version!-- Logging --logback.version1.0.13/logback.versionslf4j.version1.7.5/slf4j.version!-- Test --junit.version4.11/junit.version!-- AspectJ --aspectj.version1.7.4/aspectj.version/propertiesdependencies!-- Spring and Transactions --dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion${spring-framework.version}/version/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-tx/artifactIdversion${spring-framework.version}/version/dependency!-- Logging with SLF4J  LogBack --dependencygroupIdorg.slf4j/groupIdartifactIdslf4j-api/artifactIdversion${slf4j.version}/versionscopecompile/scope/dependencydependencygroupIdch.qos.logback/groupIdartifactIdlogback-classic/artifactIdversion${logback.version}/versionscoperuntime/scope/dependency!-- AspectJ dependencies --dependencygroupIdorg.aspectj/groupIdartifactIdaspectjrt/artifactIdversion${aspectj.version}/versionscoperuntime/scope/dependencydependencygroupIdorg.aspectj/groupIdartifactIdaspectjtools/artifactIdversion${aspectj.version}/version/dependency/dependencies/project

Tenga en cuenta que he agregado aspectjrtdependencias aspectjtools(versión 1.7.4) al proyecto. También he actualizado la versión del marco Spring para que sea la más reciente, es decir, 4.0.2.RELEASE.

Clase modelo

Vamos a crear un bean Java simple que utilizaremos para nuestro ejemplo con algunos métodos adicionales. Código Employee.java:

package com.journaldev.spring.model;import com.journaldev.spring.aspect.Loggable;public class Employee {private String name;public String getName() {return name;}@Loggablepublic void setName(String nm) {this.name=nm;}public void throwException(){throw new RuntimeException("Dummy Exception");}}

¿Has notado que el método setName() está anotado con Loggableuna anotación? Es una anotación de Java personalizada que definimos en el proyecto. Veremos su uso más adelante.

Clase de servicio

Vamos a crear una clase de servicio para trabajar con el bean Employee. Código EmployeeService.java:

package com.journaldev.spring.service;import com.journaldev.spring.model.Employee;public class EmployeeService {private Employee employee;public Employee getEmployee(){return this.employee;}public void setEmployee(Employee e){this.employee=e;}}

Podría haber usado anotaciones de Spring para configurarlo como un componente de Spring, pero en este proyecto usaremos una configuración basada en XML. La clase EmployeeService es muy estándar y solo nos proporciona un punto de acceso para los beans Employee.

Configuración de Spring Bean con AOP

Si está utilizando STS, tiene la opción de crear un “Archivo de configuración de Spring Bean” y elegir el espacio de nombres del esquema AOP, pero si está utilizando otro IDE, puede simplemente agregarlo en el archivo de configuración de Spring Bean. El archivo de configuración de mi proyecto Bean se ve como se muestra a continuación: spring.xml:

?xml version="1.0" encoding="UTF-8"?beans xmlns_xsi="https://www.w3.org/2001/XMLSchema-instance"xmlns:aop="https://www.springframework.org/schema/aop"xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttps://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-4.0.xsd"!-- Enable AspectJ style of Spring AOP --aop:aspectj-autoproxy /!-- Configure Employee Bean and initialize it --bean name="employee"property name="name" value="Dummy Name"/property/bean!-- Configure EmployeeService bean --bean name="employeeService"property name="employee" ref="employee"/property/bean!-- Configure Aspect Beans, without this Aspects advices wont execute --bean name="employeeAspect" /bean name="employeeAspectPointcut" /bean name="employeeAspectJoinPoint" /bean name="employeeAfterAspect" /bean name="employeeAroundAspect" /bean name="employeeAnnotationAspect" //beans

Para utilizar Spring AOP en beans Spring, debemos hacer lo siguiente:

  1. Declare el espacio de nombres AOP como xmlns_aop=” https://www.springframework.org/schema/aop “
  2. Agregue el elemento aop:aspectj-autoproxy para habilitar la compatibilidad de Spring AspectJ con proxy automático en tiempo de ejecución
  3. Configurar clases de Aspect como otros beans Spring

Puedes ver que tengo muchos aspectos definidos en el archivo de configuración del bean de primavera, es hora de analizarlos uno por uno.

Ejemplo de AOP antes del aspecto de Spring

Código de EmployeeAspect.java:

package com.journaldev.spring.aspect;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;@Aspectpublic class EmployeeAspect {@Before("execution(public String getName())")public void getNameAdvice(){System.out.println("Executing Advice on getName()");}@Before("execution(* com.journaldev.spring.service.*.get*())")public void getAllAdvice(){System.out.println("Service method getter called");}}

Los puntos importantes de la clase de aspecto anterior son:

  • Es necesario que las clases de aspecto tengan @Aspectanotación.
  • La anotación @Before se utiliza para crear un consejo Before
  • El parámetro de cadena que se pasa en la @Beforeanotación es la expresión Pointcut
  • El consejo de getNameAdvice() se ejecutará para cualquier método de Spring Bean con firma public String getName(). Este es un punto muy importante para recordar, si creamos el bean Employee usando el operador new, los consejos no se aplicarán. Solo cuando usemos ApplicationContext para obtener el bean, se aplicarán los consejos.
  • Podemos usar un asterisco (*) como comodín en expresiones Pointcut, getAllAdvice() se aplicará para todas las clases en com.journaldev.spring.serviceel paquete cuyo nombre comience con gety no tome ningún argumento.

Veremos los consejos en acción en una clase de prueba después de haber analizado todos los diferentes tipos de consejos.

Métodos de punto de corte y reutilización de Spring AOP

A veces tenemos que usar la misma expresión Pointcut en varios lugares. Podemos crear un método vacío con @Pointcutanotación y luego usarlo como una expresión en los avisos. Código EmployeeAspectPointcut.java:

package com.journaldev.spring.aspect;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;@Aspectpublic class EmployeeAspectPointcut {@Before("getNamePointcut()")public void loggingAdvice(){System.out.println("Executing loggingAdvice on getName()");}@Before("getNamePointcut()")public void secondAdvice(){System.out.println("Executing secondAdvice on getName()");}@Pointcut("execution(public String getName())")public void getNamePointcut(){}@Before("allMethodsPointcut()")public void allServiceMethodsAdvice(){System.out.println("Before executing service method");}//Pointcut to execute on all the methods of classes in a package@Pointcut("within(com.journaldev.spring.service.*)")public void allMethodsPointcut(){}}

El ejemplo anterior es muy claro: en lugar de una expresión, utilizamos el nombre del método en el argumento de anotación del consejo.

Argumentos de JoinPoint y Advice de Spring AOP

Podemos usar JoinPoint como parámetro en los métodos de asesoramiento y, al usarlo, obtener la firma del método o el objeto de destino. Podemos usar args()expresiones en el punto de corte para aplicarlas a cualquier método que coincida con el patrón de argumento. Si usamos esto, entonces debemos usar el mismo nombre en el método de asesoramiento desde donde se determina el tipo de argumento. También podemos usar objetos genéricos en los argumentos de asesoramiento. Código EmployeeAspectJoinPoint.java:

package com.journaldev.spring.aspect;import java.util.Arrays;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;@Aspectpublic class EmployeeAspectJoinPoint {@Before("execution(public void com.journaldev.spring.model..set*(*))")public void loggingAdvice(JoinPoint joinPoint){System.out.println("Before running loggingAdvice on method="+joinPoint.toString());System.out.println("Agruments Passed=" + Arrays.toString(joinPoint.getArgs()));}//Advice arguments, will be applied to bean methods with single String argument@Before("args(name)")public void logStringArguments(String name){System.out.println("String argument passed="+name);}}

Ejemplo de asesoramiento posterior a la AOP de primavera

Veamos una clase de aspecto simple con un ejemplo de los consejos After, After Throwing y After Returning. Código EmployeeAfterAspect.java:

package com.journaldev.spring.aspect;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Aspect;@Aspectpublic class EmployeeAfterAspect {@After("args(name)")public void logStringArguments(String name){System.out.println("Running After Advice. String argument passed="+name);}@AfterThrowing("within(com.journaldev.spring.model.Employee)")public void logExceptions(JoinPoint joinPoint){System.out.println("Exception thrown in Employee Method="+joinPoint.toString());}@AfterReturning(pointcut="execution(* getName())", returning="returnString")public void getNameReturningAdvice(String returnString){System.out.println("getNameReturningAdvice executed. Returned String="+returnString);}}

Podemos utilizar withinuna expresión pointcut para aplicar el consejo a todos los métodos de la clase. Podemos utilizar el consejo @AfterReturning para obtener el objeto devuelto por el método recomendado. Tenemos el método throwException() en el bean Employee para mostrar el uso del consejo After Throwing.

Ejemplo de AOP de resorte alrededor del aspecto

Como se explicó anteriormente, podemos usar el aspecto Around para cortar la ejecución del método antes y después. Podemos usarlo para controlar si el método recomendado se ejecutará o no. También podemos inspeccionar el valor devuelto y cambiarlo. Este es el consejo más poderoso y debe aplicarse correctamente. Código EmployeeAroundAspect.java:

package com.journaldev.spring.aspect;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;@Aspectpublic class EmployeeAroundAspect {@Around("execution(* com.journaldev.spring.model.Employee.getName())")public Object employeeAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){System.out.println("Before invoking getName() method");Object value = null;try {value = proceedingJoinPoint.proceed();} catch (Throwable e) {e.printStackTrace();}System.out.println("After invoking getName() method. Return value="+value);return value;}}

En los consejos de Around siempre se requiere que ProceedingJoinPoint sea un argumento y debemos usar su método proceed() para invocar el método de objeto de destino. Si el método de Advised está devolviendo algo, es responsabilidad de Advised devolverlo al programa que lo llamó. Para los métodos nulos, el método de Advised puede devolver un valor nulo. Dado que los consejos de Around cortan el método de Advised, podemos controlar la entrada y la salida del método, así como su comportamiento de ejecución.

Consejos de primavera con puntos de anotación personalizados

Si observa todas las expresiones pointcut de consejos anteriores, existen posibilidades de que se apliquen a otros beans en los que no está previsto. Por ejemplo, alguien puede definir un nuevo bean de Spring con el método getName() y el consejo comenzará a aplicarse a él, aunque no esté previsto. Es por eso que debemos mantener el alcance de la expresión pointcut lo más limitado posible. Un enfoque alternativo es crear una anotación personalizada y anotar los métodos en los que queremos que se aplique el consejo. Este es el propósito de tener el método Employee setName() anotado con la anotación @Loggable . La anotación @Transactional de Spring Framework es un gran ejemplo de este enfoque para Spring Transaction Management . Código de Loggable.java:

package com.journaldev.spring.aspect;public @interface Loggable {}

Código de EmployeeAnnotationAspect.java:

package com.journaldev.spring.aspect;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;@Aspectpublic class EmployeeAnnotationAspect {@Before("@annotation(com.journaldev.spring.aspect.Loggable)")public void myAdvice(){System.out.println("Executing myAdvice!!");}}

El método myAdvice() solo avisará al método setName(). Este es un enfoque muy seguro y, siempre que queramos aplicar el consejo a cualquier método, todo lo que necesitamos es anotarlo con la anotación Loggable.

Configuración XML de Spring AOP

Siempre prefiero la anotación, pero también tenemos la opción de configurar aspectos en el archivo de configuración de Spring. Por ejemplo, supongamos que tenemos una clase como la que se muestra a continuación. Código EmployeeXMLConfigAspect.java:

package com.journaldev.spring.aspect;import org.aspectj.lang.ProceedingJoinPoint;public class EmployeeXMLConfigAspect {public Object employeeAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){System.out.println("EmployeeXMLConfigAspect:: Before invoking getName() method");Object value = null;try {value = proceedingJoinPoint.proceed();} catch (Throwable e) {e.printStackTrace();}System.out.println("EmployeeXMLConfigAspect:: After invoking getName() method. Return value="+value);return value;}}

Podemos configurarlo incluyendo la siguiente configuración en el archivo de configuración de Spring Bean.

bean name="employeeXMLConfigAspect" /!-- Spring AOP XML Configuration --aop:configaop:aspect ref="employeeXMLConfigAspect" order="1"aop:pointcut expression="execution(* com.journaldev.spring.model.Employee.getName())"/aop:around method="employeeAroundAdvice" pointcut-ref="getNamePointcut" arg-names="proceedingJoinPoint"//aop:aspect/aop:config

El propósito de los elementos de configuración xml de AOP queda claro en su nombre, por lo que no entraré en muchos detalles al respecto.

Ejemplo de AOP de primavera

Tengamos un programa Spring simple y veamos cómo todos estos aspectos se aplican a los métodos del bean. Código SpringMain.java:

package com.journaldev.spring.main;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.journaldev.spring.service.EmployeeService;public class SpringMain {public static void main(String[] args) {ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");EmployeeService employeeService = ctx.getBean("employeeService", EmployeeService.class);System.out.println(employeeService.getEmployee().getName());employeeService.getEmployee().setName("Pankaj");employeeService.getEmployee().throwException();ctx.close();}}

Ahora, cuando ejecutamos el programa anterior, obtenemos el siguiente resultado.

Mar 20, 2014 8:50:09 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefreshINFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4b9af9a9: startup date [Thu Mar 20 20:50:09 PDT 2014]; root of context hierarchyMar 20, 2014 8:50:09 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitionsINFO: Loading XML bean definitions from class path resource [spring.xml]Service method getter calledBefore executing service methodEmployeeXMLConfigAspect:: Before invoking getName() methodExecuting Advice on getName()Executing loggingAdvice on getName()Executing secondAdvice on getName()Before invoking getName() methodAfter invoking getName() method. Return value=Dummy NamegetNameReturningAdvice executed. Returned String=Dummy NameEmployeeXMLConfigAspect:: After invoking getName() method. Return value=Dummy NameDummy NameService method getter calledBefore executing service methodString argument passed=PankajBefore running loggingAdvice on method=execution(void com.journaldev.spring.model.Employee.setName(String))Agruments Passed=[Pankaj]Executing myAdvice!!Running After Advice. String argument passed=PankajService method getter calledBefore executing service methodException thrown in Employee Method=execution(void com.journaldev.spring.model.Employee.throwException())Exception in thread "main" java.lang.RuntimeException: Dummy Exceptionat com.journaldev.spring.model.Employee.throwException(Employee.java:19)at com.journaldev.spring.model.Employee$$FastClassBySpringCGLIB$$da2dc051.invoke(generated)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:711)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:58)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)at com.journaldev.spring.model.Employee$$EnhancerBySpringCGLIB$$3f881964.throwException(generated)at com.journaldev.spring.main.SpringMain.main(SpringMain.java:17)

Puedes ver que los consejos se ejecutan uno por uno según sus configuraciones de punto de corte. Debes configurarlos uno por uno para evitar confusiones. Eso es todo por el Tutorial de ejemplo de AOP de Spring . Espero que hayas aprendido los conceptos básicos de AOP con Spring y que puedas aprender más con los ejemplos. Descarga el proyecto de muestra desde el siguiente enlace y juega con él.

Descargar Proyecto AOP de Primavera

SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja un comentario

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

Scroll al inicio