使用@AspectJ定义一个切面:
package com.ivy.annotation.aspectj; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class PreGreetingAspect { @Before("execution(*greetTo(..))") public void beforeGreeting() { System.out.println("How are you"); } }
第三方处理程序通过类中是否拥有@Aspect注解判断其是否为一个切面。@Before注解表示该增强是前置增强,成员值是一个切点表达式,意思是在目标类的greetTo()方法上织入增强,greetTo()方法可以带任意的入参和任意的返回值。而beforeGreeting()方法就是增强所使用的横切逻辑,该横切逻辑在目标方法前调用。
- 编程方式织入
ProxyFactory还有另一个兄弟,AspectJProxyFactory,通过AspectJProxyFactory可以实现Aspect定义到目标对象的织入。例如:为NaiveWaiter生成织入PreGreetingAspect切面的代理:
package com.ivy.annotation.aspectj; import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; import com.ivy.aop.advice.NaiveWaiter; import com.ivy.aop.advice.Waiter; public class AspectJProxyTest { public static void main(String[] args) { // TODO Auto-generated method stub Waiter target = new NaiveWaiter(); AspectJProxyFactory factory = new AspectJProxyFactory(); factory.setTarget(target); factory.addAspect(PreGreetingAspect.class); Waiter proxy = factory.getProxy(); proxy.greetTo("John"); proxy.serveTo("John"); } }
首先AspectProxyFactory设置了目标对象,之后添加一个切面类,该类必须是带@Aspect注解的类,然后就可以获取织入了切面的代理对象了。
运行结果:
log4j:WARN No appenders could be found for logger (org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory). log4j:WARN Please initialize the log4j system properly. How are you greet to John... serve to John...
2. 自动代理织入
通过配置使用@AspectJ切面
一般情况都是通过Spring的配置完成切面织入的工作:
<?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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <aop:aspectj-autoproxy proxy-target-class="true">
</aop:aspectj-autoproxy>
<bean id="waiter" class="com.ivy.aop.advice.NaiveWaiter"/> <bean class="com.ivy.annotation.aspectj.PreGreetingAspect"/> </beans>
- @AspectJ形式的Pointcut
@AspectJ形式的Pointcut声明,依附在@Aspect所标注的Aspect定义类之内,通过Pointcut注解,指定AspectJ形式的Pointcut表达式之后,将这个指定了相应表达式的注解标注到Aspect定义类的某个方法上即可。
Pointcut表述语言中有以下几种可用标志符:
execution
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?), 其中方法的返回类型/方法名以及参数部分的匹配模式是必须指定的,其他部分的匹配模式可以省略。
execution中使用两种通配符,即*和..。例如 execution(public void Foo.doSomething(String))
- * 可以用于任何部分的匹配模式中,可以匹配相邻的多个字符,即一个Word。所以可以简化为execution(* *(String))
- ..可以在两个位置使用,一个在declaring-type-pattern规定的位置,一个在方法参数匹配模式的位置。例如
execution(void cn.spring21.*.doSomething(*)) // 只能指定到cn.spring21这一层下的所有类
execution(void cn.spring21..*.doSomething(*)) // 可以匹配cn.spring21包下的所有类,以及cn.spring21下层包下声明的所有类型
如果..用于方法参数列表匹配位置,则表示该方法可以有0到多个参数,参数类型不限。如下:
execution(void *.doSomething(..))
within
within标志符只接受类型声明,它将会匹配指定类型下的所有Joinpoint。不过,因为SpringAoP只支持方法级别的Joinpoint,所以在我们为within指定某个类后,它将匹配指定类所声明的所有方法执行。
切点表达式函数
切点表达式由关键字和操作参数组成,Spring支持9个切点表达式函数,5个类型:
- 方法切点函数
execution(): 方法匹配模式串,execution(* greetTo(..))表示所有目标类的greetTo()方法
@annotation(): 方法注解类名,@annotation(com.ivy.anno.NeedTest)表示任何标注了@NeedTest注解的目标类
2. 方法入参切点函数
args(类名): args(com.ivy.advice.Waiter)表示所有有且仅有一个按类型匹配于Waiter入参的方法
@args(类型注解类名): @args(com.ivy.Monitorable)表示任何这样的一个目标方法:它有一个入参且入参对象的类标注@Monitorable注解
3. 目标类切点函数
within(类名匹配串): within(com.ivy.service.*)表示com.ivy.service包中的所有连接点,即包中所有类的所有方法。而within(com.ivy.service.*Service)表示com.ivy.service包中所有以Service结尾的类的所有连接点。
target(类名): ,target(com.ivy.Waiter)定义的切点,Waiter以及Waiter实现类NaiveWaiter中所有连接点都匹配该切点
@within(类型注解类名): @within(com.ivy.Monitorable)定义的切点,假如Waiter类标注了@Monitorable注解,则Waiter以及NaiveWaiter类的所有连接点都匹配
4. 目标类切点函数
@target(类型注解类名): @target(com.ivy.Monitorable),假如NaiveWaiter标注了@Monitorable,则NaiveWaiter所有连接点匹配切点。
5. 代理类切点函数
this(类名): 代理类按类型匹配于指定类,则被代理的目标类所有连接点匹配切点。
注意:
所有以@开头的标志符,都只能指定注解类型参数,而且,注解只有Java 5及之后才有效。
不同增强类型
@Before 前置增强
@AfterReturning 后置增强
@Around 环绕增强
@AfterThrowing 抛出增强
@After Final增强,不管是抛出异常或是争抢退出,该增恰国内都会得到执行,该增强没有对应的增强接口,一般用于释放资源。
@DeclareParents 引介增强
示例:
@Aspect public class MockAspect { @Pointcut("execution(* destroy(..))") public void destroy(){} @Before("execution(public void *.methodName(String))") public void setUpResourceFolder() { ... } @After("destroy()") public void cleanUpResourceIfNeccessary() { ... } }
切点复合运算
package com.ivy.annotation.aspectj; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Before; public class TestAspect { // 匹配com.ivy包中所有greetTo()方法的切点 @After("within(com.ivy.*)&&execution(* greetTo(..))") public void greetToFun() { System.out.println("--greetToFun() executed!"); } // 匹配所有serveTo()方法并且该方法不位于NaiveWaiter目标类的切点 @Before("!target(com.ivy.NaiveWaiter)&&execution(* serveTo(..))") public void notServeInNaiveWaiter() { System.out.println("--notServeInNaiveWaiter() executed!"); } // 匹配Waiter和Seller接口实现类所有连接点的切点 @AfterReturning("target(com.ivy.Waiter)||target(com.ivy.Seller)") public void waiterOrSeller() { System.out.println("--waiterOrSeller() executed!"); } }
命名切点
切点直接声明在增强方法处,这种切点声明方式称为匿名切点,匿名切点只能在声明处使用。如果希望在其他地方重用一个切点,可以通过@Pointcut注解以及切面类方法对切点进行命名。
package com.ivy.annotation.aspectj; import org.aspectj.lang.annotation.Pointcut; public class TestNamePointcut { // 通过注解方法inPackage(), 该方法名就作为切点的名称, // 方法修饰符为private,表明该命名切点只能在本切面类中使用 @Pointcut("within(com.ivy)") private void inPackage() {} // 通过注解方法inPackage(), 该方法名就作为切点的名称, // 方法修饰符为protected,表明该命名切点可以在当前包中的切面类,子切面类中使用 @Pointcut("execution(* greetTo(..))") protected void greetTo() {} // 通过注解方法inPackage(), 该方法名就作为切点的名称, // 方法修饰符为public,表明该命名切点可以在任意类中使用 @Pointcut("inPackage() and greetTo()") public void inPkgGreetTo() {} }
inPkgGreetTo可以引用同类的greetTo()和inPackage(),而inPkgGreetTo()可以被任何类使用。
package com.ivy.annotation.aspectj; import org.aspectj.lang.annotation.Before; public class TestNamePoint { @Before("NamePointcut.inPkgGreetTo()") public void pkgGreetTo() { System.out.println("--pgkGreetTo() executed!--"); } @Before("!target(com.ivy.NaiveWaiter)&&NamePointcut.inPkgGreetTo()") public void pkgGreetToNotNaiveWaiter() { System.out.println("--pgkGreetTo() executed!--"); } }
增强织入顺序
- 如果增强在同一个切面类中声明,则依照增强在切面类中定义的顺序进行织入。
- 如果增强位于不同的切面类中,且这些切面类都实现了Ordered接口,则由接口方法的顺序号决定。
- 如果增强位于不同的切面类中,且这些切面没有实现Ordered接口,则顺序不定。