@AspectJ asume un estilo de declarar aspectos como clases regulares de Java anotadas con anotaciones. El estilo @AspectJ fue introducido por el proyecto AspectJ como parte de la versión AspectJ 5. Spring interpreta las mismas anotaciones como AspectJ 5, utilizando la biblioteca proporcionada por AspectJ para analizar y hacer coincidir sectores. Sin embargo, el tiempo de ejecución de AOP sigue siendo Spring AOP puro y no depende del compilador ni de la herramienta de enlace de AspectJ.

Compilador y herramienta de enlace de AspectJ. que le permiten utilizar el lenguaje AspectJ completamente.

Habilitación del soporte @AspectJ

Para usar aspectos @AspectJ en una configuración de Spring, debe habilitar el soporte Spring para configurar Spring AOP basado en aspectos @AspectJ y beans de proxy automático dependiendo de si sus consejos sobre estos aspectos o no. Con proxy automático, queremos decir que si Spring detecta que un bean tiene consejos sobre uno o más aspectos, automáticamente crea un proxy para que ese bean intercepte las llamadas a métodos y garantice que los consejos se ejecuten según sea necesario.

El soporte @AspectJ se puede activar usando la configuración de estilo XML o Java. En cualquier caso, debe asegurarse de que la biblioteca aspectjweaver.jar de AspectJ esté en el classpath de su aplicación (versión 1.8 o posterior). Esta biblioteca está disponible en el directorio lib de la distribución de AspectJ o desde el repositorio central de Maven.

Habilitación del soporte @AspectJ usando la configuración de Java

Para habilitar Compatibilidad con @AspectJ usando @Configurationdesde Java, agregue la anotación @EnableAspectJAutoProxy como se muestra en el siguiente ejemplo:

Java

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
Kotlin

@Configuration
@EnableAspectJAutoProxy
class AppConfig

Habilitar la compatibilidad con @AspectJ usando la configuración XML

Para habilitar la compatibilidad con @AspectJ usando la configuración basada en XML, use el aop:aspectj element-autoproxy, como se muestra en el siguiente ejemplo:

<aop:aspectj-autoproxy/>

Declaración de aspecto

Cuando se habilita la compatibilidad con @AspectJ, Spring define automáticamente cualquier bean definido en el contexto de su aplicación con una clase que sea un aspecto @AspectJ (tiene la anotación @Aspect) y se utiliza para configurar Spring AOP. Los siguientes dos ejemplos demuestran la definición simple necesaria para un aspecto no tan útil.

El primero de los dos ejemplos muestra una definición de bean normal en el contexto de la aplicación, que apunta a una clase de bean que tiene la anotación @Aspect:


<bean id ="myAspect" class="org.xyz.NotVeryUsefulAspect">
        <!-- configure las propiedades de aspecto aquí -->
</bean>

El segundo de dos ejemplos muestra la definición de la clase NotVeryUsefulAspect, que está anotada con el anotación de organización anotación de organización ;

Java

package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
Kotlin

package org.xyz
import org.aspectj.lang.annotation .Aspect;
@Aspect
class NotVeryUsefulAspect

Los aspectos (clases anotadas con @Aspect) pueden tener métodos y campos como cualquier otra clase. También pueden contener declaraciones de instantáneas, consejos e introducciones (tipo cruzado).

Automático detección de aspectos mediante escaneo de beans
Puede registrar clases de aspectos como beans normales en una configuración Spring XML utilizando métodos anotados con @Bean, en clases anotadas con @Configuration anotación, o haga que Spring los descubra automáticamente a través del escaneo de classpath, como cualquier otro Spring Bean administrado. Sin embargo, tenga en cuenta que la anotación @Aspect no es suficiente para la detección automática en el classpath. Para hacer esto, necesita agregar una anotación @Component separada (o, alternativamente, una anotación de estereotipo especial que coincida con las reglas del escáner de componentes Spring).
¿Aconsejando aspectos utilizando otros aspectos?
En Spring AOP, los aspectos en sí no son pueden ser objetivos para el suministro de asesoramiento desde otros aspectos. La anotación @Aspect en una clase la marca como un aspecto y por lo tanto la excluye del autoproxy.

Declaración de un segmento

Los segmentos definen puntos de conexión y, por lo tanto, le permiten controlar el tiempo de ejecución del consejo. Spring AOP solo admite puntos de unión de ejecución de métodos para Spring beans, por lo que puede pensar en un segmento como lo mismo que la ejecución de métodos para Spring beans. Una declaración de segmento consta de dos partes: una firma, que incluye el nombre y cualquier parámetro, y una expresión de segmento, que especifica qué ejecuciones del método nos interesan. En el estilo AOP con la anotación @AspectJ, la firma del sector se especifica mediante la definición del método normal y la expresión del sector se especifica mediante la anotación @Pointcut (el método que sirve como firma del sector debe tener un retorno tipo de void) .

Un ejemplo puede ayudar a aclarar la diferencia entre una firma de sector y una expresión de sector. El siguiente ejemplo define un segmento llamado anyOldTransfer que corresponde a la ejecución de cualquier método llamado transfer:

Java

@Pointcut("execution(* transfer(..))") // cortar expresión
private void anyOldTransfer() {} // cortar firma
Kotlin

@Pointcut("execution(* transfer(..))") // cortar expresión
private fun anyOldTransfer() {} // cortar firma

La expresión de sector que produce el valor de la anotación @Pointcut es una expresión de sector normal de AspectJ. Para obtener una visión detallada del lenguaje de corte de AspectJ, consulte la Guía de programación de AspectJ. (y para extensiones: Guía del desarrollador de AspectJ 5 ) o uno de los libros sobre AspectJ (por ejemplo, Eclipse AspectJ de Collier et al. o AspectJ in Action de Ramniwas Laddad).

Sección admitida punteros

