spring AOP 4: explicación detallada de la sintaxis del identificador de punto de entrada @AspectJ

El indicador de corte de punto AspectJ (usado para indicar el propósito de las expresiones de corte de punto) soportado por Spring AOP. Actualmente en Spring AOP, solo hay un punto de conexión para el método de ejecución (porque Spring se basa en un proxy dinámico, Spring solo admite puntos de conexión de método. Esto es diferente de algunos Otros marcos de AOP son diferentes, como AspectJ y JBoss, además de los puntos de corte del método, también proporcionan puntos de acceso para campos y constructores. Spring carece de soporte para puntos de conexión de campo y no puede permitirnos crear notificaciones detalladas, como la intercepción La modificación del campo de objeto. Y no es compatible con el punto de conexión del constructor, no podemos aplicar la notificación cuando se crea el bean. Pero la intercepción del método puede satisfacer la mayoría de las necesidades. Si necesita una función de intercepción del punto de conexión que no sea la intercepción del método, entonces podemos Use Aspect para complementar la funcionalidad de Spring AOP).

Los indicadores de punto de entrada AspectJ admitidos por Spring AOP son los siguientes:

Descripción del método coincidente:

ejecución (): punto de conexión para la ejecución del método coincidente;

  Estructura de sintaxis (expresión de corte de punto de AspectJ): ejecución (modificador de método, método, valor de retorno, la clase de método pertenece al nombre de método coincidente (tabla de parámetros formales en el método) excepción de declaración de método lanzada)

  La parte de fuente roja no se puede omitir, y todas las partes admiten el comodín "*" para que coincida con todas.

Método de coincidencia de parámetros:

args (): se usa para hacer coincidir los parámetros del método ejecutado actualmente con el tipo especificado de método de ejecución;

@args: se utiliza para hacer coincidir la ejecución del método ejecutado actualmente con los parámetros que llevan la anotación especificada;

El tipo de objeto proxy AOP actual coincide:

this (): el método de ejecución utilizado para hacer coincidir el tipo de objeto proxy AOP actual; tenga en cuenta que la coincidencia de tipos del objeto proxy AOP, que puede incluir la introducción de interfaces y la coincidencia de tipos;

Coincidencia de clase objetivo:

target (): el método de ejecución utilizado para hacer coincidir el tipo del objeto de destino actual; tenga en cuenta que el tipo de objeto de destino coincide, por lo que no incluye la introducción de interfaces y la coincidencia de tipos;

@target: se utiliza para hacer coincidir el método de ejecución del tipo de objeto de destino actual, donde el objeto de destino contiene la anotación especificada;

 inside (): se utiliza para hacer coincidir la ejecución de métodos dentro del tipo especificado;

@within: se utiliza para hacer coincidir todos los métodos dentro del tipo de anotación especificado;

Los métodos marcados con esta anotación coinciden:

 @anotación: se usa para hacer coincidir el método de ejecución actual con el método de anotación especificado;

Objeto Bean que coincide con un nombre específico:

bean (): extensión Spring AOP, AspectJ no tiene un método de ejecución para el indicador, que se utiliza para hacer coincidir el objeto Bean con un nombre específico;

Para citar otros puntos de entrada con nombre:

punto de referencia: significa hacer referencia a otros puntos de entrada con nombre, solo compatibles con el estilo @ApectJ, no con el estilo de esquema.

       Los indicadores de puntos de corte admitidos por los puntos de corte de AspectJ son: llamar, obtener, establecer, preinicialización, inicialización estática, inicialización, manejador, ejecución de consejos, dentro del código, flujo, flujo abajo, si, @esto, @withincode; pero Spring AOP no admite actualmente estas instrucciones Operador, el uso de estos indicadores arrojará una IllegalArgumentException. Estos indicadores Spring AOP pueden extenderse en el futuro.

1. Nombramiento y punto de entrada anónimo

       Los puntos de corte nombrados pueden ser referenciados por otros puntos de corte, mientras que los puntos de corte anónimos no.

   Solo @AspectJ admite puntos de entrada con nombre, mientras que el estilo de esquema no admite puntos de entrada con nombre.

