1、启用对@AspectJ的支持
Spring除了支持Schema方式配置AOP,还支持注解方式:使用@AspectJ风格的切面声明。
Spring默认不支持@AspectJ风格的切面声明,为了支持需要使用如下配置:
这样Spring就能发现@AspectJ风格的切面并且将切面应用到目标对象。
2、声明切面
@AspectJ风格的声明切面非常简单,使用@Aspect注解进行声明:
@Aspect()
Public class Aspect{
……
}
然后将该切面在配置文件中声明为Bean后,Spring就能自动识别并进行AOP方面的配置:
该切面就是一个POJO,可以在该切面中进行切入点及通知定义,接着往下看吧。
3、声明切入点
@AspectJ风格的命名切入点使用org.aspectj.lang.annotation包下的@Pointcut+方法(方法必须是返回void类型)实现。
<@Pointcut(value="切入点表达式", argNames = "参数名列表")
public void pointcutName(……) {}
value:指定切入点表达式(在哪切);
argNames:指定命名切入点方法参数列表参数名字,可以有多个用“,”分隔,这些参数将传递给通知方法同名的参数,同时比如切入点表达式“args(param)”将匹配参数类型为命名切入点方法同名参数指定的参数类型。
pointcutName:切入点名字,可以使用该名字进行引用该切入点表达式。
实例:
@Pointcut(value="execution(* cn.javass..*.sayAdvisorBefore(..)) && args(param)", argNames = "param")
public void beforePointcut(String param) {}
定义了一个切入点,名字为“beforePointcut”,该切入点将匹配目标方法的第一个参数类型为通知方法实现中参数名为“param”的参数类型。
4、声明通知
@AspectJ风格的声明通知也支持5种通知类型:
一、前置通知:使用org.aspectj.lang.annotation 包下的@Before注解声明;
@Before(value = "切入点表达式或命名切入点", argNames = "参数列表参数名")
value:指定切入点表达式或命名切入点;
argNames:与Schema方式配置中的同义。
定义目标接口:
public interface IHelloWorldService {
public void sayHello();
}
定义目标接口实现:
public class HelloWorldService implements IHelloWorldService {
@Override
public void sayHello() {
System.out.println("============Hello World!");
}
}
package cn.javass.spring.chapter6.aop;
import org.aspectj.lang.annotation.Aspect;
定义切面:
@Aspect
public class HelloWorldAspect2 {
//定义切入点:
@Pointcut(value="execution(* cn.javass..*.*(..)) && args(param)", argNames = "param")
public void beforePointcut(String param) {}
//定义通知:(前置通知)
@Before(value = "beforePointcut(param)", argNames = "param")
public void beforeAdvice(String param) {
System.out.println("===========before advice param:" + param);
}
}
在chapter6/advice2.xml配置文件中进行如下配置:
测试代码cn.javass.spring.chapter6.AopTest:
@Test
public void testAnnotationBeforeAdvice() {
System.out.println("======================================");
ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/advice2.xml");
IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayBefore("before");
System.out.println("======================================");
}
将输出:
==========================================
===========before advice param:before
============say before
==========================================
切面、切入点、通知全部使用注解完成:二、后置返回通知:使用org.aspectj.lang.annotation 包下的@AfterReturning注解声明;
@AfterReturning(
value="切入点表达式或命名切入点",
pointcut="切入点表达式或命名切入点",
argNames="参数列表参数名",
returning="返回值对应参数名")
//实例
@AfterReturning(
value="execution(* cn.javass..*.sayBefore(..))",
pointcut="execution(* cn.javass..*.sayAfterReturning(..))",
argNames="retVal", returning="retVal")
public void afterReturningAdvice(Object retVal) {
System.out.println("===========after returning advice retVal:" + retVal);
}
value:指定切入点表达式或命名切入点;
pointcut:同样是指定切入点表达式或命名切入点,如果指定了将覆盖value属性指定的,pointcut具有高优先级;
argNames:与Schema方式配置中的同义;
returning:与Schema方式配置中的同义。
三、后置异常通知:使用org.aspectj.lang.annotation 包下的@AfterThrowing注解声明;
@AfterThrowing (
value="切入点表达式或命名切入点",
pointcut="切入点表达式或命名切入点",
argNames="参数列表参数名",
throwing="异常对应参数名")
@AfterThrowing(
value="execution(* cn.javass..*.sayAfterThrowing(..))",
argNames="exception", throwing="exception")
public void afterThrowingAdvice(Exception exception) {
System.out.println("===========after throwing advice exception:" + exception);
}
value:指定切入点表达式或命名切入点;四、后置最终通知:使用org.aspectj.lang.annotation 包下的@After注解声明;
@After (
value="切入点表达式或命名切入点",
argNames="参数列表参数名")
@After(value="execution(* cn.javass..*.sayAfterFinally(..))")
public void afterFinallyAdvice() {
System.out.println("===========after finally advice");
}
value:指定切入点表达式或命名切入点;
五、环绕通知:使用org.aspectj.lang.annotation 包下的@Around注解声明;
@Around (
value="切入点表达式或命名切入点",
argNames="参数列表参数名")
@Around(value="execution(* cn.javass..*.sayAround(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("===========around before advice");
Object retVal = pjp.proceed(new Object[] {"replace"});
System.out.println("===========around after advice");
return retVal;
}
value:指定切入点表达式或命名切入点;5、引入
@AspectJ风格的引入声明在切面中使用org.aspectj.lang.annotation包下的@DeclareParents声明:
@DeclareParents(
value=" AspectJ语法类型表达式",
defaultImpl=引入接口的默认实现类)
private Interface interface;
@DeclareParents(
value="cn.javass..*.IHelloWorldService+", defaultImpl=cn.javass.spring.chapter6.service.impl.IntroductiondService.class)
private IIntroductionService introductionService;
value:匹配需要引入接口的目标对象的AspectJ语法类型表达式;与Schema方式中的types-matching属性同义;
private Interface interface:指定需要引入的接口;与Schema方式中的implement-interface属性同义;
defaultImpl:指定引入接口的默认实现类,与Schema方式中的default-impl属性同义;
没有与Schema方式中的delegate-ref属性同义的定义方式;
6、AspectJ切入点指示符
切入点指示符用来指示切入点表达式目的,,在Spring AOP中目前只有执行方法这一个连接点,Spring AOP支持的AspectJ切入点指示符如下:
execution:用于匹配方法执行的连接点;
within:用于匹配指定类型内的方法执行;
this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
@within:用于匹配所以持有指定注解类型内的方法;
@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
@annotation:用于匹配当前执行方法持有指定注解的方法;
bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;
reference pointcut:表示引用其他命名切入点,只有@ApectJ风格支持,Schema风格不支持。
注意:只有execution指示器是唯一的执行匹配,其它指示器用于限制匹配。
6.1 类型匹配语法
首先让我们来了解下AspectJ类型匹配的通配符:
* :匹配任何数量字符;
..:(两个点)匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
+ :匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
java.lang.String 匹配String类型;
java.*.String 匹配java包下的任何“一级子包”下的String类型;
如匹配java.lang.String,但不匹配java.lang.ss.String
java..* 匹配java包及任何子包下的任何类型;
如匹配java.lang.String、java.lang.annotation.Annotation
java.lang.*ing 匹配任何java.lang包下的以ing结尾的类型;
java.lang.Number+ 匹配java.lang包下的任何Number的子类型;
如匹配java.lang.Integer,也匹配java.math.BigInteger
6.2 匹配表达式
6.2.1 匹配类型:
使用如下方式匹配
注解 类的全限定名字
注解:可选,类型上持有的注解,如@Deprecated;
类的全限定名:必填,可以是任何类全限定名。
6.2.2 匹配方法执行:
使用如下方式匹配:
注解 修饰符 返回值类型 类型声明 方法名(参数列表) 异常列表
注解:可选,方法上持有的注解,如@Deprecated;
修饰符:可选,如public、protected;
返回值类型:必填,可以是任何类型模式;“*”表示所有类型;
类型声明:可选,可以是任何类型模式;
方法名:必填,可以使用“*”进行模式匹配;
参数列表:“()”表示方法没有任何参数;“(..)”表示匹配接受任意个参数的方法,“(..,java.lang.String)”表示匹配接受java.lang.String类型的参数结束,且其前边可以接受有任意个参数的方法;“(java.lang.String,..)” 表示匹配接受java.lang.String类型的参数开始,且其后边可以接受任意个参数的方法;“(*,java.lang.String)” 表示匹配接受java.lang.String类型的参数结束,且其前边接受有一个任意类型参数的方法;
异常列表:可选,以“throws 异常全限定名列表”声明,异常全限定名列表如有多个以“,”分割,如throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException。
匹配Bean名称:可以使用Bean的id或name进行匹配,并且可使用通配符“*”;
6.3 组合切入点表达式
AspectJ使用 且(&&)、或(||)、非(!)来组合切入点表达式。
在Schema风格下,由于在XML中使用“&&”需要使用转义字符“&&”来代替之,所以很不方便,因此Spring ASP 提供了and、or、not来代替&&、||、!。
6.4 切入点使用示例
一、execution:使用“execution(方法表达式)”匹配方法执行;
public * *(..)
任何公共方法的执行
* cn.javass..IPointcutService.*()
cn.javass包及所有子包下IPointcutService接口中的任何无参方法
* cn.javass..*.*(..)
cn.javass包及所有子包下任何类的任何方法
* (!cn.javass..IPointcutService+).*(..)
非“cn.javass包及所有子包下IPointcutService接口及子类型”的任何方法
* cn.javass..IPointcutService+.*()
cn.javass包及所有子包下IPointcutService接口及子类型的的任何无参方法
* (cn.javass..IPointcutService+
&& java.io.Serializable+).*(..)
任何实现了cn.javass包及所有子包下IPointcutService接口和java.io.Serializable接口的类型的任何方法
@java.lang.Deprecated * *(..)
任何持有@java.lang.Deprecated注解的方法
二、within:使用“within(类型表达式)”匹配指定类型内的方法执行;
within(cn.javass..*)
cn.javass包及子包下的任何方法执行
within(cn.javass..IPointcutService+)
cn.javass包或所有子包下IPointcutService类型及子类型的任何方法
三、this:使用“this(类型全限定名)”匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口方法也可以匹配;注意this中使用的表达式必须是类型全限定名,不支持通配符;
this(cn.javass.spring.chapter6.service.IPointcutService)
当前AOP对象实现了IPointcutService接口的任何方法
this(cn.javass.spring.chapter6.service.IIntroductionService)
当前AOP对象实现了 IIntroductionService接口的任何方法,也可能是引入接口
四、target:使用“target(类型全限定名)”匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;注意target中使用的表达式必须是类型全限定名,不支持通配符;
target(cn.javass.spring.chapter6.service.IPointcutService)
当前目标对象(非AOP对象)实现了 IPointcutService接口的任何方法
target(cn.javass.spring.chapter6.service.IIntroductionService)
当前目标对象(非AOP对象) 实现了IIntroductionService 接口的任何方法,不可能是引入接口
五、args:使用“args(参数类型列表)”匹配当前执行的方法传入的参数为指定类型的执行方法;注意是匹配传入的参数类型,不是匹配方法签名的参数类型;参数类型列表中的参数必须是类型全限定名,通配符不支持;args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用;
args (java.io.Serializable,..)
任何一个以接受“传入参数类型为 java.io.Serializable” 开头,且其后可跟任意个任意类型的参数的方法执行,args指定的参数类型是在运行时动态匹配的
参考来源:
【第六章】 AOP 之 6.4 基于@AspectJ的AOP ——跟我学spring3
【第六章】 AOP 之 6.5 AspectJ切入点语法详解 ——跟我学spring3