Spring AOP admite los siguientes designadores de sectores de AspectJ (diseñadores de corte de puntos/PCD) para su uso en expresiones de sectores:

  • execution: Para coordinar puntos de conexión al ejecutar un método. Este es el indicador de corte básico que se debe usar cuando se trabaja con Spring AOP.

  • within: limita la coincidencia para unir puntos dentro de ciertos tipos (ejecutar un método declarado dentro del tipo coincidente cuando se usa Spring AOP).

  • this: limita la coincidencia con los puntos de conexión (ejecución del método cuando se usa Spring AOP) donde el bean referencia (proxy) Spring AOP) es una instancia del tipo dado.

  • target: restringe la negociación a puntos de conexión (ejecución del método cuando se usa Spring AOP) donde el objeto de destino (el objeto que es la aplicación proxy) es una instancia del tipo dado.

  • args: restringe la negociación para unir puntos (método ejecución cuando se usa Spring AOP) donde los argumentos son instancias de los tipos dados.

  • @target: limita la negociación a puntos de conexión (ejecución del método cuando se usa Spring AOP) donde la clase del objeto en ejecución tiene una anotación de un tipo determinado.

  • @args: Limita la negociación a puntos de conexión (ejecución del método cuando se usa Spring AOP) donde el tipo de tiempo de ejecución de los argumentos reales pasados tiene anotaciones de los tipos especificados.

  • @within: limita la negociación para unir puntos dentro de los tipos que tienen esta anotación (ejecutando métodos declarados en tipos con esta anotación cuando se usa Spring AOP).

  • @annotation: restringe la negociación a puntos de conexión donde el asunto del punto de conexión (el método ejecutado en Spring AOP) tiene la anotación dada.

Otros tipos de puntero

El lenguaje de corte con todas las funciones de AspectJ admite punteros de corte adicionales que no son compatibles con Spring: call, get, set, preinitialization, staticinitialization, initialization, handler, adviceexecution , withincode , cflow , cflowbelow , if , @this y @withincode. El uso de estos punteros de sector en expresiones de sector interpretadas por Spring AOP da como resultado una IllegalArgumentException.

El conjunto de punteros de sector admitidos por Spring AOP puede ampliarse en versiones futuras para admitir más punteros de sector de AspectJ.

Debido a que Spring AOP limita la negociación solo a puntos de unión de ejecución de métodos, la descripción anterior de los punteros de corte proporcionó una definición más limitada que la que puede encontrar en la Guía de programación de AspectJ. Además, AspectJ tiene una semántica basada en tipos y en el punto de unión de ejecución tanto this como target se refieren al mismo objeto: el objeto que ejecuta el método. Spring AOP es un sistema basado en proxy que distingue entre el objeto proxy en sí (que está vinculado a this) y el objetivo detrás del proxy (que está vinculado a target).

Debido a la naturaleza orientada al proxy del marco AOP, las llamadas de Spring dentro del objeto de destino no son interceptadas por definición. Con un proxy JDK, solo puede interceptar llamadas a métodos en la interfaz pública del proxy. CGLIB intercepta llamadas a métodos de proxy públicos y protegidos (e incluso a métodos de alcance de paquete, si es necesario). Sin embargo, las interacciones generales de proxy siempre deben enmarcarse utilizando firmas públicas.

Tenga en cuenta que las definiciones de sectores generalmente coinciden con cualquier método interceptado. Si el segmento está destinado estrictamente para uso público, incluso en un escenario de proxy CGLIB con posibles interacciones no públicas a través del proxy, debe definirse en consecuencia.

Si también necesita interceptar llamadas a métodos o incluso constructores dentro de la clase de destino, busque la capacidad de utilizar el enlace nativo de AspectJ impulsado por Spring en lugar del marco AOP basado en proxy de Spring. El enlace nativo es una forma diferente de usar AOP con diferentes características, así que asegúrese de familiarizarse con el enlace antes de tomar una decisión.

Spring AOP también admite un PCD adicional llamado bin . Este PCD le permite restringir la negociación del punto de conexión a Spring beans con nombre específicos o a un conjunto de Spring beans con nombre (cuando se utilizan comodines). El PCD Bean tiene la siguiente forma:

Java
bean(idOrNameOfBean)
Kotlin
bean(idOrNameOfBean)

El idOrNameOfBean marcador puede ser el nombre de cualquier Spring Bean. Existe una capacidad limitada para usar comodines usando el carácter *, por lo que si establece algunas convenciones de nomenclatura para sus Spring beans, puede escribir una expresión PCD bean para recuperarlos. Al igual que con otros punteros de corte, el PCD para bean se puede utilizar con los operadores && (y), || (o) y ! (negación).

PCD para Bean solo se admite en Spring AOP, pero no con enlace nativo en AspectJ. Esta es una extensión específica de Spring para los PCD estándar que define AspectJ y, por lo tanto, no está disponible para aspectos declarados en un modelo anotado con @Aspect.

PCD para Bean funciona a nivel de instancia (basado en el concepto de nombres de Spring Bean) y no solo a nivel de tipo (que es a lo que se limita AOP basado en enlaces). Los punteros de corte basados en instancias son una característica del marco AOP basado en proxy de Spring, dada su estrecha integración con Spring Bean Factory, que le permite identificar beans específicos por nombre de una manera natural y accesible.

Combinación de expresiones de sector

¡Puedes combinar expresiones de sector usando &&, || y ! También puedes hacer referencia a expresiones de sector por su nombre . El siguiente ejemplo muestra tres expresiones de corte:

Java

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} 
@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} 
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} 
  1. anyPublicOperation se negocia si el punto de conexión de ejecución del método es la ejecución de cualquier método público.
  2. inTrading se negocia si la ejecución del método ocurre en el módulo comercial.
  3. tradingOperation es consistente si la ejecución del método representa cualquier método público en el módulo comercial.
Kotlin

@Pointcut("execution(public * * (..))")
private fun anyPublicOperation() {} 
@Pointcut("within(com.xyz.myapp.trading.. *)")
private fun inTrading() {} 
@Pointcut("anyPublicOperation() && inTrading()")
private fun tradingOperation() {} 
  1. anyPublicOperation es consistente si el punto de unión de ejecución del método es la ejecución de cualquier método público.
  2. inTrading se acuerda si la ejecución del método ocurre en el módulo comercial.
  3. tradingOperation se acuerda si la ejecución del método representa cualquier método público en el módulo comercial.

Una mejor manera es construir expresiones de sectores más complejas a partir de componentes con nombre más pequeños, como se mostró anteriormente. Al acceder a los sectores por nombre, se aplican las reglas de visibilidad normales de Java (puede ver sectores privados del mismo tipo, protegidos). sectores en una jerarquía, sectores públicos en cualquier ubicación, etc.) La visibilidad no afecta la negociación de sectores.

Compartir definiciones de sectores comunes

Cuando se trabaja con aplicaciones empresariales, los desarrolladores a menudo necesitan hacer referencia módulos de aplicación y conjuntos específicos de operaciones desde varios aspectos. Para ello, se recomienda definir un aspecto CommonPointcuts que capture expresiones de corte comunes. Este aspecto suele parecerse al siguiente ejemplo:

Java