Como se muestra a continuación, @AspectJ se refiere a los puntos de entrada con nombre de la siguiente manera:

2. Escriba la sintaxis coincidente

Primero, comprendamos los comodines de la coincidencia de tipo AspectJ:

         *: Coincide con cualquier número de caracteres;

         ..: hacer coincidir cualquier número de repeticiones de caracteres, como hacer coincidir cualquier número de subpaquetes en modo de tipo y hacer coincidir cualquier número de parámetros en modo de parámetro de método.

         +: Coincide con el subtipo del tipo especificado; solo se puede colocar como sufijo después del patrón de tipo.

Ejemplos: 

1、java.lang.String    匹配String类型; 
2、java.*.String        匹配java包下的任何“一级子包”下的String类型; 
   如匹配java.lang.String,但不匹配java.lang.ss.String 
3、java..*             匹配java包及任何子包下的任何类型; 
   如匹配java.lang.String、java.lang.annotation.Annotation 
4、java.lang.*ing      匹配任何java.lang包下的以ing结尾的类型; 
5、java.lang.Number+  匹配java.lang包下的任何Number的自类型; 
                   如匹配java.lang.Integer,也匹配java.math.BigInteger

A continuación, veamos el tipo específico de expresión coincidente:

         Tipo de coincidencia: coincida de la siguiente manera

 Anotación? Nombre completo de la clase

  • Anotación: opcional, las anotaciones contenidas en el tipo, como @Deprecated;
  • Nombre completo de la clase: requerido, puede ser el nombre completo de cualquier clase.

         Implementación del método de coincidencia: utilice el siguiente método para hacer coincidir:

Anotación? ¿Modificador? ¿Declaración de tipo de valor de retorno? Nombre del método (lista de parámetros) ¿Lista de excepciones?
  • Anotación: opcional, la anotación contenida en el método, como @Deprecated;
  • Modificador: opcional, como público, protegido;
  • Tipo de valor de retorno: requerido, puede ser cualquier modo de tipo; "*" significa todos los tipos;
  • Declaración de tipo: opcional, puede ser cualquier patrón de tipo;
  • Nombre del método: requerido, puede usar "*" para la coincidencia de patrones;
  • Lista de parámetros: "()" significa que el método no tiene ningún parámetro; "(..)" significa métodos coincidentes que aceptan cualquier número de parámetros, y "(.., java.lang.String)" significa métodos coincidentes que aceptan tipos java.lang.String El parámetro finaliza y el método con cualquier número de parámetros puede aceptarse delante de él; "(java.lang.String, ..)" significa que el método que coincide con el parámetro de tipo java.lang.String es aceptado, y el método detrás de él puede aceptar cualquier número de parámetros ; "(*, Java.lang.String)" significa que el método que acepta parámetros de tipo java.lang.String finaliza y acepta un método con un parámetro de cualquier tipo delante de él;
  • Lista de excepciones: opcional, declarada con "lista de nombres completamente calificados de excepción de lanzamientos", si hay varias listas de nombres completamente calificados de excepciones, separados por "," como lanzamientos java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException.

         Hacer coincidir el nombre del bean: puede usar la identificación o el nombre del bean para hacer coincidir, y puede usar el comodín "*";

3. Expresiones combinadas de puntos de corte

       AspectJ usa y (&&), o (||), y no (!) Para combinar expresiones de corte de punto.

       En el estilo de esquema, debido a que el uso de "&&" en XML necesita usar el carácter de escape "& amp; & amp;" para reemplazarlo, es muy inconveniente, por lo que Spring AOP proporciona y, o no reemplaza &&, ||, ! .

3.1 Ejemplos de uso de puntos de entrada

   3.1.1, ejecución () : utilice el método de coincidencia "ejecución (expresión de método)" para ejecutar;

 

Patrón

Descripción

público * * (..)

