AspectJ编程学习笔记
名词 | 含义 |
切面(方面,Aspect) | 一个关注点的模块化,这个关注点实现可能横切(crosscutting)多个对象切面的例子包括:事务控制、日志记录、权限控制等在AspectJ中,切面表现为Java类,其源码具有AspectJ的特殊语法增强,使用ajc编译器编译 |
连接点(Joinpoint) | 程序执行过程中明确的点,例如方法的调用开始、结束,或者特定的异常被抛出,横切(crosscutting)在连接点发生 |
通知(Advice) | 在特定的连接点,AOP框架执行的操作 |
切入点(Pointcut) | 切入点是这样的一种程序构造:包含一个连接点的集合、以及收集的连接点的上下文信息(例如方法参数、被执行方法和对象)。通知在这些切入点被触发。AOP框架允许开发者以多种方式定义切入点 |
引入(Introduction) | 添加方法、字段、接口、注解等到被通知的类 |
目标对象(Target Object) | 包含连接点的对象,也被称作被通知或被代理对象 |
AOP代理(AOP Proxy) | AOP框架创建的对象,以目标对象为基础,织入了通知逻辑代理主要包括动态代理、字节码增强两种类型 |
- AspectJ编译器能识别任何普通的Java代码,可以使用ajc编译.java文件
- 织入方式:
源代码织入 织入器作为编译器的一部分,处理源代码,支持经典语法和注解语法。生成的字节码符合JVM规范,需要使用ajc代替javac 字节码织入 传递给织入器的是字节码。使用这种方式时,包含编译普通Java类、编译切面,织入3个步骤。 加载时织入 传递给织入器的是Java类字节码、切面类,以及aop.xml配置文件。 - AspectJ暴露的连接点。只能在这些连接点上应用通知:
暴露的连接点 代码表现 方法 执行
Execution方法体:该连接点环绕整个方法体的执行过程。这也是Spring AOP唯一支持的接入点类型。通知被织入方法体
切入点语法:execution(方法签名)调用
Call方法调用:该接入点发生在方法被调用时。通知被织入调用者代码处
切入点语法:call(方法签名)构造器 执行
Execution对象构造逻辑:该连接点环绕整个构造器的执行过程
切入点语法:execution(构造器签名)调用
Call对象构造调用
切入点语法:call(构造器签名)字段访问 读访问
Read access读取类或对象的字段
切入点语法:get(字段签名)写访问
Read access写入类或对象的字段
切入点语法:set(字段签名)异常处理 处理器
Handler处理异常的catch块
切入点语法:handler(类型签名)初始化 类初始化
Class initialization类加载,包括静态成员初始化部分
切入点语法:staticinitialization(类型签名)对象初始化
Object initialization在构造器中的对象初始化。包含:从一个父构造器返回,到当前构造器执行完成的部分(不包括调用父构造器的部分)
切入点语法:initialization(构造器签名)
Spring的@Configurable即为此例 。下面的例子解释了连接点的范围:
123456789101112131415161718192021public class SavingsAccount extends Account{private boolean isOverdraft;private int minimumBalance;//构造器1public SavingsAccount( int accountNumber,boolean isOverdraft ){super( accountNumber );//对象初始化连接点:构造器1,2this.isOverdraft = isOverdraft;}//构造器2public SavingsAccount( int accountNumber ){//对象初始化连接点:构造器2this( accountNumber, false );//对象初始化连接点:构造器2this.minimumBalance = 25;}}对象预初始化
pre-initialization在构造器中的对象初始化之前:用的很少,从当前构造器调用,到父构造器调用结束为止
切入点语法:preinitialization(构造器签名)通知 执行
Execution通知的执行:环绕某个通知的整个执行。可以对通知进行通知
切入点语法:adviceexecution() - AspectJ切入点声明语法
123456789/*** 切入点声明语法,包含5个部分:* 访问标识符:例如 public* 关键字:pointcut* 切入点名称()* 切入点类型:例如execution、call,参考:第3点* 切入点签名:根据切入点类型不同,可能是类型签名、方法签名、字段签名*/public pointcut secureAccess(): execution(* MessageCommunicator.deliver(..));AspectJ支持仅有切入点名称,而没有类型和签名的切入点,通常使用在抽象切面中。
AspectJ支持匿名切入点,作为通知签名的一部分存在。
AspectJ支持使用 !、 && 、|| 来作为切入点的运算符 - AspectJ通知定义语法:由通知声明、切入点定义、通知体三部分组成
组成 说明 整体语法形式:
1234[@AdviceName("通知的名称")][返回值] 通知类型([上下文信息]) [returning|throwing] [throws 通知抛出的异常] : 切入点定义([上下文信息]) {通知体}通知声明 通知类型可以是:before, after 或 around,其中around需要声明返回值通知声明可以指定通知体可用的上下文信息,包括:执行对象通知声明可以指定通知体可能抛出的异常 切入点定义 位于冒号之后,任何匹配切入点定义的连接点上都会执行本通知 通知体 可以访问一系列特殊的变量或关键字,例如:proceed、thisJoinPoint、thisJoinPointStaticPart、 thisEnclosingJoinPointStaticPart - AspectJ支持的通知类型
通知类型 说明 Before advice 在连接点执行之前执行,如果抛出异常,那么连接点不被执行 After advice 在连接点执行之后执行,根据连接点的执行结果,具有3个子类型:After (finally):在连接点执行之后执行,不论其结果,通知声明语法:after()After returning:在连接点执行成功后执行,通知声明语法:after() returning(<ReturnType returnObject>)After throwing:在连接点执行失败后执行,通知声明语法:after() throwing(<ExceptionType exceptionObject>) Around advice 环绕连接点的执行过程,具有修改连接点执行上下文的能力,可以用来: - 在连接点之前/之后添加额外的逻辑,例如性能分析
- 跳过原先逻辑还执行备选的逻辑,例如缓存。只要不调用proceed(),即跳过
- 使用try-catch包裹原先逻辑,提供异常处理策略,例如事务管理
通配符用于匹配一系列的连接点。
* | 匹配点号.范围内的任意数目的字符在类型签名中:表示部分包名或者部分类名在其他地方:表示部分的方法或者字段名 |
.. | 匹配任意数目的字符,包括任意数量的点号在类型签名中:表示任意直接、间接的子包在方法签名中:表示任意数量的方法参数 |
+ | 作为类型签名的后缀,表示包含任意的子类型 |
这里的类型泛指:类、接口、注解、切面、基本类型
基本类型签名示例:
签名 | 说明 |
Account | 仅匹配Account类型 |
*Account | 匹配任何类名以Account结尾的类型 |
java.*.Date | 匹配java的直接子包中的Date |
java..* | 匹配java包及其任意子包中的任意类型 |
javax..*Model+ | 匹配java包及其任意子包中的任意以Model结尾的类型,及其子类型 |
基于注解的类型签名示例:
由于Java不支持注解的继承,所有注解名后不会出现加号。注意注解必须运行时可见(
RetentionPolicy.RUNTIME )
签名 | 说明 |
@Secured Account | 标注了@Secured注解的Account类型 |
@Sensitive * | 标注了@Sensitive注解的任何类型 |
@Business* Customer+ | Customer及其子类型,且标注了Business开头的注解 |
基于泛型的类型签名示例:
签名 | 说明 |
Map<Long,Account> | 仅仅匹配Map<Long,Account>类型 |
*<Account> | 任何把Account作为其泛型参数的类型 |
Collection<@Sensitive *> | Collection类型,其泛型参数类必须被标注了@Sensitive |
Collection<? extends Account> | Collection类型,其泛型参数类必须是Account或者其子类 |
Collection<? super Account> | Collection类型,其泛型参数类必须是Account的父类 |
联合类型签名示例:
签名 | 说明 |
!Collection | 除了Collection的任何类型 |
Collection || Map | Collection或者Map类型 |
java.util.RandomAccess+ && java.util.List+ | 任何同时实现了这两个接口的子类,例如ArrayList |
!@Secured * | 任何没有标注@Secured 的类型 |
@Secured @Sensitive * | 任何同时标注了@Secured @Sensitive的类型 |
@(Secured || Sensitive) * | 任何标注了@Secured @Sensitive之一的类型 |
构造器签名与方法类似,但是:
- 由于构造器没有返回值,因此不得在签名中指定返回值
- 由于构造器不能为静态,故不得添加static关键字
- 由于构造器没有名称,必须使用new作为其名称部分
签名 | 说明 |
public void Account.set*(*) | Account类中,包含单个参数、返回类型为void、以set开头的公共方法 |
public void Account.*() | Account类中,任何返回类型为void、没有参数的公共方法 |
public * Account.*() | Account类中,任何没有参数的公共方法 |
public * Account.*(..) | Account类中,包含任意参数(包括0参)的公共方法 |
* Account.*(..) | Account类中,包含任意参数(包括0参)的方法 |
* *.*(..)* *(..) | 任意方法 |
!public * Account.*(..) | 任何Account类的非公共方法 |
* *(..) throws SQLException | 任何声明抛出SQLException的方法 |
* Account+.*(..) | Account及其子类的任何方法 |
* java.io.Reader.read(char[],..) | java.io.Reader类的read方法,第一个参数必须为char[] |
* javax..*.add*Listener(EventListener+) | javax的任意子包中的类的方法,必须以add开头、Listener结尾,且包含唯一的参数EventListener类型及其子类型 |
* java.io.PrintStream.printf(String,Object...) | java.io.PrintStream的printf方法,第一个参数为字符串,第二个参数为Object... |
Account AccountService.*(..) | AccountService类中任何返回AccountService的方法 |
public Account.new() | 匹配Account类的0参公共构造器 |
基于注解的方法签名示例:
注意注解必须运行时可见( RetentionPolicy.RUNTIME )
签名 | 说明 |
@Secured * *(..) | 任何标注了@Secured的方法 |
@Secured @Transactional * *(..) | 任何同时标注了@Secured、@Transactional 的方法 |
@(Secured || Transactional) * *(..) | 任何标注了@Secured或者@Transactional 的方法 |
(@Sensitive *) *(..) | 任何返回值类型上标注了@Sensitive的方法 |
* (@BusinessEntity *).*(..) | 任何标注了@BusinessEntity的类的任何方法 |
* *(@RequestParam (*)) | 任何包含了单个参数,且参数上标注了@RequestParam 的方法,例如:void show(@RequestParam Long id) |
* *(@Sensitive *)* *((@Sensitive *)) | 任何包含了单个参数,且参数的类型上标注了@RequestParam 的方法,例如:void create(ClassAnnotatedWithSensitive obj) |
* *(@RequestParam(@Sensitive *)) | void create(@RequestParam ClassAnnotatedWithSensitive obj) |
字段签名中,使用get表示读字段,set表示写字段:
字段签名示例:
注意注解必须运行时可见( RetentionPolicy.RUNTIME )
签名 | 说明 |
private double Account.balance | Account类的私有double类型的balance字段 |
* Account.* | Account类的任何字段 |
* Account+.* | Account类及其子类的任何字段 |
@Sensitive * * | 字段上标注了@Sensitive的任何字段 |
(@Sensitive *) *.* | 字段的类型上标注了@Sensitive的任何字段 |
* (@Sensitive *).* | 标注了@Sensitive的类的任何字段 |
AspectJ提供几种切入点指示器(designators),配合上面所述的切入点签名,即组成切入点。
切入点通过两种方式与连接点进行匹配:
- 类型限定的切入点:直接映射到某个类型的连接点,参考AspectJ暴露的连接点一节中的“切入点语法”部分
- 无类型的切入点:根据指定的信息,来选择特定的连接点。这些信息可以是:连接点上下文的运行时类型、控制流,或者此法作用域(lexical scope)
匹配:指定类型和签名的连接点
Spring:仅支持execution类型
静态确定:是
不去限定连接点的类型,还是通过若干种规则去匹配多种连接点。
Spring虽然仅仅支持execution一种类型的连接点,但是却支持多种方式来选择它们
基于控制流的切入点
这一类的切入点匹配 处于其他切入点的控制流之中 的连接点。这句话比较难以理解,以贷款业务为例,假设Account.debit()方法调用Account.getBalance()进行余额查询,那么,可以说:getBalance在debit的控制流之中。类似的,字段访问、异常处理,也可以处于某个切入点的控制流之中。
Spring不支持这一类切入点,不能静态确定(需要运行时判断)
包含两种切入点类型:cflow、cflowbelow,后者不包括启动控制流的连接点本身
切入点 | 说明 |
cflow(execution(* Account.debit(..))) | 匹配所有这样的连接点:位于Account的任何debit方法的控制流之中,包括debit方法本身 |
cflowbelow(execution(* Account.debit(..))) | 匹配所有这样的连接点:位于Account的任何debit方法的控制流之中,不包括debit方法本身 |
cflow(execution(@Transactional * *(..))) | 匹配所有位于配置@Transactional方法的控制流之中的任何连接点 |
cflow(transacted()) | 匹配切入点transacted所选择的任何连接点的控制流之中的连接点 |
下面以事务管理的场景为例,来说明控制流切入点的用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package cc.gmem.aj.cflow; public class Account { //两个方法均具有事务注解,但是,我们需要在“最外层”来启动事务 //需要忽略内层的事务注解 @Transactional public void debit() { getBalance(); } @Transactional public void getBalance() { } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public aspect TransactionAspect { //匹配所有标注了@Transactional的方法执行 private pointcut transacted() : execution(@Transactional * *(..)); //匹配这样的@Transactional方法:不在@Transactional方法的所确定的控制流的内部 private pointcut topLevelTransacted() : transacted() && !cflowbelow(transacted()); Object around() : topLevelTransacted() { System.out.println( "启动事务" ); Object ret = proceed(); System.out.println( "结束事务" ); return ret; } } |
基于词法结构的切入点
匹配:在指定类型、方法、构造器内的连接点
Spring:支持within(),不支持withincode()
可以静态确定
包含两种切入点类型:within(类型签名)、withincode(构造器签名|方法签名)
切入点 | 说明 |
within(Account) | Account类,及其嵌套类中的任何连接点 |
within(Account+) | Account类(或者子类),及其嵌套类中的任何连接点 |
within(@javax.persistence.Entity *) | 标注了的@Entity类,及其嵌套类中的任何连接点 |
withincode(* Account.debit(..)) | Account的debit方法中的任何连接点 |
执行对象切入点
根据运行时对象类型来选择连接点。并且,可以收集执行对象——双重功能。
包含两种类型:this(<Type or ObjectIdentifier>)、target(<Type or ObjectIdentifier>),前者匹配this引用为指定类型的连接点;后者匹配方法调用target为指定类型的连接点。
该类切入点不支持类型通配,也不支持泛型。该类切入点不会匹配任何静态方法的执行连接点。
切入点 | 说明 |
this(Account) | 任何连接点,只要表达式 this instnaceof Account为真,即匹配 |
target(Account) | 任何(通常是方法调用类型)连接点,只要被调用的方法所属对象 instanceof Account,即匹配 |
注意区别:
execution(* Account.*(..)) 匹配Account类的任何静态、成员方法
execution(* *.*(..)) && this(Account):匹配Account及其子类的任何成员方法
参数切入点
该类切入点根据连接点的参数对象的运行时类型来匹配。并且,可以收集匹配的参数对象。
Spring支持此类切入点。无法静态确定。
所谓参数对象,根据连接点不同,含义也不同:
- 对于方法、构造器连接点,参数对象即方法参数
- 对于异常处理连接点,参数对象即被处理的异常
- 对于字段写入连接点,参数对象即被写入的值
该类切入点的形式为:args(Type or ObjectIdentifier, ..)
切入点 | 说明 |
args(Account, .., int) | 任何方法或者构造器,第一个参数的运行时类型为Account或其子类型,最后一个参数的运行时类型为int |
args(RemoteException) | 任何具有单个RemoteException类型参数的连接点。 |
基于注解的切入点
注意:注解本身被标注的注解影响AOP行为:
- @Retention(RetentionPolicy.RUNTIME):只有设置为运行时保留,才能在运行时进行匹配性判断
- @Inherited:只有标注了此注解,子编程元素(类、方法)才能继承父元素上标注的注解
AspectJ支持根据类型、方法、字段所标注的注解来匹配连接点
切入点 | 说明 |
@this(TypePattern or ObjectIdentifier) | 任何连接点,只要this的类上具有TypePattern指定的注解注意:如果注解是@Inherited的,父类上有指定注解即可 |
@target(TypePattern or ObjectIdentifier) | 任何连接点,只要target的类上具有TypePattern指定的注解 |
@args(TypePattern or ObjectIdentifier, ..) | 任何连接点,只要参数的类上具有TypePattern指定的注解 |
@within(TypePattern or ObjectIdentifier) | 任何连接点,只要在具有TypePattern指定的注解的类型的词法作用域内 |
@withincode(TypePattern or ObjectIdentifier) | 任何连接点,只要在具有TypePattern指定的注解的构造器或方法的词法作用域内 |
@annotation(TypePattern or ObjectIdentifier) | 任何连接点,只要目标上具有指定的注解目标的含义:
|
条件检查切入点
此类切入点在连接点上的一些条件检查。通常和其它切入点联合使用
切入点 | 说明 |
if(debug) | 任何debug静态字段(位于切面类)设置为true的连接点 |
if(System.currentTimeMillis() > triggerTime) | 任何发生在triggerTime以后的连接点 |
if(circle.getRadius() < 5) | 任何连接点,只要circle的radius小于5。circle必须是被收集的上下文对象,或者切面的静态字段。 |
本段代码示意经典的AspectJ语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package cc.gmem.aj; //切面的声明 public aspect SecurityAspect { private Authenticator auth = new Authenticator(); //切入点的声明 pointcut secureAccess(): execution(* MessageCommunicator.deliver(..)); //通知声明:针对secureAccess切入点的before通知 before() : secureAccess() { System.out.println( "Checking and authenticating user" ); auth.doAuthenticate(); } } |
本段代码示意基于注解的AspectJ语法:
基于注解的语法,可以作为普通Java类编译,并且使用LTW(加载时织入)机制,在类被加载到JVM中时进行织入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package cc.gmem.aj; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; @Aspect public class SecurityAspect { private Authenticator auth = new Authenticator(); @Pointcut ( "execution(* cc.gmem.aj.MessageCommunicator.deliver(..))" ) public void secureAccess() { } @Before ( "secureAccess()" ) public void secure() { System.out.println( "Checking and authenticating user" ); auth .doAuthenticate(); } } |
环绕通知的简单例子,注意proceed()的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public aspect SecurityAspect { private Authenticator auth = new Authenticator(); pointcut secureAccess(): execution(* MessageCommunicator.deliver(..)); Object around() : secureAccess() { long start = System.nanoTime(); //伪关键字proceed,用于继续执行被通知的目标方法 Object ret = proceed(); //thisJoinPointStaticPart是通知可用变量之一,可以获取连接点的静态信息,例如方法名 System.out.println( thisJoinPointStaticPart.getSignature() + " took " + ( end - start ) + " nanoseconds" ); return ret; } } |
上下文收集的简单例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package cc.gmem.aj; import java.sql.Connection; import java.sql.SQLException; public aspect ConnectionAspect { //匹配Connection的任何抛出SQLException方法的调用 //被调用(target)的Connection对象收集为connection变量。 pointcut connectionOperation( Connection connection )//独立定义的切入点,必须使用这种参数声明来传递上下文信息给通知 : call(* Connection.*(..) throws SQLException) && target(connection); Object around( Connection connection ) : connectionOperation(connection) {//传递了上下文 long startTime = System.nanoTime(); Object ret = proceed(); //thisJoinPoint是通知体可用的变量之一 System.out.println( "Operation " + thisJoinPoint + " on " + connection + " took " + ( System.nanoTime() - startTime ) ); return ret; } } |
上下文收集注解的例子:
1 2 3 4 5 6 7 8 9 |
before(Secured secured ) : //匹配任何方法执行 execution(* *(..)) //匹配连接点目标————即方法本身————上具有@secured注解的连接点 && @annotation(secured) { //注解可以像普通对象一样被使用 checkPermission(secured.permission()); } |
上下文收集的另外一个例子,注意传递给目标的参数被收集,可被通知体使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package cc.gmem.aj; public aspect AccountAspect { boolean processOverdraft( Account account, float amount ) { return true; } void around( Account account, float amount ) throws InsufficientBalanceException //匹配任何对Account.debit(float) throws InsufficientBalanceException方法的调用 : call(void Account.debit(float) throws InsufficientBalanceException) && target(account) && args(amount)//收集的上下文信息作为局部变量使用 { try { proceed( account, amount );//传递给目标方法的参数,可以任意修改 } catch ( InsufficientBalanceException ex ) { //进行透支处理 if ( !processOverdraft( account, amount ) ) { throw ex; } } } } |
在classpath的META-INF/aop.xml中,声明需要织入的切面:
1 2 3 4 5 |
<aspectj> <aspects> <aspect name="cc.gmem.aj.SecurityAspect"/> </aspects> </aspectj> |
通过指定JVM启动参数-javaagent来启用LTW:
1 |
-javaagent:%ASPECTJ_HOME%\lib\aspectjweaver.jar |
如果使用Spring,可以通过配置,避免修改JVM启动参数
AspectJ支持连接点、切入点、切面等构造。
AspectJ的动态横切构造依赖于“通知”实现。通知包括Before、After、Around几种。
收集连接点上下文
连接点上下文包括两种类型:
连接点牵涉的对象 | 切入点:this(), target(), args()可以用于收集相应的对象可以使用类型(Type)或者对象名称(ObjectIdentifier)两种方式来收集,对于后者,必须在通知声明处声明相应的对象 |
连接点关联的注解 | 切入点:@this(), @target(), @args(), @annotation(),@within(), @withincode()可用于收集相应的注解 |
通过反射访问连接点上下文
作为备选方式,AspectJ提供了基于反射机制的访问静态(不会随着连接点执行而变化的)、动态连接点上下文信息的方法。这种方式可以完全替换上一条进行动态上下文收集,但是具有以下缺点:
- 性能较为低下
- 缺少静态语法检查
- 比较笨重
通过AspectJ提供的通知体可用的特殊变量来使用这种反射API:
变量 | 说明 |
thisJoinPoint | 变量类型为JoinPoint。可以访问目标对象、执行对象、参数的信息,包括相应的注解信息。可以通过getStaticPart()方法访问静态信息 |
thisJoinPointStaticPart | 变量类型为JoinPoint.StaticPart。可以访问源码位置、连接点类型、连接点签名。 |
thisEnclosingJoinPointStaticPart | 变量类型为JoinPoint.StaticPart。用于访问包围的连接点静态上下文,所谓“包围的连接点”依连接点类型而不同,例如:
|
两种访问连接点上下文的方式的对照表
切入点语法 | 反射方式 |
pointcut pc(Account acc) : this(acc) | Account acc = (Account)thisJoinPoint.getThis() |
pointcut pc(Account acc): target(acc) | Account acc = (Account)thisJoinPoint.getTarget() |
pointcut pc(Account acc,Customer cust): args(acc, cust) | Object[] arguments = thisJoinPoint.getArgs(); Account acc = (Account)arguments[0]; Customer cust = (Customer)arguments[1]; |
poincut pc(Secure sec) : @this(annot); | Secure sec = thisJoinPoint.getThis().getClass(). getAnnotation(Secure.class) |
poincut pc(Secure sec) : @target(sec); | Secure sec = thisJoinPoint.getTarget().getClass(). getAnnotation(Secure.class); |
pointcut pc(Secure sec, Transactional tx) : @args(sec, tx); | Object[] arguments = thisJoinPoint.getArgs(); Secure sec = arguments[0].getClass(). getAnnotations(Secure.class); Transactional tx = arguments[1].getClass(). getAnnotations(Transactional.class); |
poincut pc(Sensitive sens) : @annotation(sec); | FieldSignature sig = (FieldSignature)thisJoinPointStaticPart.getSignature(); Sensitive sens = sig.getField().getAnnotation(Sensitive.class); |
pointcut pc(Secure sec) : @within(sec); | thisJoinPointStaticPart.getSignature(). getDeclaringType().getAnnotation(Secure.class); |
pointcut pc(Secure sec) : @withincode(sec); | MethodSignature sig = (MethodSignature)thisJoinPointStaticPart.getSignature(); Secure sec = sig.getMethod().getAnnotation(Secure.class); |
静态横切总是在动态横切之前应用。
静态横切构造由:跨类型声明(inter-type declaration ,ITD,亦称“引入introduction)”)和织入时声明(weave-time declarations)组成
跨类型声明(引入)
可以修改类、接口、或者切面的静态结构,例如增加一个字段,添加一个接口。在一个切面中,声明其他类型的结构,故曰ITD。下面是一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package cc.gmem.aj; public aspect TrackingAspect { //声明MessageCommunicator实现接口AccessTracked,效果和Java标准语法一样 declare parents: MessageCommunicator implements AccessTracked; //声明AccessTracked类型的一个字段,所有它的子类型自动获取此字段 private long AccessTracked.lastAccessedTime; //声明AccessTracked类型的两个方法,所有它的子类型自动继承这些方法 public void AccessTracked.updateLastAccessedTime() { lastAccessedTime = System.currentTimeMillis(); } public long AccessTracked.getLastAccessedTime() { return lastAccessedTime; } before( AccessTracked accessTracked ) : //声明了一个通知局部变量accessTracked execution(* AccessTracked+.*(..)) //AccessTracked子类型的任何方法 && !execution(* AccessTracked.*(..))//除了AccessTracked本身的方法 && this(accessTracked)//this对象收集为accessTracked变量 { accessTracked.updateLastAccessedTime(); } private static interface AccessTracked { } } |
成员引入的规则
- 只能引入private、public成员,前者只能被切面类访问,后者可以被系统中所有类访问
- 如果是private的,多个切面可以引入同名的成员
- 切面可以引入字段、函数、构造器到类或者接口。特别的,可以引入方法的实现到接口——缺省实现
- 如果切面引入一个和类中同名的方法,那么,类中的那个方法被保留
- 成员引入只能针对一个类型
接口缺省实现的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package cc.gmem.aj; public interface Nameable { public void setName( String name ); public String getName(); //缺省实现 static aspect Impl { private String Nameable.name; public void Nameable.setName( String name ) { this.name = name; } public String Nameable.getName() { return this.name; } } } |
使用ITD来实现Java的Mixin机制
Mixin是一种程序构造,允许在已有类中混入特定的逻辑,Java语言没有语言级别的机制,下面的例子示意了如何使用AspectJ进行混入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//一个标记性接口,用于混入功能 public interface BeanSupport {} //一个简单的POJO,需要混入:属性变更监听的功能 public class Customer implements BeanSupport { private String address; public String getAddress() { return address; } public void setAddress( String address ) { this.address = address; } } |
通过下面的代码,即可为Customer或者任何实现了BeanSupport的类型混入属性变更的功能,而没有任何的侵入性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
public aspect BeanMakerAspect { private PropertyChangeSupport BeanSupport.propertyChangeSupport; public void BeanSupport.addPropertyChangeListener( PropertyChangeListener listener ) { propertyChangeSupport.addPropertyChangeListener( listener ); } public void BeanSupport.removePropertyChangeListener( PropertyChangeListener listener ) { propertyChangeSupport.removePropertyChangeListener( listener ); } pointcut beanCreation( BeanSupport bean ) //匹配BeanSupport及其子类型的初始化连接点 : initialization(BeanSupport+.new(..)) && this(bean); pointcut beanPropertyChange( BeanSupport bean, Object newValue ) //匹配BeanSupport及其子类型的任何Setter方法的执行 : execution(void BeanSupport+.set*(*)) && args(newValue) && this(bean); //BeanSupport初始化成功后,创建一个propertyChangeSupport字段的实例 after( BeanSupport bean ) returning : beanCreation(bean) { bean.propertyChangeSupport = new PropertyChangeSupport( bean ); } //Setter调用后,发布相应的事件 void around( BeanSupport bean, Object newValue ) : beanPropertyChange(bean, newValue) { String methodName = thisJoinPointStaticPart.getSignature().getName(); String propertyName = Introspector.decapitalize( methodName.substring( 3 ) ); Object oldValue = getPropertyValue( bean, propertyName ); proceed( bean, newValue ); bean.propertyChangeSupport.firePropertyChange( propertyName, oldValue, newValue ); } } |
修改类层次结构
AspectJ提供两种需要类层次的形式:
1 2 3 4 5 6 7 8 |
declare parents : [TypePattern] implements [InterfaceList]; declare parents : [TypePattern] extends [Class or InterfaceList]; //举例:所有实体类实现BeanSupport接口 public aspect EntityBeanParticipationAspect { declare parents: @Entity * implements BeanSupport; } //hasMethod、hasField,任何包含指定模式方法或者字段的类: declare parents: hasmethod(@Observed * *(*)) implements BeanSupport; |
引入注解
AspectJ支持为字段、类、方法等添加注解
语法形式 | 说明 |
declare @method: <Method signature pattern>:<Annotation>; | 引入注解给AccountService的任何方法,例如:declare @method: * AccountService.*(..): @Transactional(Propagation.Required); |
declare @constructor: <Constructor signature pattern>:<Annotation>; | 引入注解给构造器,例如:declare @constructor: AccountService+.new(): @ConfigurationOnly; |
declare @field: <Field signature pattern>:<Annotation>; | 引入注解给字段,例如:declare @field: * MissileInformation.*: @Classified; |
declare @type: <Type signature pattern>:<Annotation>; | 引入注解给类型,例如:declare @type: banking..* : @PrivacyControlled; |
简化异常处理
允许不强制捕获受查异常,声明方式如下:
1 2 |
//这里声明不需要强制捕获的异常 declare soft : <ExceptionTypePattern> : <pointcut>; |
织入时声明——警告或者错误信息
可以在织入时,根据一定的模式来生成错误或者警告信息,举例:
1 2 3 4 5 6 |
//当doAuthenticate方法在SecurityAspect以外调用时,生成警告 declare warning: call(void Authenticator.doAuthenticate()) //切入点:方法被调用的地方,与execution不同 && !within(SecurityAspect) : //切入点,不在SecurityAspect内 "Authentication should be performed only by SecurityAspect"; |
这种场景下,本质上还是Spring基于JDK动态代理的AOP机制,只是借用AspectJ的语法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd "> <!-- 提示Spring自动为目标对象创建代理 --> <aop:aspectj-autoproxy /> <!-- 声明普通Bean --> <bean id="messageCommunicator" class="cc.gmem.aj.MessageCommunicator" /> <!-- 声明切面Bean,注意,必须使用注解语法定义切面 --> <bean id="securityAspect" class="cc.gmem.aj.SecurityAspect" /> </beans> |
可以基于AspectJ,而不是Spring AOP来实现:事务管理、缓存、任务调度等常用AOP场景,以避免Spring AOP的固有缺陷。
特别是使用@Configurable注解来支持非Spring Bean的依赖注入,必须基于AspectJ完成。
1 2 3 4 5 6 7 8 |
<!-- 支持非受管对象(Spring Bean)的依赖注入和生命周期注解 --> <context:spring-configured /> <!-- 支持基于AspectJ的事务管理 --> <tx:annotation-driven transaction-manager="txManager" mode="aspectj" /> <!-- 支持基于AspectJ的缓存机制--> <cache:annotation-driven cache-manager="cacheManager" mode="aspectj" /> <!-- 支持基于AspectJ的任务调度和执行 --> <task:annotation-driven scheduler="taskScheduler" executor="taskExecutor" mode="aspectj" /> |
"如果使用Spring,可以通过配置,避免修改JVM启动参数" 怎么弄?原理是什么