参考:
http://sishuok.com/forum/posts/list/281.html
https://my.oschina.net/ven01/blog/748415
https://docs.spring.io/spring/docs/5.0.17.RELEASE/spring-framework-reference/core.html#aop
注解开启AOP
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
proxyTargetClass
默认值为false
,表示默认使用JDK动态代理,未实现接口的类使用CGLIB,为true表示默认使用CGLIB代理
exposeProxy
默认值为false,为true表示通过aop框架暴露该代理对象,aopContext能够访问
相关注解
@Aspect
声明切面bean,加在类上,配合@Component
使用,否则Spring无法扫描到
@Aspect
@Component
public class ControllerPermitAspect {
}
@Pointcut
用于声明切入点
切入点指示符
指示符 | 作用 |
---|---|
execution | 用于匹配方法执行的连接点 |
within | 用于匹配指定类型内的方法执行 |
this | 用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配 |
target | 用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配 |
args | 用于匹配当前执行的方法传入的参数为指定类型的执行方法 |
@within | 用于匹配所以持有指定注解类型内的方法 |
@target | 用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解 |
@args | 用于匹配当前执行的方法传入的参数持有指定注解的执行 |
@annotation | 用于匹配当前执行方法持有指定注解的方法 |
bean | Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法 |
reference pointcut | 表示引用其他命名切入点,只有@ApectJ风格支持,Schema风格不支持 |
匹配语法
符号 | 说明 |
---|---|
* | 匹配任何数量字符 |
.. | 匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数 |
+ | 配指定类型的子类型;仅能作为后缀放在类型模式后边 |
例:
示例 | 说明 |
---|---|
java.*.String | 匹配java包下的任何“一级子包”下的String类型,如匹配java.lang.String,但不匹配java.lang.ss.String |
java.lang.String | 匹配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 |
组合切入点表达式
AspectJ使用 且(&&)、或(||)、非(!)来组合切入点表达式。
在Schema风格下,由于在XML中使用“&&”需要使用转义字符“&&”来代替之,所以很不方便,因此Spring ASP 提供了and、or、not来代替&&、||、!
// 摘自Spring官网
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {}
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
// ...
}
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
/********************************************/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
/***********************************/
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection<T> param);
}
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Advice implementation
}
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
// Advice implementation
}
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code and bean
}
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code, bean, and jp
}
@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});
}
@Aspect
public class UsageTracking {
@DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
public static UsageTracked mixin;
@Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
}
execution 表达式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
-
modifiers-pattern:方法的操作权限
-
ret-type-pattern:返回值
-
declaring-type-pattern:方法所在的包
-
name-pattern:方法名
-
parm-pattern:参数名
-
throws-pattern:异常
其 中,除ret-type-pattern和name-pattern之外,其他都是可选的。如,execution(* com.spring.service..(..))表示com.spring.service包下,返回值为任意类型;方法名任意;参数不作限制的 所有方法。
@Before
在被代理对象的方法前先调用
@Before("execution(public int young.test.*(int, int))")
public void beforMethod(JoinPoint point){
String methodName = point.getSignature().getName();
List<Object> args = Arrays.asList(point.getArgs());
System.out.println("调用前连接点方法为:" + methodName + ",参数为:" + args);
}
@After
在被代理对象的方法后调用,无论连接点方法执行成功还是出现异常,都将执行后置方法
@After(("execution(public int young.test.*(int, int))"))
public void afterMethod(JoinPoint point){
String methodName = point.getSignature().getName();
List<Object> args = Arrays.asList(point.getArgs());
System.out.println("调用后连接点方法为:" + methodName + ",参数为:" + args);
}
@AfterReturning
在被代理对象的方法正常返回后调用,出现异常时不会调用
/*通过returning属性指定连接点方法返回的结果放置在result变量中,在返回通知方法中可以从result变量中获取连接点方法的返回结果了。*/
@AfterReturning(value="execution(public young.test.*(int, int))", returning="result")
public void afterReturning(JoinPoint point, Object result){
String methodName = point.getSignature().getName();
List<Object> args = Arrays.asList(point.getArgs());
System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",目标方法执行结果为:" + result);
}
@AfterThrowing
在被代理对象的方法抛出异常后调用,否则不执行
/*通过throwing属性指定连接点方法出现异常信息存储在ex变量中,在异常通知方法中就可以从ex变量中获取异常信息了*/
@AfterThrowing(value="execution(public test.young.*(int, int))",throwing="ex")
public void afterReturning(JoinPoint point, Exception ex){
String methodName = point.getSignature().getName();
List<Object> args = Arrays.asList(point.getArgs());
System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",异常为:" + ex);
}
@Around
将被代理对象的方法封装起来,用环绕通知取代他
@Around("execution(public int test.young.*(int, int))")
public Object aroundMethod(ProceedingJoinPoint pdj){
/*result为连接点的放回结果*/
Object result = null;
String methodName = pdj.getSignature().getName();
/*前置通知方法*/
System.out.println("前置通知方法>目标方法名:" + methodName + ",参数为:" + Arrays.asList(pdj.getArgs()));
/*执行目标方法*/
try {
result = pdj.proceed();
/*返回通知方法*/
System.out.println("返回通知方法>目标方法名" + methodName + ",返回结果为:" + result);
} catch (Throwable e) {
/*异常通知方法*/
System.out.println("异常通知方法>目标方法名" + methodName + ",异常为:" + e);
}
/*后置通知*/
System.out.println("后置通知方法>目标方法名" + methodName);
return result;
}
JoinPoint
表示目标类连接点对象
public interface JoinPoint {
String toString(); // 连接点所在位置的相关信息
String toShortString(); // 连接点所在位置的简短相关信息
String toLongString(); // 连接点所在位置的全部相关信息
Object getThis(); // 返回AOP代理对象,也就是com.sun.proxy.$Proxy18
Object getTarget(); // 返回被代理的对象
Object[] getArgs(); // 返回被通知方法参数列表
Signature getSignature(); // 获取封装了连接点信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置
String getKind(); //连接点类型
StaticPart getStaticPart(); //返回连接点静态部分
}
JoinPoint.StaticPart
提供访问连接点的静态部分,如被通知方法签名、连接点类型等
ProceedingJoinPoint
JoinPoint的子类,只能在@Around中使用
public interface ProceedingJoinPoint extends JoinPoint {
public Object proceed() throws Throwable; // 执行目标方法
public Object proceed(Object[] args) throws Throwable; // 执行目标方法并使用传入的对象
}