Ejecución de cualquier método público.

* cn.javass..IPointcutService. * ()

Cualquier método sin parámetros en la interfaz IPointcutService bajo el paquete cn.javass y todos los subpaquetes

* cn.javass .. *. * (..)

cualquier método de cualquier clase bajo el paquete cn.javass y todos los subpaquetes

* cn.javass..IPointcutService. * (*)

El paquete cn.javass y todos los subpaquetes tienen solo un método de parámetro para cualquiera de las interfaces IPointcutService

* (! cn.javass..IPointcutService +). * (..)

Cualquier método que no sea "paquete cn.javass y todos los subpaquetes interfaz y subtipo IPointcutService"

* cn.javass..IPointcutService +. * ()

cualquier método sin parámetros de la interfaz IPointcutService y los subtipos en el paquete cn.javass y todos los subpaquetes

* cn.javass..IPointcut * .test * (java.util.Date)

En el paquete cn.javass y en todos los subpaquetes, el tipo de prefijo IPointcut comienza con test y solo tiene un método con un tipo de parámetro de java.util.Date. Tenga en cuenta que la coincidencia se basa en el tipo de parámetro de la firma del método, no en la ejecución. El tipo del parámetro entrante determina

Tal como el método de definición: prueba de vacío público (Object obj); incluso si se pasa java.util.Date durante la ejecución, no coincidirá;

* cn.javass..IPointcut * .test * (..) lanza

IllegalArgumentException, ArrayIndexOutOfBoundsException

Cualquier método del tipo de prefijo IPointcut bajo el paquete cn.javass y todos los subpaquetes, y arroja excepciones IllegalArgumentException y ArrayIndexOutOfBoundsException

* (cn.javass..IPointcutService +

&& java.io.Serializable +). * (..)

Cualquier método que implemente los tipos de la interfaz IPointcutService y la interfaz java.io.Serializable bajo el paquete cn.javass y todos los subpaquetes

@ java.lang.Deprecated * * (..)

Cualquier método con @ java.lang. Anotación reducida

@ java.lang.Deprecated @ cn.javass..Secure * * (..)

Cualquier método con @ java.lang.Deprecated y @ cn.javass ... Asegure las anotaciones

@ (java.lang.Deprecated || cn.javass..Secure) * * (..)

Cualquier método con @ java.lang.Deprecated o @ cn.javass..Secure annotation

(@ cn.javass..Secure *) * (..)

Cualquier tipo de valor de retorno que contenga @ cn.javass ... Método seguro

* (@ cn.javass..Secure *). * (..)

Cualquier tipo que defina un método contiene @ cn.javass..Secure's method

* * (@ cn.javass..Secure (*), @ cn.javass..Secure (*))

Cualquier método con dos parámetros en la firma, y ​​estos dos parámetros están marcados por @ Secure,

如 prueba de vacío público (@Secure String str1,

@Secure String str1);

* * ((@ cn.javass..Secure *)) o

* * (@ cn.javass..Secure *)

Cualquier método con un parámetro, y el tipo de parámetro contiene @ cn.javass..Secure;

Como la prueba de vacío público (modelo de modelo) y la anotación @Secure en la clase de modelo

* * (

@ cn.javass..Secure (@ cn.javass..Secure *),

@ cn.javass..Secure (@ cn.javass..Secure *))

任何带有两个参数的方法,且这两个参数都被@ cn.javass..Secure标记了;且这两个参数的类型上都持有@ cn.javass..Secure;

 

* *(

java.util.Map<cn.javass..Model, cn.javass..Model>

, ..)

任何带有一个java.util.Map参数的方法,且该参数类型是以< cn.javass..Model, cn.javass..Model >为泛型参数;注意只匹配第一个参数为java.util.Map,不包括子类型;

如public void test(HashMap<Model, Model> map, String str);将不匹配,必须使用“* *(

java.util.HashMap<cn.javass..Model,cn.javass..Model>

, ..)”进行匹配;

而public void test(Map map, int i);也将不匹配,因为泛型参数不匹配

