L'indicateur de pointcut AspectJ (utilisé pour indiquer le but des expressions de point de coupe) pris en charge par Spring AOP. Actuellement, dans Spring AOP, il n'y a qu'un seul point de connexion pour la méthode d'exécution (parce que Spring est basé sur un proxy dynamique, Spring ne prend en charge que les points de connexion de méthode. Ceci est différent de certains D'autres cadres AOP sont différents, tels que AspectJ et JBoss, en plus des points de coupure de méthode, ils fournissent également des points d'accès pour les champs et les constructeurs. Spring manque de prise en charge des points de connexion de champ et ne peut pas nous permettre de créer des notifications à granularité fine, telles que l'interception La modification du champ objet. Et il ne prend pas en charge le point de connexion constructeur, nous ne pouvons pas appliquer de notification lorsque le bean est créé. Mais l'interception de méthode peut répondre à la plupart des besoins. Si vous avez besoin d'une fonction d'interception de point de connexion autre que l'interception de méthode, alors nous pouvons Utilisez Aspect pour compléter la fonctionnalité de Spring AOP).
Les indicateurs du point d'entrée AspectJ pris en charge par Spring AOP sont les suivants:
Correspondance de description de méthode:
execution (): point de connexion pour l'exécution de la méthode d'appariement;
Structure de la syntaxe (expression pointcut AspectJ): exécution (méthode de modification de méthode valeur de retour classe de méthode appartient au nom de méthode correspondant (table de paramètres formelle dans la méthode) exception de déclaration de méthode levée)
La partie de police rouge ne peut pas être omise, et toutes les parties prennent en charge le caractère générique "*" pour correspondre à toutes.
Correspondance des paramètres de méthode:
args (): utilisé pour faire correspondre les paramètres de la méthode actuellement exécutée au type spécifié de méthode d'exécution;
@args: utilisé pour faire correspondre l'exécution de la méthode actuellement exécutée avec des paramètres qui portent l'annotation spécifiée;
Le type d'objet proxy AOP actuel correspond:
this (): la méthode d'exécution utilisée pour faire correspondre le type d'objet proxy AOP actuel; notez que la correspondance de type de l'objet proxy AOP, qui peut inclure l'introduction d'interfaces et la correspondance de type;
Correspondance de classe cible:
target (): la méthode d'exécution utilisée pour faire correspondre le type de l'objet cible actuel; notez que le type de l'objet cible est mis en correspondance, de sorte qu'il n'inclut pas l'introduction d'interfaces et la mise en correspondance de types;
@target: utilisé pour faire correspondre la méthode d'exécution du type d'objet cible actuel, où l'objet cible contient l'annotation spécifiée;
within (): utilisé pour faire correspondre l'exécution des méthodes dans le type spécifié;
@within: utilisé pour faire correspondre toutes les méthodes dans le type d'annotation spécifié;
Les méthodes marquées par cette annotation correspondent:
@annotation: utilisé pour faire correspondre la méthode d'exécution actuelle avec la méthode d'annotation spécifiée;
Objet Bean correspondant à un nom spécifique:
bean (): extension Spring AOP, AspectJ n'a pas de méthode d'exécution pour l'indicateur, qui est utilisé pour faire correspondre l'objet Bean avec un nom spécifique;
Pour citer d'autres points d'entrée nommés:
point de référence: signifie faire référence à d'autres points d'entrée nommés, uniquement pris en charge par le style @ApectJ, pas par le style de schéma.
Les indicateurs de coupe de point pris en charge par les coupes de point AspectJ sont: appel, obtention, définition, préinitialisation, initialisation statique, initialisation, gestionnaire, conseil d'exécution, withincode, cflow, cflowbelow, if, @this, @withincode; mais Spring AOP ne prend actuellement pas en charge ces instructions L'opérateur, à l'aide de ces indicateurs, lèvera une exception IllegalArgumentException. Ces indicateurs Spring AOP pourraient être étendus à l'avenir.
1. Nom et point d'entrée anonyme
Les coupes de points nommées peuvent être référencées par d'autres coupes de points, contrairement aux coupes de points anonymes.
Seul @AspectJ prend en charge les points d'entrée nommés, tandis que le style de schéma ne prend pas en charge les points d'entrée nommés.
Comme indiqué ci-dessous, @AspectJ fait référence aux points d'entrée nommés de la manière suivante:
2. Saisissez la syntaxe de correspondance
Commençons par comprendre les caractères génériques de la correspondance de type AspectJ:
*: Correspond à n'importe quel nombre de caractères;
..: faire correspondre n'importe quel nombre de répétitions de caractères, comme faire correspondre n'importe quel nombre de sous-packages en mode type et faire correspondre n'importe quel nombre de paramètres en mode paramètre de méthode.
+: Correspond au sous-type du type spécifié; ne peut être placé comme suffixe qu'après le modèle de type.
Exemples:
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
Ensuite, regardons le type spécifique d'expression correspondante:
Type de correspondance: correspond comme suit
Annotation? Nom complet de la classe
- Annotation: facultative, les annotations contenues sur le type, telles que @Deprecated;
- Nom complet de la classe: obligatoire, peut être le nom complet de n'importe quelle classe.
Implémentation de la méthode d'appariement: utilisez la méthode suivante pour l'appariement:
Annotation? Modificateur? Déclaration du type de valeur renvoyée? Nom de la méthode (liste de paramètres) Liste d'exceptions?
- Annotation: facultative, l'annotation contenue sur la méthode, telle que @Deprecated;
- Modificateur: facultatif, tel que public, protégé;
- Type de valeur de retour: obligatoire, peut être n'importe quel mode de type; "*" signifie tous les types;
- Déclaration de type: facultative, peut être n'importe quel modèle de type;
- Nom de la méthode: obligatoire, vous pouvez utiliser "*" pour la correspondance des modèles;
- Liste des paramètres: "()" signifie que la méthode n'a pas de paramètres; "(..)" signifie les méthodes correspondantes qui acceptent un nombre illimité de paramètres et "(.., java.lang.String)" signifie les méthodes correspondantes qui acceptent les types java.lang.String Le paramètre se termine et la méthode avec un nombre quelconque de paramètres peut être acceptée devant lui; "(java.lang.String, ..)" signifie que la méthode qui correspond au paramètre de type java.lang.String est acceptée et la méthode derrière elle peut accepter n'importe quel nombre de paramètres ; "(*, Java.lang.String)" signifie que la méthode qui accepte les paramètres de type java.lang.String se termine et accepte une méthode avec un paramètre de tout type devant elle;
- Liste d'exceptions: facultative, déclarée avec "lève l'exception liste de noms pleinement qualifiés", s'il existe plusieurs listes de noms entièrement qualifiés d'exceptions, séparées par "," telles que java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException.
Faire correspondre le nom du bean: vous pouvez utiliser l'ID ou le nom du bean pour faire correspondre et vous pouvez utiliser le caractère générique "*";
3. Expressions combinées de point de coupe
AspectJ utilise et (&&), ou (||), et non (!) Pour combiner des expressions pointcut.
Dans le style de schéma, parce que l'utilisation de "&&" en XML doit utiliser le caractère d'échappement "& amp; & amp;" pour le remplacer, il est très gênant, donc Spring AOP fournit et, ou, ne pas remplacer &&, ||, ! .
3.1 Exemples d'utilisation du point d'entrée
3.1.1, execution () : utilisez la méthode d'appariement "execution (expression de méthode)" pour exécuter;
Motif |
La description |
Publique * *(..) |
Exécution de toute méthode publique |
* cn.javass..IPointcutService. * () |
Toute méthode sans paramètre dans l'interface IPointcutService sous le package cn.javass et tous les sous-packages |
* cn.javass .. *. * (..) |
toute méthode de n'importe quelle classe sous le package cn.javass et tous les sous-packages |
* cn.javass..IPointcutService. * (*) |
Le package cn.javass et tous les sous-packages n'ont qu'une seule méthode de paramètre pour l'une des interfaces IPointcutService |
* (! cn.javass..IPointcutService +). * (..) |
Toute méthode autre que "package cn.javass et tous les sous-packages Interface et sous-type IPointcutService" |
* cn.javass..IPointcutService +. * () |
toute méthode sans paramètre de l'interface et des sous-types IPointcutService sous le package cn.javass et tous les sous-packages |
* cn.javass..IPointcut * .test * (java.util.Date) |
Dans le package cn.javass et tous les sous-packages, le type de préfixe IPointcut commence par test et n'a qu'une seule méthode avec un type de paramètre java.util.Date. Notez que la correspondance est basée sur le type de paramètre de la signature de méthode, et non sur l'exécution. Le type du paramètre entrant détermine Tels que la méthode de définition: test public void (Object obj); même si java.util.Date est transmis pendant l'exécution, il ne correspondra pas; |
* cn.javass..IPointcut * .test * (..) jette IllegalArgumentException, ArrayIndexOutOfBoundsException |
Toute méthode du type de préfixe IPointcut sous le package cn.javass et tous les sous-packages, et lève les exceptions IllegalArgumentException et ArrayIndexOutOfBoundsException |
* (cn.javass..IPointcutService + && java.io.Serializable +). * (..) |
Toute méthode qui implémente les types de l'interface IPointcutService et de l'interface java.io.Serializable sous le package cn.javass et tous les sous-packages |
@ java.lang.Deprecated * * (..) |
Toute méthode avec annotation @ java.lang.Deprecated |
@ java.lang.Deprecated @ cn.javass..Secure * * (..) |
Toute méthode avec @ java.lang.Deprecated et @ cn.javass..Secure annotations |
@ (java.lang.Deprecated || cn.javass..Secure) * * (..) |
Toute méthode avec @ java.lang.Deprecated ou @ cn.javass..Secure annotation |
(@ cn.javass..Secure *) * (..) |
Tout type de valeur de retour contenant @ cn.javass..Secure, méthode |
* (@ cn.javass..Secure *). * (..) |
Tout type qui définit une méthode contient la méthode @ cn.javass..Secure |
* * (@ cn.javass..Secure (*), @ cn.javass..Secure (*)) |
Toute méthode avec deux paramètres dans la signature, et ces deux paramètres sont marqués par @ Secure, 如 test de vide public (@Secure String str1, @Secure String str1); |
* * ((@ cn.javass..Secure *)) ou * * (@ cn.javass..Secure *) |
Toute méthode avec un paramètre et le type de paramètre contient @ cn.javass..Secure; Tels que le test de vide public (modèle Model) et l'annotation @Secure sur la classe Model |
* * ( @ 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文件中没生成变量调试信息是获取不到方法参数名字的。
所以我们可以使用策略来确定参数名:
- 如果我们通过“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只包含一个通知和一个切入点。