Spring AOP默认是使用j2se的动态代理为AOP代理, 这一点可以应用于任何实现接口的类, 但是在一个没有实现任何接口的类情况下, 由于j2se的动态代理是不支持没有实现接口的类的, 所以Spring AOP在这种情况下会使用第三方类库CgLib来为AOP代理
Spring AOP使用AspectJ5提供的类库来解释和匹配注释, 因此需要保证AspectJ的aspectjweaver.jar库包含在项目中
以xml的方式启用@AspectJ支持
首先在xml配置文件中需要添加导入AOP命名空间的标签元素, 所以需要在xml添加如下的配置:
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd”
配置需要用到aop:aspectj-autoproxy元素:
<aop:aspectj-autoproxy/>
这样就开启了AspectJ支持
声明切面
在开启了@AspectJ的支持后, 任何带有@Aspect注释的Bean将被Spring自动检测并用于配置Spring AOP
下面是声明切面简例
例1:
package com.wenj.aop.aspect;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class TestAOPAspect {
//...
}
这样就声明了一个简单的切面, 切面就像其他普通的类一样, 可以有任何方法和字段, 切面也可以包含切入点, 事件通知等
注意: 不可以通过另外一个切面给一个切面添加事件通知
声明切入点
Spring AOP 只为Spring Bean对象提供方法执行联接点, 所以可以认为Spring AOP的切入点就是匹配拦截Spring Bean的执行方法, 切入点的声明有两个部分: 识别标记和任意参数, 我们可以任意的定义切入点所要匹配拦截的执行方法, 切入点是使用@Pointcut注释来声明的
下面是一个小示例
例1:
@Pointcut("execution(* com.wenj.servicesimpl..*.*(..))")
public void anyMethod(){ }
此切入点的功能就是拦截com.wenj.servicesimpl包下和子包下的所有执行方法
注意:在Spring中并不是支持所有的AspectJ的切入点类型, 只是支持一部分, 这一部分包括: execution, within, this, target, args, @target, @args,
@within, @annotation, 但是这些也是有限制的, 由于Spring AOP限制了只
能是匹配执行方法的拦截, 所以以上的切入点的定义比真正AspectJ的定义类型
组件的要少
结合切入点表达
切入点可以通过使用&&, || 和 !来进行结合, 可以直接通过切入点的名称来进行对切入点的引用结合
下面是一个示例
例1:
@Pointcut("execution(* com.wenj.servicesimpl..add(..))")
public void addMethod(){ }
@Pointcut("execution(* com.wenj.servicesimpl..delete(..))")
public void deleteMethod(){ }
//这里结合了add与delete切入点
@Pointcut("addMethod() || deleteMethod()")
public void orMethod(){}
以上先分别定义addMethod()和deleteMethod()的切入点, 然后orMethod()切入点是通过 ||来结合addMethod()和deleteMethod()切入点, 这样我们就可以使用orMethod()这个切入点来拦截来自addMethod()和deleteMethod()的Spring Bean的执行方法
共享切入点定义
对于一个项目来说, 我们可以通过管理系统的模块来管理系统, 所以对于不同的模块我们可以定义不同的切入点来进行管理, 下面是典型的切面管理模块定义
例1:
package com.wenj.aop.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class TestAOPAspect {
/* web层的联接点
* 对于一切定义在com.wenj.web包及其子包下的执行方法进行拦截
*/
@Pointcut("within(com.wenj.web..*)")
public void inWebLayer() {}
/*
* service层的联接点
* 对已一切定义在com.wenj.service包及其子包下的执行方法进行拦截
*/
@Pointcut("within(com.wenj.services..*)")
public void inServiceLayer() {}
/*
* DAO层
* 对已一切定义在com.wenj.dao包及其子包下的执行方法进行拦截
*/
@Pointcut("within(com.wenj.dao..*)")
public void inDataAccessLayer() {}
/*
* 业务服务层
* 在此就会拦截任何定义在services包包及其子包的执行方法
*/
@Pointcut("execution(* com.wenj.services..*.*(..))")
public void businessService() {}
/*
* DAO层
* 在此就会拦截任何定义在dao包及其子包的执行方法
*/
@Pointcut("execution(* com.wenj.dao..*.*(..))")
public void dataAccessOperation() {}
}
注意:
通过类匹配模式串声明切入点,within()函数定义的连接点是针对目标类而言,而非针
对运行期对象的类型而言,这一点和execetion()是相同 的。但和execution()函数不
同的是,within()所指定的连接点最小范围只能是类,而execution()所指定的连接点,
可以大到包, 小到方法入参。所以从某种意义上说,execution()函数的功能涵盖了
within()函数的功能。
所以一般会用execution的比较多
匹配表达式样例
匹配表达式的格式为:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
其中returning type pattern, name pattern 和 parameters pattern 可以任意指定, 返回类型需要与联接点相匹配, 常用*作为返回类型, 意在匹配所有的返回类型, 名称匹配类型匹配的是执行方法的名称, 也可以用*来表示, 意在匹配所有名称的执行方法, 参数类型匹配稍微复杂点, 0--表示匹配的执行方法没有参数, (..)--表示匹配的执行方法, 参数任意, 参数个数可以是0 或更多, (*)--表示匹配的执行方法其中一个参数为任意类型, 例如(*, String)表示第一个参数为任意类型的, 第二个参数为String类型的
下面是一些常见的匹配表达式样例:
· 匹配任何域为public的执行方法:
execution(public * *(..))
· 匹配任何以”start”开头的执行方法:
execution(* start*(..))
· 匹配任何被定义在TestBean接口的执行方法:
execution(* com.wenj.services.TestBean.*(..))
· 匹配任何被定义在services包下的执行方法:
execution(* com.wenj.services..*(..))
· 匹配任何被定义在services包及其子包下的执行方法:
execution(* com.wenj.services..*.*(..))
· 匹配任何在services包下Bean的执行方法:
within(com.wenj.services.*)
· 匹配任何在services包及其子包下Bean的执行方法:
within(com.wenj.services..*)
· 匹配任意实现TestBean接口Bean的执行方法:
this(com.wenj.services.TestBean)
· 匹配目标对象实现TestBean接口Bean的执行方法:
target(com.wenj.services.TestBean)
· 匹配一个以传入参数类型为java.io.Serializable开头, 后面参数类型任意的执行方法, args参数是运行时动态匹配的:
args(java.io.Serializable,..)
· 匹配一个单一参数且传入参数类型为java.io.Serializable的执行方法, args参数是运行时动态匹配的:
args(java.io.Serializable)
注意: 这里不同于在切入点声明为execution(* *(java.io.Serializable)). 参数匹配是在运行时动态的匹配传入参数为Serializable, 而execution匹配时仅仅匹配一个单一参数的方法且参数为Serializable类型的;
· 匹配任何目标对象持有Transactional注解的Bean方法, 必须在目标对象声明这个注释,在接口上声明不起作用:
@target(org.springframework.transaction.annotation.Transactional)
· 匹配任何目标对象的类型持有Transactional注解的Bean方法, 必须在目标对象声明这个注释,在接口上声明不起作用:
@within(org.springframework.transaction.annotation.Transactional)
. 匹配当前持有注解Transactional的执行方法:
@annotation(org.springframework.transaction.annotation.Transactional)
· 匹配任何一个只接受一个参数的方法, 且方法运行时传入的参数持有注解Classified, 动态切入点, 类似于arg指示符:
@args(com.wenj.security.Classified)
· 匹配以tradeService命名的Bean的执行方法:
bean(tradeService)
· 匹配命名结尾为Service的Bean的执行方法:
bean(*Service)
声明事件通知
事件通知与之前的切入点是有联系的, 下面是Advice的一些介绍
Before Advice
Before Advice是以注释@Before声明在切面的事件通知
如:
@Pointcut("execution(public * *(..))")
public void publicMethod(){}
@Before("publicMethod()")
public void printMsg(){
// ...
}
其中publicMethod()为被声明为@Pointcut的拦截方法
作用是: 被拦截且匹配的执行方法前, 先执行注释为@Before的事件通知方法
After returning advice
After returning advice是以注释@AfterReturning声明的事件通知
如:
@Pointcut("execution(public * *(..))")
public void publicMethod(){}
@AfterReturning("publicMethod()")
public void printMsg2(){
// ...
}
作用是: 在被拦截且匹配的执行方法成功返回后, 执行@AfterReturning的事件通知方法
有时候我们需要在事件通知的方法体内访问被拦截的执行方法返回的类型, 这时我们可以指定返回参数
如:
@Pointcut("execution(* com.wenj.services..getInt(..))")
public void getIntegerProxy(){}
@AfterReturning(
pointcut = "getIntegerProxy()",
returning = "retVal")
public void printMsg3(Object retVal) {
// ...
Integer i = (Integer)retVal;
System.out.println("@AfterReturning i: " + i);
}
returning为被拦截执行方法的返回值
After (finally) advice
After (finally) advice是以@After注释声明的事件通知
如:
@Pointcut("execution(* com.wenj.services..*(..))")
public void testAfterAdvice(){}
@After("testAfterAdvice()")
public void afterAdvice(){
System.out.println("@After");
}
Around advice
Around advice是Spring AOP的最后一个事件通知,是以注释@Around声明的事件, 在匹配拦截执行方法后可以执行类似Before和After的事件通知, 并且它是可以决定什么时候怎么样甚至是执行方法已经在执行的时候进行切入, Around Advice 一般用于的执行方法之前与之后需要共享数据, 当然也可以用Before Advice和After Advice实现(但是对于线程安全来说这种做法是不安全的)
@Pointcut("execution(* com.wenj.services..*(..))")
public void testAroundAdvice(){}
@Around("testAroundAdvice()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch/*相当于Before*/
Object retVal = pjp.proceed();
// stop stopwatch /*相当于After*/
System.out.println("@Around");
return retVal;
}
通知事件参数设置
传递参数给Advice
之前的@AfterReturning的示例中, 已经知道怎么绑定被拦截执行方法的返回值或抛出异常, 现在来实现通过绑定被拦截执行方法的参数使匹配更加严谨
如:
@Pointcut("execution(* com.wenj.services..*(..))")
public void testArgs(){ }
@Before("testArgs() && args(id,..)")
public void passArgs2Advice(int id){
if(id != 0){
System.out.println("@Before && Args: id != 0");
}
}
这里的@Before添加了参数匹配,只有被拦截执行方法的参数第一个参数为id的才会被匹配拦截
上述例子也可以改成下面这样:
@Pointcut("execution(* com.wenj.services..*(..)) && args(id,..)")
public void testArgs(int id){ }
@Before("testArgs(id)")
public void passArgs2Advice(int id){
if(id != 0){
System.out.println("@Before && Args: id != 0");
}
}
......
参数传递到此告一个段落了, 感兴趣的朋友可以参考AspectJ 编程指导获取更多的细节