* *(java.util.Collection<@cn.javass..Secure *>)

任何带有一个参数(类型为java.util.Collection)的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.javass..Secure注解;

如public void test(Collection<Model> collection);Model类型上持有@cn.javass..Secure

* *(java.util.Set<? extends HashMap>)

任何带有一个参数的方法,且传入的参数类型是有一个泛型参数,该泛型参数类型继承与HashMap;

Spring AOP目前测试不能正常工作

* *(java.util.List<? super HashMap>)

任何带有一个参数的方法,且传入的参数类型是有一个泛型参数,该泛型参数类型是HashMap的基类型;如public voi test(Map map);

Spring AOP目前测试不能正常工作

* *(*<@cn.javass..Secure *>)

任何带有一个参数的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.javass..Secure注解;

Spring AOP目前测试不能正常工作

 3.1.2、within():使用“within(类型表达式)”匹配指定类型内的方法执行;

模式

描述

within(cn.javass..*)

cn.javass包及子包下的任何方法执行

within(cn.javass..IPointcutService+)

cn.javass包或所有子包下IPointcutService类型及子类型的任何方法

within(@cn.javass..Secure *)

持有cn.javass..Secure注解的任何类型的任何方法

必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

 

 

 

 

 

 

 

 3.1.3、this():使用“this(类型全限定名)”匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口方法也可以匹配;注意this中使用的表达式必须是类型全限定名,不支持通配符;

模式

描述

this(cn.javass.spring.chapter6.service.IPointcutService)

当前AOP对象实现了 IPointcutService接口的任何方法

this(cn.javass.spring.chapter6.service.IIntroductionService)

当前AOP对象实现了 IIntroductionService接口的任何方法

也可能是引入接口

 

 

 

 

 

 

 

 3.1.4、target():使用“target(类型全限定名)”匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;注意target中使用的表达式必须是类型全限定名,不支持通配符;

模式

描述

target(cn.javass.spring.chapter6.service.IPointcutService)

当前目标对象(非AOP对象)实现了 IPointcutService接口的任何方法

target(cn.javass.spring.chapter6.service.IIntroductionService)

当前目标对象(非AOP对象) 实现了IIntroductionService 接口的任何方法

不可能是引入接口

 

 

 

 

 

 

 

3.1.5、args():使用“args(参数类型列表)”匹配当前执行的方法传入的参数为指定类型的执行方法;注意是匹配传入的参数类型,不是匹配方法签名的参数类型;参数类型列表中的参数必须是类型全限定名,通配符不支持;args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用; 

模式

描述

args (java.io.Serializable,..)

任何一个以接受“传入参数类型为 java.io.Serializable” 开头,且其后可跟任意个任意类型的参数的方法执行,args指定的参数类型是在运行时动态匹配的

 

 

 

 

 3.1.6、@within:使用“@within(注解类型)”匹配所以持有指定注解类型内的方法;注解类型也必须是全限定类型名;

模式

描述

@within cn.javass.spring.chapter6.Secure)

任何目标对象对应的类型持有Secure注解的类方法;

必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

 

 

 

 

 3.1.7、@target:使用“@target(注解类型)”匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;注解类型也必须是全限定类型名;

模式

描述

@target (cn.javass.spring.chapter6.Secure)

任何目标对象持有Secure注解的类方法;

必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

 

 

 

 

 3.1.8、@args:使用“@args(注解列表)”匹配当前执行的方法传入的参数持有指定注解的执行;注解类型也必须是全限定类型名;

模式

描述

@args (cn.javass.spring.chapter6.Secure)

任何一个只接受一个参数的方法,且方法运行时传入的参数持有注解 cn.javass.spring.chapter6.Secure;动态切入点,类似于arg指示符;

 

 

 

 

 3.1.9、@annotation:使用“@annotation(注解类型)”匹配当前执行方法持有指定注解的方法;注解类型也必须是全限定类型名;

模式

描述

@annotation(cn.javass.spring.chapter6.Secure )