package com.xyz.myapp;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class CommonPointcuts {
    /**
    * El punto de conexión está en la capa de interacción web si un método
    * está definido en un tipo en el paquete com.xyz.myapp.web o cualquier subpaquete
    * debajo de él.
    */
    @Pointcut("within(com.xyz.myapp.web..*)")
    public void inWebLayer() {}
    /**
    * El punto de conexión está en el nivel de servicio si se define un método
    * en un tipo en el paquete com.xyz .myapp.service o cualquier subpaquete
    * debajo de ese.
    */
    @Pointcut("within(com.xyz.myapp.service..*)")
    public void inServiceLayer() {}
    /**
    * El punto de conexión está en la capa de acceso a datos si se define un método
    * en un tipo del paquete com .xyz.myapp.dao o cualquier subpaquete
    * debajo de él
    */
    @Pointcut("within(com.xyz.myapp.dao..*)")
    public void inDataAccessLayer() {}
    /**
    * Un servicio empresarial es la ejecución de cualquier método definido en el servicio.
    * interfaz. Esta definición supone que las interfaces están ubicadas en
    * el paquete "servicio" y que los tipos de implementación están en subpaquetes.
    *
    * Si agrupa las interfaces de servicio por área funcional (por ejemplo,,
    * en los paquetes com.xyz.myapp.abc.service y com.xyz.myapp.def.service), entonces
    * la expresión de segmento "execution(* com.xyz .miaplicaciónservicio.*.*(...))".
    * se puede utilizar en su lugar.
    *
    * Alternativamente, puede escribir la expresión utilizando un PCD
    * "orientado a beans", por ejemplo, "bean(*Service)". (Esto supone que usted
    * nombró sus beans de servicio Spring de manera consistente).
    */
    @Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
    public void businessService() {}
    /**
    * Una operación de acceso a datos es la ejecución de cualquier método definido para
    * interfaz dao. Esta definición supone que las interfaces están ubicadas en
    * el paquete "dao" y que los tipos de implementación están en subpaquetes.
    */
    @Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
    public void dataAccessOperation() {}
}
Kotlin

package com.xyz.myapp
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut
@Aspect
class CommonPointcuts {
    /**
    * El punto de conexión está en la capa de interfaz web si se define un método
    * en un tipo en el paquete com.xyz.myapp.web o cualquier subpaquete
    * debajo de ese.
    */
    @Pointcut("within(com.xyz.myapp.web..*)")
    fun inWebLayer() {
    }
    /**
    * El punto de conexión está en la capa de servicios si se define un método
    * en un tipo en el Paquete com.xyz.myapp.service o cualquier subpaquete
    * debajo de ese.
    */
    @Pointcut("within(com.xyz.myapp.service..*)")
    fun inServiceLayer() {
    }
    /**
    * El punto de conexión está en la capa de acceso a datos si un método
    * está definido en un tipo de paquete com.xyz.myapp.dao o cualquier subpaquete
    * debajo de él.
    */
    @Pointcut("within(com.xyz.myapp.dao..*)")
    fun inDataAccessLayer() {
    }
    /**
    * Un servicio empresarial es la ejecución de cualquier método definido en el servicio.
    * interfaz. Esta definición supone que las interfaces están ubicadas en
    * el paquete "servicio" y que los tipos de implementación están en subpaquetes.
    *
    * Si agrupa las interfaces de servicio por área funcional (por ejemplo,
    * en los paquetes com.xyz.myapp.abc.service y com.xyz.myapp.def.service), entonces
    * la expresión de segmento "execution(* com.xyz .miaplicaciónservicio.*.*(...))".
    * se puede utilizar en su lugar.
    *
    * Alternativamente, puede escribir la expresión utilizando un PCD
    * "orientado a beans", por ejemplo, "bean(*Service)". (Esto supone que usted
    * nombró sus beans de servicio Spring de manera consistente).
    */
    @Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
    fun businessService() {
    }
    /**
    * Una operación de acceso a datos es la ejecución de cualquier método definido para
    * interfaz dao. Esta definición supone que las interfaces están ubicadas en
    * el paquete "dao" y que los tipos de implementación están en subpaquetes.
    */
    @Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
    fun dataAccessOperation() {
    }
}

Puede hacer referencia a sectores definidos en dicho aspecto siempre que necesite utilizar una expresión de sector. Por ejemplo, para hacer que la capa de servicio sea transaccional, podría escribir lo siguiente:


<aop:config>
    <aop:advisor
            pointcut="com.xyz.myapp.CommonPointcuts.businessService()"
            advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

Ejemplos

Los usuarios de Spring AOP utilizan con mayor frecuencia la segmentación de datos execution. El formato de una expresión con el puntero de ejecución es el siguiente:

    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
                throws-pattern?)
        

Todas las partes excepto el patrón de tipo de retorno (ret-type-pattern en el fragmento anterior), el patrón de nombre y el patrón de parámetro son opcionales. El patrón de tipo de retorno especifica cuál debe ser el tipo de retorno del método para que coincida el punto de unión. El patrón de tipo de devolución más utilizado es *. Coincide con cualquier tipo de devolución. El nombre de tipo completo coincide solo si el método devuelve ese tipo. El patrón de nombre coincide con el nombre del método. El comodín * se puede utilizar en un patrón de nombre, ya sea en su totalidad o en parte. Si especifica una plantilla de tipo de declaración, incluya un . al final para agregarlo al componente de plantilla de nombre. El patrón de parámetros es un poco más complejo: () coincide con un método que no toma parámetros, mientras que (..) coincide con cualquier número (cero o más) de parámetros. El patrón (*) coincide con un método que toma un parámetro de cualquier tipo. (*,String) corresponde a un método que toma dos parámetros. El primero puede ser de cualquier tipo y el segundo debe ser una String.

