@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.
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 @Configuration
desde Java, agregue la anotación @EnableAspectJAutoProxy
como se
muestra en el siguiente ejemplo:
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
@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
;
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
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).
@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).
@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
:
@Pointcut("execution(* transfer(..))") // cortar expresión
private void anyOldTransfer() {} // cortar firma
@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.
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:
bean(idOrNameOfBean)
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:
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {}
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
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.inTrading
se negocia si la ejecución del método ocurre en el módulo comercial.tradingOperation
es consistente si la ejecución del método representa cualquier método público en el módulo comercial.
@Pointcut("execution(public * * (..))")
private fun anyPublicOperation() {}
@Pointcut("within(com.xyz.myapp.trading.. *)")
private fun inTrading() {}
@Pointcut("anyPublicOperation() && inTrading()")
private fun tradingOperation() {}
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.inTrading
se acuerda si la ejecución del método ocurre en el módulo comercial.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:
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() {}
}
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 esSerializable
, y la opción "execution" coincide si la firma del método declara un único parámetro de tipoSerializable
.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
ohandler
. Puntero definitivo seleccione el grupo deseado de puntos de conexión (posiblemente de muchos géneros):
within
ywithincode
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:
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() {
// ...
}
}
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:
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() {
// ...
}
}
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
:
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() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
@AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doAccessCheck() {
// ...
}
}
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:
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) {
// ...
}
}
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:
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() {
// ...
}
}
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:
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) {
// ...
}
}
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)":
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() {
// ...
}
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.
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":
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;
}
}
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:
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..) ")
public void validateAccount(Account account) {
// ...
}
@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í:
@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,.. )")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
@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:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}
@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
:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
@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:
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection<T> param);
}
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:
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Implementación del consejo
}
@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:
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args (param)")
public void beforeSampleMethod(Collection<MyType> param) {
// Implementación del consejo
}
@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 atributoargNames
:
@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
}
@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:
@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
}
@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
:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod ()")
public void audit(JoinPoint jp) {
// ... usa el punto de unión
}
@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 atributoargNames
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 atributoargNames
, 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:
@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});
}
@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):
@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();
}
}
@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:
UsageTracked useTracked = (UsageTracked) context.getBean("myService");
val useTracked = context.getBean("myService") as UsageTracked
Modelos de instanciación de aspectos
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:
@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
public class MyAspect {
private int someState;
@Before("com.xyz.myapp.CommonPointcuts.businessService()")
public void recordServiceUsage() {
// ...
}
}
@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:
@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;
}
}
@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
:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// anotación de marcador
}
@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:
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
// ...
}
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
// ...
}