当前执行方法上持有注解 cn.javass.spring.chapter6.Secure将被匹配

 

 

 

 

 3.1.10、bean():使用“bean(Bean id或名字通配符)”匹配特定名称的Bean对象的执行方法;Spring AOP扩展的,在AspectJ中无相应概念;

模式

描述

bean(*Service)

匹配所有以Service命名(id或name)结尾的Bean

 

 

 

3.1.11、reference pointcut:表示引用其他命名切入点,只有@ApectJ风格支持,Schema风格不支持,如下所示:

 

 比如我们定义如下切面:

package cn.javass.spring.chapter6.aop; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.annotation.Pointcut; 
@Aspect 
public class ReferencePointcutAspect { 
    @Pointcut(value="execution(* *())") 
    public void pointcut() {} 
}

可以通过如下方式引用: 

@Before(value = "cn.javass.spring.chapter6.aop.ReferencePointcutAspect.pointcut()") 
public void referencePointcutTest2(JoinPoint jp) {} 

除了可以在@AspectJ风格的切面内引用外,也可以在Schema风格的切面定义内引用,引用方式与@AspectJ完全一样。 

 到此我们切入点表达式语法示例就介绍完了,我们这些示例几乎包含了日常开发中的所有情况,但当然还有更复杂的语法等等,如果以上介绍的不能满足您的需要,请参考AspectJ文档。

 

二、通知参数

      如果想获取被被通知方法参数并传递给通知方法,该如何实现呢?接下来我们将介绍两种获取通知参数的方式。

2.1、使用JoinPoint获取

  Spring AOP提供使用org.aspectj.lang.JoinPoint类型获取连接点数据,任何通知方法的第一个参数都可以是JoinPoint(环绕通知是ProceedingJoinPoint,JoinPoint子类),当然第一个参数位置也可以是JoinPoint.StaticPart类型,这个只返回连接点的静态部分。

1) JoinPoint:提供访问当前被通知方法的目标对象、代理对象、方法参数等数据:

package org.aspectj.lang; 
import org.aspectj.lang.reflect.SourceLocation; 
public interface JoinPoint { 
    String toString();         //连接点所在位置的相关信息 
    String toShortString();     //连接点所在位置的简短相关信息 
    String toLongString();     //连接点所在位置的全部相关信息 
    Object getThis();         //返回AOP代理对象 
    Object getTarget();       //返回目标对象 
    Object[] getArgs();       //返回被通知方法参数列表 
    Signature getSignature();  //返回当前连接点签名 
    SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置 
    String getKind();        //连接点类型 
    StaticPart getStaticPart(); //返回连接点静态部分 
}

 

2)ProceedingJoinPoint:用于环绕通知,使用proceed()方法来执行目标方法: 

public interface ProceedingJoinPoint extends JoinPoint { 
    public Object proceed() throws Throwable; 
    public Object proceed(Object[] args) throws Throwable; 
} 

 

3) JoinPoint.StaticPart:提供访问连接点的静态部分,如被通知方法签名、连接点类型等: 

public interface StaticPart { 
Signature getSignature();    //返回当前连接点签名 
String getKind();          //连接点类型 
    int getId();               //唯一标识 
String toString();         //连接点所在位置的相关信息 
    String toShortString();     //连接点所在位置的简短相关信息 
    String toLongString();     //连接点所在位置的全部相关信息 
}

 

示例:使用如下方式在通知方法上声明,必须是在第一个参数,然后使用jp.getArgs()就能获取到被通知方法参数: 

@Before(value="execution(* sayBefore(*))") 
public void before(JoinPoint jp) {} 
 
@Before(value="execution(* sayBefore(*))") 
public void before(JoinPoint.StaticPart jp) {} 

2.2、自动获取:通过切入点表达式可以将相应的参数自动传递给通知方法,例如前边章节讲过的返回值和异常是如何传递给通知方法的。

在Spring AOP中,除了execution和bean指示符不能传递参数给通知方法,其他指示符都可以将匹配的相应参数或对象自动传递给通知方法。