Los siguientes ejemplos muestran algunas expresiones de segmento comunes:

  • Ejecutar cualquier método público:

     ejecución(public * *())
  • Ejecutar cualquier método cuyo nombre comience con set:

     ejecución(* set*(..))
  • Ejecute cualquier método definido por la interfaz AccountService:

    ejecución(* com.xyz.service.AccountService.*(..))
  • Ejecute cualquier método definido en el paquete service:

     ejecución( * com.xyz.service.*.*(..))
  • Ejecutar cualquier método definido en un paquete de servicio o uno de sus subpaquetes:

     ejecución(* com.xyz.service ..*.* (..))
  • Cualquier punto de conexión (ejecución del método solo en Spring AOP) dentro de un paquete de servicio:

     dentro de (com.xyz.service.*)
  • Cualquier punto de unión (ejecución de método solo en Spring AOP) dentro de un paquete de servicio o uno de sus subpaquetes:

     dentro de(com.xyz.service..*)
  • Cualquier punto de conexión (ejecución de método solo en Spring AOP) donde el proxy implementa la interfaz AccountService:

     this(com.xyz.service.AccountService)
    this se usa con mayor frecuencia en forma enlazable.
  • Cualquier punto de conexión (ejecución de método solo en Spring AOP) donde el destino implementa la interfaz AccountService:

     target(com.xyz.service.AccountService)
    target se usa con mayor frecuencia en forma enlazable.
  • Cualquier punto de conexión (ejecución de método solo en Spring AOP) que toma un parámetro y donde se pasa el argumento en tiempo de ejecución es Serializable:

     args(java.io.Serializable)
    args se utiliza con mayor frecuencia en forma encuadernada.

    Tenga en cuenta que el que se proporciona en este El segmento de ejemplo es diferente de execution(* * *(java.io.Serializable)). La opción "args" coincide si el argumento pasado en tiempo de ejecución es Serializable, y la opción "execution" coincide si la firma del método declara un único parámetro de tipo Serializable.

  • Cualquier punto de conexión (ejecución de método solo en Spring AOP) donde el objeto de destino tenga la anotación @Transactional:

     @target(org.springframework.transaction.annotation.Transactional)
    También puede utilizar @target en formato encuadernado.
  • Cualquier punto de unión (ejecución del método solo en Spring AOP) donde el tipo de destino declarado tenga la anotación @Transactional:

     @within(org.springframework.transaction.annotation.Transactional)
    También puede usar @within en un formato enlazado.
  • Cualquier punto de unión (ejecución del método solo en Spring AOP) donde el método de ejecución tiene la anotación @Transactional:

     @annotation(org. springframework.transaction.annotation.Transactional)
    También puedes usar @annotation en un formato enlazable.
  • Cualquier punto de conexión (ejecución de método solo en Spring AOP) que toma un parámetro y donde se pasa el tipo de argumento tiene la anotación @Classified:

     @ args(com.xyz.security.Classified)
    También puedes usar @args en forma enlazada.
  • Cualquier punto de unión (ejecución de método solo en Spring AOP) para Spring beans llamado tradeService:

     bean(tradeService)
  • Cualquier punto de conexión (ejecución de método solo en Spring AOP) para Spring beans cuyos nombres coincidan con la expresión comodín *Service:

     bean(*Servicio)

Escribir buenos sectores

Durante la compilación, AspectJ procesa los sectores para optimizar la eficiencia de la conciliación. Examinar el código y determinar si cada punto de unión corresponde (estática o dinámicamente) a un segmento determinado es un proceso que requiere muchos recursos. (La coincidencia dinámica significa que la coincidencia no se puede determinar completamente mediante análisis estático y que se realiza una prueba en el código para determinar la coincidencia real cuando se ejecuta el código). Cuando AspectJ encuentra por primera vez una declaración de segmento, la reescribe en una forma óptima para el procedimiento de comparación. ¿Qué quiere decir esto? Básicamente, los sectores se reescriben en DNF (forma normal disyuntiva) y los componentes del sector se ordenan de tal manera que aquellos componentes que se pueden calcular con menos recursos se prueban primero. Esto significa que no tiene que preocuparse por describir cómo funcionan los distintos punteros de corte y pueden usarse en cualquier orden en la declaración de corte.

Sin embargo, AspectJ sólo puede hacer lo que se le dice que haga. Para obtener un rendimiento óptimo de las coincidencias, debe pensar en el objetivo final y limitar al máximo el espacio de búsqueda de coincidencias en la definición. Los punteros existentes se dividen naturalmente en uno de tres grupos: genéricos, de definición y contextuales:

  • Los punteros genéricos seleccionan un tipo específico de punto de conexión: execution , get, set, call o handler.

  • Puntero definitivo seleccione el grupo deseado de puntos de conexión (posiblemente de muchos géneros): within y withincode

  • Los punteros contextuales coinciden ( y opcionalmente enlazado) según el contexto: this, target y @annotation

Una sección bien escrita debe incluir al menos los dos primeros tipos de indicadores (genéricos y atributivos). Puede incluir punteros contextuales para hacer coincidencias según el contexto del punto de unión o vincular ese contexto para usarlo en una sugerencia. Especificar solo el puntero genérico o solo el contextual funcionará, pero puede afectar la complejidad de realizar la vinculación (tiempo y uso de memoria) debido al procesamiento y análisis adicionales. Los punteros de definición son extremadamente rápidos de hacer coincidir y su uso significa que AspectJ puede eliminar muy rápidamente grupos de puntos de unión que no deben procesarse más. Un buen segmento siempre debe incluir uno de estos si es posible.

Declaración de consejos

Un consejo está asociado con una expresión de segmento y se ejecuta antes, después o en lugar de ejecutar un método que coincide con la rebanada. Una expresión de segmento puede ser una simple referencia a un segmento con nombre o una expresión de segmento directa.

Consejo previo

Puedes declarar un consejo "before" en un aspecto usando el @Before anotación:

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
    @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
}
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
@Aspect
class BeforeExample {
    @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doAccessCheck() {
        // ...
    }
}

Si usa una expresión de corte directa, puede sobrescriba el ejemplo anterior de la siguiente manera:

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }
}
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
@Aspect
class BeforeExample {
    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    fun doAccessCheck() {
        // ...
    }
}}

Consejo después de devolver

El consejo "después de regresar" se ejecuta cuando la ejecución del método coincidente regresa en el orden normal. Puede declararlo usando la anotación @AfterReturning:

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
    @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
}
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
    @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doAccessCheck() {
        // ...
    }
}
Es posible tener múltiples declaraciones de consejos (y otros miembros), todas dentro de un mismo aspecto. Estos ejemplos muestran solo una declaración de consejo para resaltar el impacto de cada uno.

A veces es posible que desee acceder al valor real devuelto en el cuerpo del consejo. Puede utilizar un formulario @AfterReturning que vincule un valor de retorno para proporcionar dicho acceso, como se muestra en el siguiente ejemplo:

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
    @AfterReturning(
        pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }
} 
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
    @AfterReturning(
        pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        returning = "retVal")
    fun doAccessCheck(retVal: Any) {
        // ...
    }
}

El nombre utilizado en el atributo returning debe coincidir con el nombre del parámetro en el método de asesoramiento. Si la ejecución del método regresa, el valor de retorno se pasa al método de asesoramiento como el valor del argumento correspondiente. La expresión returning también limita la coincidencia solo a aquellas ejecuciones de métodos que devuelven un valor del tipo especificado (en este caso Object, que coincide con cualquier valor de retorno).

