声明切面
@Aspect
public class DictionaryAspect {
}
注意 Aspect除了加@Aspect注解外,必须得确保Spring可以找的到该Component。也就是要么加@Component注解,要么在Srping的配置文件中配置bean。
Spring支持的切入点表达式即AspectJ的切入点表达式。 最常用的切入点指示符(PCD)就是execution和within
Execution表达式格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
注意,除了返回类型、方法名、参数是必选外,其余均是可选
eg. execution(* com.xyz.service..*.*(..))
匹配com.xyz.service包或子包下,返回值为任意类型,参数为任意个任意类型的所有方法
execution(public * *(..))
execution(* set*(..))
execution(* com.xyz.service.AccountService.*(..))
execution(* com.xyz.service.*.*(..))
execution(* com.xyz.service..*.*(..))
within(com.xyz.service.*)
within(com.xyz.service..*)
this(com.xyz.service.AccountService)
target(com.xyz.service.AccountService)
args(java.io.Serializable)
请注意在例子中给出的切入点不同于 execution(* *(java.io.Serializable)): args版本只有在动态运行时候传入参数是Serializable时才匹配,而execution版本在方法签名中声明只有一个 Serializable类型的参数时候匹配。@target(org.springframework.transaction.annotation.Transactional)
@within(org.springframework.transaction.annotation.Transactional)
@annotation(org.springframework.transaction.annotation.Transactional)
@args(com.xyz.security.Classified)
具体例子参照帖子:http://www.javarticles.com/2015/09/spring-aop-annotation-pointcut-designator-example.htmlbean(tradeService)
bean(*Service)
另外,表达式可以组合使用,使用&&和||来组合;已经声明的Pointcut可以在其他地方引用。可以在其他PointCut中引用,也可以在Advice中引用,见最后边的例子
需要注意的地方:
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
@AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
注意,returning指定的参数值必须跟通知方法的入参一致。
@AfterThrowing(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
// pjp.proceed,即调用连接点方法。可以传入参数,见下文
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
很多情况下,我们在通知中想获取连接点的参数,而Spring提供了相应的机制
在通知方法中访问当前的连接点 任何通知方法可以将第一个参数定义为org.aspectj.lang.JoinPoint类型(环绕通知的第一个参数ProceedingJoinPoint,它是JoinPoint的一个子类)。JoinPoint接口提供了一些列方法,比如:getArgs();(返回方法参数)、getThis();(返回代理对象)、getTarget();(返回目标对象)、getSignature();(返回正在被通知的方法的相关信息)、toString();(打印正在被通知的方法的有用信息)
传递参数给通知方法
前边看到了如何获取返回值和获取抛出的异常,为了在通知方法内获取参数,我们使用指示符args
表达式来绑定。将args
中用来指定参数类型的地方写成一个参数名,那么当前通知方法执行时对应的参数值就会用该参数传进来。
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&" +
"args(account,..)")
public void validateAccount(Account account) {
// ...
}
args(account,..),有两个含义。一是,限定一个最少一个参数的方法,且第一个参数类型必须是Account的方法;二是,其使得在通知方法体内,可以通过account获取到连接点的Account对象。
另一种定义方法,定义一个切入点,在切入点指定参数,在Advice时引用
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&" +
"args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
代理对象(this)、目标对象(target) 和注解(@within, @target, @annotation, @args)都可以用一种类似的格式来绑定。详见官网。
在通知和切入点的注解中,有一个”argNames”属性用来指定参数名称。最好不用吧,具体详见官网。
@Around("execution(List<Account> find*(..) &&" +
"com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp, String accountHolderNamePattern)
throws Throwable {
String newPattern = preProcess(accountHolderNamePattern);
return pjp.proceed(new Object[] {newPattern});
}
大部分情况下,我们都会这样去给proceed绑定参数
如果有多个通知想要在同一个连接点执行,那么通知执行的次序是未知的。可以使用下面两种方式解决:
在多个切面中,Order.getValue()返回值越小的优先级越高
eg:
package com.xyz.someapp;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemArchitecture {
/**
* web层的所有类
*/
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() {}
/**
* service层的所有类
*/
@Pointcut("within(com.xyz.someapp.service..*)")
public void inServiceLayer() {}
/**
* dao层的所有类
*/
@Pointcut("within(com.xyz.someapp.dao..*)")
public void inDataAccessLayer() {}
/**
* 同上边的inServiceLayer
*/
@Pointcut("execution(* com.xyz.someapp.service.*.*(..))")
public void businessService() {}
/**
* 同上边的inDataAccessLayer
*/
@Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
public void dataAccessOperation() {}
/**
* 一个组合的例子,也引用了自身类中已定义的businessService和dataAccessOperation
*/
@Pointcut("businessService() && dataAccessOperation()")
private void test() {}
}
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
/**
* 引用了上边定义的dataAccessOperation
*/
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
/**
*或者自己定义
*/
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
}
用惯了注解方式,当然觉得用注解方式配置AOP比较清爽简单。但是他也有自己的缺点。