示例:

@Before(value="execution(* test(*)) && args(param)", argNames="param") 
public void before1(String param) { 
    System.out.println("===param:" + param); 
} 

 切入点表达式execution(* test(*)) && args(param) :

1)首先execution(* test(*))匹配任何方法名为test,且有一个任何类型的参数;

2)args(param)将首先查找通知方法上同名的参数,并在方法执行时(运行时)匹配传入的参数是使用该同名参数类型,即java.lang.String;如果匹配将把该被通知参数传递给通知方法上同名参数。

其他指示符(除了execution和bean指示符)都可以使用这种方式进行参数绑定。

在此有一个问题,即前边提到的类似于【3.1.2构造器注入】中的参数名注入限制:在class文件中没生成变量调试信息是获取不到方法参数名字的。

所以我们可以使用策略来确定参数名:

  1. 如果我们通过“argNames”属性指定了参数名,那么就是要我们指定的;
@Before(value=" args(param)", argNames="param") //明确指定了 
public void before1(String param) { 
    System.out.println("===param:" + param); 
} 

2、如果第一个参数类型是JoinPoint、ProceedingJoinPoint或JoinPoint.StaticPart类型,应该从“argNames”属性省略掉该参数名(可选,写上也对),这些类型对象会自动传入的,但必须作为第一个参数; 

@Before(value=" args(param)", argNames="param") //明确指定了 
public void before1(JoinPoint jp, String param) { 
    System.out.println("===param:" + param); 
} 

3、如果“class文件中含有变量调试信息”将使用这些方法签名中的参数名来确定参数名; 

@Before(value=" args(param)") //不需要argNames了 
public void before1(JoinPoint jp, String param) { 
    System.out.println("===param:" + param); 
} 

4、如果没有“class文件中含有变量调试信息”,将尝试自己的参数匹配算法,如果发现参数绑定有二义性将抛出AmbiguousBindingException异常;对于只有一个绑定变量的切入点表达式,而通知方法只接受一个参数,说明绑定参数是明确的,从而能配对成功。 

@Before(value=" args(param)")  
public void before1(JoinPoint jp, String param) { 
    System.out.println("===param:" + param); 
}

5、以上策略失败将抛出IllegalArgumentException。

接下来让我们示例一下组合情况吧:

@Before(args(param) && target(bean) && @annotation(secure)",  
        argNames="jp,param,bean,secure") 
public void before5(JoinPoint jp, String param, 
IPointcutService pointcutService, Secure secure) { 
…… 
}

该示例的执行步骤如图6-5所示。

除了上边介绍的普通方式,也可以对使用命名切入点自动获取参数:

@Pointcut(value="args(param)", argNames="param") 
private void pointcut1(String param){} 
@Pointcut(value="@annotation(secure)", argNames="secure") 
private void pointcut2(Secure secure){} 
     
@Before(value = "pointcut1(param) && pointcut2(secure)", 
argNames="param, secure") 
public void before6(JoinPoint jp, String param, Secure secure) { 
…… 
}

 自此给通知传递参数已经介绍完了,示例代码在cn.javass.spring.chapter6.ParameterTest文件中。 

在Spring配置文件中,所以AOP相关定义必须放在<aop:config>标签下,该标签下可以有<aop:pointcut>、<aop:advisor>、<aop:aspect>标签,配置顺序不可变。

  • <aop:pointcut>:用来定义切入点,该切入点可以重用;
  • <aop:advisor>:用来定义只有一个通知和一个切入点的切面;
  • <aop:aspect>:用来定义切面,该切面可以包含多个切入点和通知,而且标签内部的通知和切入点定义是无序的;和advisor的区别就在此,advisor只包含一个通知和一个切入点。

 

 

发布了203 篇原创文章 · 获赞 6 · 访问量 4504

Supongo que te gusta

Origin blog.csdn.net/weixin_42073629/article/details/105212525
Recomendado
Clasificación