Nota Tenga en cuenta que cuando se utiliza el consejo "después de lanzar", es imposible devolver una referencia completamente diferente.

Consejo después de lanzar

El consejo "después de lanzar" es ejecutado cuando el método correspondiente completa la ejecución arrojando una excepción. Se puede declarar usando la anotación @AfterThrowing, como se muestra en el siguiente ejemplo:

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
    @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }
}
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing
@Aspect
class AfterThrowingExample {
    @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doRecoveryActions() {
        // ...
    }
}

A menudo, desea que el consejo solo se ejecute cuando se produzcan excepciones. se lanzan de un cierto tipo, pero a menudo también requieren acceso a la excepción en el cuerpo del consejo. El atributo throw se puede utilizar tanto para restringir la búsqueda coincidente (si se desea; de lo contrario, utilice Throwable como tipo de excepción) como para vincular la excepción lanzada a un parámetro tip. El siguiente ejemplo muestra cómo hacer esto:

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
    @AfterThrowing(
        pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }
} 
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing
@Aspect
class AfterThrowingExample {
    @AfterThrowing(
        pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        throwing = "ex")
    fun doRecoveryActions(ex: DataAccessException) {
        // ...
    }
}

El nombre utilizado en el atributo throw debe coincidir con el nombre del parámetro en el método de asesoramiento. Si la ejecución de un método finaliza con una excepción, la excepción se pasa al método de asesoramiento como el valor del argumento correspondiente. La expresión throw también limita la búsqueda coincidente solo a aquellas ejecuciones de métodos que arrojan una excepción del tipo especificado (en este caso DataAccessException).

Tenga en cuenta que @AfterThrowing no apunta a una devolución de llamada de manejo de excepciones general. En particular, un método de asesoramiento anotado con @AfterThrowing solo debe recibir excepciones del propio punto de unión (el método de destino declarado por el usuario), y no del compañero @After/@AfterReturning. método.

El consejo después (finalmente)

El consejo “Después (finalmente)” se ejecuta cuando el método correspondiente completa la ejecución. Se declara utilizando la anotación @After. La junta del “después” debe estar preparada para hacer frente a condiciones de retorno tanto normales como excepcionales. Normalmente se utiliza para liberar recursos y propósitos similares. El siguiente ejemplo muestra cómo utilizar el consejo "después (completo)":

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
    @After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.After
@Aspect
class AfterFinallyExample {
    @After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doReleaseLock() {
        // ...
    }
}

Tenga en cuenta que el consejo marcado con la anotación @After se define en AspectJ como "consejo después (finalización)", similar al bloque "finalmente" en una declaración try-catch). Se llamará para cualquier resultado, devolución normal o excepción lanzada desde el punto de unión (el método de destino declarado por el usuario), a diferencia de la anotación @AfterReturning, que solo se aplica a devoluciones normales exitosas.

Consejos relacionados

El último tipo de consejo es el consejo "en lugar de". El consejo "en su lugar" reemplaza la ejecución del método asociado. Puede ejecutarse antes o después de que se ejecute un método y determinar cuándo, cómo e incluso si el método se ejecuta. El consejo "en lugar" se utiliza a menudo cuando desea separar el estado "antes" y "después" de la ejecución de un método de forma segura para subprocesos, por ejemplo, para iniciar y detener un temporizador.

Utilice siempre la forma de consejo menos influyente que satisfaga sus necesidades.

Por ejemplo, no utilice consejos en lugar de si el consejoes suficiente para sus necesidades." antes de".

El consejo "en lugar" se declara anotando el método con el @Around anotación. El método debe declarar Object como su tipo de retorno y el primer parámetro del método debe ser del tipo ProceedingJoinPoint. En el cuerpo del método del consejo, debe llamar a proceed() en ProceedingJoinPoint para ejecutar el método principal. Llamar a proceed() sin argumentos hará que los argumentos originales de la persona que llama se pasen al método base cuando se llama. Para casos de uso más avanzados, existe una sobrecarga del método proceed() que toma una matriz de argumentos (Object[]). Los valores de la matriz se utilizarán como argumentos para el método base cuando se llame.

Lógica de proceder cuando se llama usando Object[] es ligeramente diferente de la lógica de proceed para el consejo "en lugar" compilado por el compilador de AspectJ. Para un consejo "en lugar" escrito utilizando el lenguaje tradicional de AspectJ, el número de argumentos pasados a proceder debe coincidir con el número de argumentos pasados en el consejo "en lugar" (no el número de argumentos aceptados por el consejo subyacente). punto de unión), y el valor pasado para continuar en una posición de argumento determinada desplaza el valor original en el punto de unión de la entidad a la que estaba vinculado ese valor (no se preocupe si esto parece una tontería ahora).

El enfoque utilizado por Spring es más simple y se adapta mejor a su semántica basada en proxy y solo en tiempo de ejecución. Solo necesita comprender esta diferencia si está compilando aspectos anotados @AspectJ escritos para Spring y utilizando el método proceed proporcionado con argumentos con el compilador y la herramienta de enlace de AspectJ. Hay una manera de escribir estos aspectos que es 100% compatible tanto con Spring AOP como con AspectJ.

El valor devuelto por el consejo "en lugar" es el valor de retorno que el código de llamada del método ve. Por ejemplo, un aspecto de almacenamiento en caché simple podría devolver un valor del caché si existe, o llamar a proceed() (y devolver ese valor) si no existe. Tenga en cuenta que proceed se puede llamar una vez, muchas veces o no llamarse en absoluto en el cuerpo del consejo "en lugar". Nada de esto está prohibido.

Si declara el tipo de retorno del método de aviso "en su lugar" como void, entonces null siempre será se devolverá a la persona que llama, ignorando efectivamente el resultado de cualquier llamada proceed(). Por lo tanto, se recomienda que el método de asesoramiento "en su lugar" declare el tipo de retorno Objeto. Un método de consejo generalmente debería devolver el valor devuelto por una llamada a proceed(), incluso si el método subyacente tiene un tipo de retorno de void. Sin embargo, la sugerencia puede devolver opcionalmente un valor almacenado en caché, un valor ajustado o algún otro valor según el caso de uso.

El siguiente ejemplo muestra cómo utilizar la sugerencia "en lugar":

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // inicia el valor del cronómetro
        Object retVal = pjp.proceed();
        // detener el cronómetro
        return retVal;
    }
}
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.ProceedingJoinPoint
@Aspect
class AroundExample {
    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
        // inicia el valor del cronómetro
        val retVal = pjp.proceed()
        // detener el cronómetro
        return retVal
    }
}

Opciones de consejos

Spring ofrece consejos completamente escritos, que significa que declaras los parámetros necesarios en la firma del consejo (como pudiste ver anteriormente en los ejemplos con la devolución y el lanzamiento de una excepción), en lugar de trabajar constantemente con matrices Object[]. Más adelante en esta sección, veremos cómo poner argumentos y otros valores contextuales a disposición del cuerpo del consejo. Primero veremos cómo escribir una sugerencia genérica que pueda reconocer el método que la sugerencia proporciona actualmente.

Acceso al JoinPoint

actual Para cualquier método tip, puede declarar como primer parámetro un parámetro de tipo org.aspectj.lang.JoinPoint. Tenga en cuenta que el consejo "en lugar" requiere que declare el primer parámetro de tipo ProceedingJoinPoint, que es una subclase de JoinPoint.

El JoinPoint Interface > proporciona una serie de métodos útiles:

  • getArgs(): devuelve los argumentos del método.

  • getThis(): Devuelve el objeto proxy.

  • getTarget(): Devuelve el objeto de destino.

  • getSignature(): Devuelve la descripción del método proporcionado por la sugerencia.

  • toString(): Imprime una descripción prácticamente utilizable del método proporcionado por la sugerencia.

Pasar parámetros a la sugerencia

Ya hemos visto cómo vincular un valor de retorno o un valor de excepción (usando los consejos "después del retorno" y "después de lanzar una excepción"). Para que los valores de los argumentos estén disponibles para el cuerpo del consejo, puede utilizar la forma de enlace args. Si utiliza un nombre de parámetro en lugar de un nombre de tipo en una expresión args, el valor del argumento correspondiente se pasa como valor del parámetro cuando se llama al consejo. Un ejemplo aclarará todo. Digamos que necesita proporcionar asesoramiento para realizar operaciones DAO que toman un objeto Account como primer parámetro y necesita acceso a la cuenta en el cuerpo del asesoramiento. Puedes escribir lo siguiente:

Java

@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..) ")
public void validateAccount(Account account) {
    // ...
}
Kotlin

@Before( "com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
fun validateAccount(account: Account) {
    // ...
}

La porción args(account,..) de la expresión de segmento tiene dos propósitos. Primero, limita la búsqueda de coincidencias solo a aquellas ejecuciones de métodos en las que el método toma al menos un parámetro y el argumento pasado como parámetro es una instancia de Account. En segundo lugar, expone el tablero al objeto Account real a través del parámetro account.

Otra forma de escribirlo es declarar un segmento que "proporciona "el valor del objeto Cuenta si coincide con el punto de unión, luego acceda al segmento nombrado desde el tablero. Se verá así:

Java

@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,.. )")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
    // ...
}
Kotlin

@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private fun accountDataAccessOperation(account: Account) {
}
@Before("accountDataAccessOperation(account)")
fun validateAccount(account: Account) {
    // ...
}

Más detalles Consulte la Guía de programación de AspectJ para obtener información.

Objeto proxy (this), objeto de destino (target) y anotaciones(@within, @target, @annotation y @args) se pueden vincular de forma similar. Los dos ejemplos siguientes muestran cómo asignar la ejecución de métodos anotados con la anotación @Auditable y extraer el código de auditoría:

El primero de dos ejemplos muestra la definición del @Auditable annotation:

Java

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
    AuditCode value();
}
Kotlin

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Auditable(val value: AuditCode)

El segundo de dos ejemplos muestra consejos que coinciden con la ejecución de métodos marcados con la anotación @Auditable:

Java

@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
    AuditCode code = auditable.value();
    // ...
}
Kotlin

@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
fun audit(auditable: Auditable) {
    val code = auditable.value()
    // ...
}

Sugerencias y parámetros genéricos

Spring AOP puede manejar genéricos utilizados en declaraciones de clases y parámetros de métodos. Digamos que tiene un tipo genérico como el siguiente:

Java

public interface Sample<T> {
    void sampleGenericMethod(T param);
    void sampleGenericCollectionMethod(Collection<T> param);
}
Kotlin

interface Sample<T> {
    fun sampleGenericMethod(param: T)
    fun sampleGenericCollectionMethod(param: Collection<T>)

Puede restringir la interceptación de tipos de métodos a ciertos tipos de parámetros por parámetro consejos vinculantes sobre el tipo de parámetro para el cual desea interceptar el método:

Java

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
    // Implementación del consejo
}
Kotlin

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
fun beforeSampleMethod(param: MyType) {
        // Implementación del consejo
}

Este enfoque no funciona para colecciones genéricas. Por lo tanto, no funcionará definir un segmento como este:

Java

@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args (param)")
public void beforeSampleMethod(Collection<MyType> param) {
        // Implementación del consejo
}
Kotlin

@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
fun beforeSampleMethod(param: Collection<MyType>) {
        // Implementación del consejo
}

Para que esto funcione, tendríamos que verificar cada elemento de la colección, lo cual no es práctico ya que tampoco podemos decidir cómo manejar valores null en general. Para lograr algo como esto, necesita convertir el parámetro al formulario Collection<?> y verificar manualmente el tipo de elementos.

Definir nombres de argumentos

Los parámetros vinculantes en las llamadas a asesoramiento se basan en hacer coincidir los nombres utilizados en las expresiones de segmento con los nombres de los parámetros declarados en las firmas de métodos de segmento y asesoramiento. Los nombres de los parámetros no están disponibles a través de la reflexión de Java, por lo que Spring AOP utiliza la siguiente estrategia para determinar los nombres de los parámetros:

  • Si el usuario ha especificado explícitamente los nombres de los parámetros, entonces los nombres de los parámetros dados son usados. Las anotaciones de sugerencia y corte tienen un atributo argNames opcional que se puede usar para especificar los nombres de los argumentos del método anotado. Estos nombres de argumentos están disponibles en tiempo de ejecución. El siguiente ejemplo muestra cómo utilizar el atributo argNames:

Java

@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
        AuditCode code = auditable.value();
        //... usa el código, el bean y el punto de conexión
}
Kotlin

@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(bean: Any, auditable: Auditable) {
    val code = auditable.value()
    //... usa el código, el bean y el punto de unión
}

Si el primer parámetro es de tipo JoinPoint, ProceedingJoinPoint o JoinPoint.StaticPart, entonces no es necesario especificar el nombre del parámetro en el valor de argNames atributo. Por ejemplo, si modifica el consejo anterior para obtener un objeto de punto de unión, el atributo argNames no debería contenerlo:

Java

@Before( value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean ,
    Auditable auditable) { AuditCode code = auditable.value();
        // ... usa el código, el bean y el punto de conexión
}
Kotlin

@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(jp: JoinPoint, bean: Any, auditable : Auditable) {
    val code = auditable.value()
    // ... usa código, bean y punto de conexión
}

Tratamiento especial del primero Los parámetros de tipo JoinPoint, ProceedingJoinPoint y JoinPoint.StaticPart son especialmente útiles para instancias de asesoramiento que no recopilan ningún otro contexto de punto de unión. En tales situaciones, se puede omitir el atributo argNames. Por ejemplo, el siguiente consejo no necesita declarar el atributo argNames:

Java

@Before("com.xyz.lib.Pointcuts.anyPublicMethod ()")
public void audit(JoinPoint jp) {
    // ... usa el punto de unión
}
Kotlin

@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
fun audit(jp: JoinPoint) {
    // ... usa el punto de unión
}
  • Usar el atributo argNames es algo incómodo, por lo que si el atributo argNames no se ha configurado, Spring AOP analiza la información de depuración de la clase e intenta determinar los nombres de los parámetros de la tabla de variables locales. Esta información estará presente hasta que las clases se compilen utilizando información de depuración (al menos -g:vars). Las consecuencias de compilar con este indicador son: (1) el código se vuelve un poco más fácil de entender (ingeniería inversa); (2) el tamaño de los archivos de clase se vuelve ligeramente mayor (normalmente sin importancia); (3) su compilador no aplica optimizaciones para eliminar variables locales no utilizadas. En otras palabras, no tendrá ningún problema si compila con este indicador.

    Si el aspecto @AspectJ fue compilado por AspectJ(ajc) compilador) incluso sin información de depuración, no es necesario agregar el atributo argNames, ya que el compilador conservará la información necesaria.
  • Si el código se compiló sin la información de depuración necesaria, Spring AOP intentará rastrear pares de variables vinculadas a parámetros (por ejemplo, si en una expresión de segmento solo una variable tiene un vínculo y el método de consejo toma sólo un parámetro, entonces la relación de pares es obvia). Si el enlace de la variable es ambiguo dada la información disponible, se lanzará una AmbiguousBindingException.

  • Si todas las estrategias anteriores fallan, se lanzará una Se lanzará IllegalArgumentException.

Continuar la ejecución de un método usando argumentos

Anteriormente notamos que le diremos cómo escribir un llame al código del método proceder usando argumentos, que funcionarán de manera consistente en Spring AOP y AspectJ. La solución es que la firma del aviso debe vincular todos los parámetros del método en orden. El siguiente ejemplo muestra cómo hacer esto:

Java

@Around("execution(List<Account> find*(..)) && " +
        "com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
        "args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
        String accountHolderNamePattern) throws Throwable {
    String newPattern = preProcess(accountHolderNamePattern);
    return pjp.proceed(new Object[] {newPattern});
}
Kotlin

@Around("execution(List<Account> find*(..)) && " +
        "com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
        "args(accountHolderNamePattern)")
fun preProcessQueryPattern(pjp: ProceedingJoinPoint,
                        accountHolderNamePattern: String): Any {
    val newPattern = preProcess(accountHolderNamePattern)
    return pjp.proceed(arrayOf<Any>(newPattern))
}

En muchos casos todavía tienes que hacer este enlace (como en el ejemplo anterior ).

Ordenar consejos

¿Qué sucede si se ejecutan varios consejos en el mismo punto de unión? Spring AOP sigue las mismas reglas de precedencia que AspectJ para determinar el orden en que se ejecutan los consejos. El consejo con mayor antigüedad (prioridad) se ejecuta primero "en sentido ascendente" (por lo tanto, si se dan dos consejos "en sentido ascendente", el que tiene el nivel de precedencia más alto se ejecuta primero). "Al salir" del punto de unión, el consejo con el nivel de prioridad más alto se ejecuta en último lugar (por lo tanto, si se dan dos consejos "después", el consejo con el nivel de prioridad más alto se ejecutará en segundo lugar).

Si dos consejos definidos en aspectos diferentes deben ejecutarse en el mismo punto de conexión, salvo que se especifique lo contrario, el orden de ejecución no se especifica. Puede controlar el orden de ejecución estableciendo prioridad. Esto se hace de la manera habitual en Spring: ya sea implementando la interfaz org.springframework.core.Ordered en la clase de aspecto, o anotando el consejo con la anotación @Order Cuando hay dos aspectos, el aspecto que devuelve el valor menor de Ordered.getOrder() (o valor de anotación) tiene el nivel de prioridad más alto.

Cada uno de los tipos individuales de asesoramiento de un aspecto particular está pensado conceptualmente para aplicarse directamente a un punto de conexión. Como consecuencia, no se espera que un método de asesoramiento anotado con @AfterThrowing reciba una excepción de un método complementario anotado con @After/@AfterReturning.

A partir de Spring Framework 5.2.7, a los métodos de asesoramiento definidos en la misma clase marcada con la anotación @Aspect que deben ejecutarse en el mismo punto de unión se les asigna un nivel de prioridad según su tipo de aviso en el siguiente orden, del nivel de prioridad más alto al más bajo: @Around, @Before, @After, @AfterReturning, @AfterThrowing Sin embargo, tenga en cuenta que un método tip anotado con @After en realidad se llamará después de cualquier método tip anotado con las anotaciones @AfterReturning o @AfterThrowing. en el mismo aspecto, siguiendo la semántica de "consejo después (finalización)" de AspectJ para la anotación @After.

Si hay dos partes del mismo tipo de consejo (por ejemplo, dos métodos de consejo con la anotación @After) definidos en la misma clase anotada con @Aspect deben ejecutarse en el mismo punto de unión, su orden no será determinado (ya que javac -las clases compiladas no pueden recibir el orden de declaración en el código fuente mediante reflexión). Considere combinar dichos métodos de asesoramiento en un método de asesoramiento para cada punto de unión en cada clase anotada con la anotación @Aspect, o refactorice el asesoramiento en clases separadas anotadas @Aspect que puedan ser ordenados a nivel de aspecto usando Ordered o @Order.

Introducciones

Introducciones (conocidas en AspectJ como declaraciones intertipo) permiten que un aspecto declare que los objetos proporcionados con el consejo implementan una interfaz determinada y proporciona una implementación de esa interfaz en nombre de esos objetos.

Puedes implementar la introducción usando el Anotación @DeclareParents. Esta anotación se utiliza para declarar que los tipos coincidentes tienen un nuevo tipo principal (de ahí el nombre). Por ejemplo, si tomamos la interfaz UsageTracked y la implementación de esa interfaz DefaultUsageTracked, entonces el siguiente aspecto declarará que todos los implementadores de interfaces de servicio también implementan UsageTracked interfaz (por ejemplo, para recopilar estadísticas a través de JMX):

Java

@Aspect
public class UsageTracking {
    @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
    public static UsageTracked mixin;
    @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
    }
}        
Kotlin

@Aspect
class UsageTracking {
    companion object {
        @DeclareParents(value = "com.xzy.myapp.service.*+", defaultImpl = DefaultUsageTracked::class)
        lateinit var mixin: UsageTracked
    }
    @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
    fun recordUsage(usageTracked: UsageTracked) {
        usageTracked.incrementUseCount()
    }
}

La interfaz que se implementará está determinada por el tipo de campo anotado. El atributo value de la anotación @DeclareParents es una plantilla de tipo AspectJ. Cualquier bean del tipo apropiado implementa la interfaz UsageTracked. Tenga en cuenta que en el consejo "antes" del ejemplo anterior, los service beans se pueden utilizar directamente como implementaciones de la interfaz UsageTracked. Al acceder al bean mediante programación, debe escribir lo siguiente:

Java
UsageTracked useTracked = (UsageTracked) context.getBean("myService");
Kotlin
val useTracked = context.getBean("myService") as UsageTracked

Modelos de instanciación de aspectos

Este es un tema complejo. Si recién está comenzando a aprender AOP, puede omitir esta sección con seguridad.

De forma predeterminada, hay una única instancia de cada aspecto en el contexto de la aplicación. AspectJ llama a esto el modelo de instanciación singleton. Se pueden definir aspectos con ciclos de vida alternativos. Spring admite los modelos de creación de instancias perthis y pertarget de AspectJ; percflow, percflowbelow y pertypewithin no son compatibles actualmente.

Puedes declarar un aspecto perthis especificando la expresión perthis en la anotación @Aspect. Considere el siguiente ejemplo:

Java

@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
public class MyAspect {
    private int someState;
    @Before("com.xyz.myapp.CommonPointcuts.businessService()")
    public void recordServiceUsage() {
        // ...
    }
}
Kotlin

@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
class MyAspect {
    private val someState: Int = 0
    @Before("com.xyz.myapp.CommonPointcuts.businessService()")
    fun recordServiceUsage() {
        // ...
    }
}

En el ejemplo anterior, el efecto de la expresión perthis es que se crea una instancia de aspecto para cada objeto de servicio único que ejecuta el servicio comercial (cada objeto único asociado con this en los puntos de unión correspondientes a la expresión de segmento). Se crea una instancia de aspecto la primera vez que se llama a un método en un objeto de servicio. Un aspecto queda fuera de alcance si el objeto de servicio también queda fuera de alcance. Antes de crear una instancia de aspecto, no se ejecuta ninguno de los consejos que contiene. Una vez que se crea una instancia de aspecto, el consejo declarado en ella comenzará a ejecutarse en los puntos de conexión mapeados, pero solo si el objeto de servicio es aquel al que está asociado el aspecto. Para obtener más información sobre las expresiones per, consulte la Guía de programación de AspectJ.

El modelo de creación de instancias pertarget funciona exactamente igual que perthis, pero crea una instancia de aspecto para cada objetivo único en puntos de unión coincidentes.

Ejemplo de AOP

Ahora que has visto cómo funcionan todas las partes, podemos juntarlas. para hacer algo práctico.

A veces, la ejecución de servicios empresariales puede fallar debido a problemas de concurrencia (como un punto muerto). Si se repite la operación, lo más probable es que tenga éxito la próxima vez que lo intente. Para los servicios empresariales donde es apropiado reintentar una operación bajo estas condiciones (operaciones idempotentes que no necesitan volver al usuario para resolver el conflicto), queremos reintentar la operación de una manera clara sin que el cliente vea un PessimisticLockingFailureException. Este es un requisito que obviamente abarca múltiples servicios a nivel de servicio y, por lo tanto, es ideal para implementarlo a través de un aspecto.

Dado que necesitamos repetir la operación, debemos usar el consejo "en lugar" para que que podemos llamar al método proceder varias veces. El siguiente listado muestra la implementación básica del aspecto:

Java

@Aspect
public class ConcurrentOperationExecutor implements Ordered {
    private static final int DEFAULT_MAX_RETRIES = 2;
    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;
    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }
    public int getOrder() {
        return this.order;
    }
    public void setOrder(int order) {
        this.order = order;
    }
    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }
}
Kotlin

@Aspect
class ConcurrentOperationExecutor : Ordered {
    private val DEFAULT_MAX_RETRIES = 2
    private var maxRetries = DEFAULT_MAX_RETRIES
    private var order = 1
    fun setMaxRetries(maxRetries: Int) {
        this.maxRetries = maxRetries
    }
    override fun getOrder(): Int {
        return this.order
    }
    fun setOrder(order: Int) {
        this.order = order
    }
    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
        var numAttempts = 0
        var lockFailureException: PessimisticLockingFailureException
        do {
            numAttempts++
            try {
                return pjp.proceed()
            } catch (ex: PessimisticLockingFailureException) {
                lockFailureException = ex
            }
        } while (numAttempts <= this.maxRetries)
        throw lockFailureException
    }
}

Tenga en cuenta que el aspecto implementa la interfaz Ordered para que podamos establecer el nivel de precedencia de aspecto más alto que el consejo de transacción (queremos que la transacción sea nueva cada vez que volvamos a intentarlo). Las propiedades maxRetries y order están configuradas por Spring. La acción principal ocurre en el consejo "en lugar de" doConcurrentOperation. Tenga en cuenta que actualmente estamos aplicando lógica de reintento a cada businessService(). Intentamos continuar la ejecución y, en caso de error PessimisticLockingFailureException, lo intentamos de nuevo, si no se han agotado todos los reintentos.

La siguiente es la configuración de Spring correspondiente:


<aop:aspectj-autoproxy/>
<bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
    <property name="maxRetries" value="3"/>
    <property name="order" value="100"/>
</bean>

Para refinar el aspecto para que solo repita operaciones idempotentes, podemos definir la siguiente anotación Idempotent :

Java

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // anotación de marcador
}
Kotlin

@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent
    // anotación de marcador

Luego puede usar la anotación para anotar la implementación de operaciones de utilidad. Cambiar el aspecto para volver a ejecutar solo operaciones idempotentes implica refinar la expresión de segmento para que solo coincidan las operaciones @Idempotent, como se muestra a continuación:

Java

@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
    // ...
}
Kotlin

@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
    // ...
}