011-Spring aop 002-核心说明-切点PointCut、通知Advice、切面Advisor
一、概述
切点Pointcut,切点代表了一个关于目标函数的过滤规则,后续的通知是基于切点来跟目标函数关联起来的。
然后要围绕该切点定义一系列的通知Advice,如@Before、@After、@AfterReturning、@AfterThrowing、@Around等等定义的方法都是通知。其含义是在切点定义的函数执行之前、完成之后、正常返回之后、抛出异常之后以及环绕前后执行对应的切面逻辑。
一个切点和针对该切点的一个通知共同构成了一个切面Advisor。对于一个方法,我们可以定义多个切点都隐含它,并且对于每个切点都可定义多个通知来形成多个切面,SpringAOP底层框架会保证在该方法调用时候将所有符合条件的切面都切入到其执行之前或之后或环绕。通知Advice的子类Interceptor或MethodInterceptor的类名更具体一些,包含了拦截器的概念。
SpringAOP使用运行时连接点Joinpoint的概念将切面切入到调用方法中,一个运行时连接点就是对于一个可访问对象的访问过程的具体化,可能其子类Invocation或MethodInvocation的类名会更加具体一些。在实际调用中运行时连接点包括了被调用方法、被调用对象、适用于该方法的拦截器链等等信息。
执行的过程类似于FilterChain,先正向执行拦截器链的前置逻辑,然后调用method,接着反向执行拦截器链的后置逻辑,最后返回结果。
1.1、切点PointCut
上例中
@Pointcut("execution(public * com.github.bjlhx15.springaop.service.MyTestService.doSomething1*(..))")
public void doSomethingPointcut(){}; @Pointcut("@annotation(com.github.bjlhx15.springaop.anno.TestTimer)")
public void timerPointcut(){}; @Pointcut("@within(com.github.bjlhx15.springaop.anno.TestLogger)")
public void recordLogPointcut(){};
都是用于定义一个切点,注释Pointcut中的value值就是切入点指示符,SpringAOP提供的这种匹配表达式是用于计算哪些方法符合该切点的定义。Pointcut接口如下所示:
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}
其中定义了两个抽象方法:获得类过滤器和获得方法匹配器。意思很明确,就是可以通过类过滤及方法过滤,来定义对目标函数的过滤规则。各子类可以指定具体的过滤器来实现不同的过滤过则。
Spring2.0中增加了AspectJExpressionPointcut来支持AspectJ关于切点定义的表达式语法。其中定义了支持的各种类型的切点函数,并支持通配符和逻辑表达式。
1.1.1、原生切点函数
原生切点函数就是我们在示例中定义切点时使用的execution、@annotation、@within等函数,在AspectJExpressionPointcut中定义了支持的各种类型的原生切点函数:
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();
static {
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
}
其中:PointcutPrimitive 是来自于aspectj 的 参数。
常用的原生切点函数:
1、类型原生切点函数within
针对类型(全限定名)的过滤方法,语法格式如下:within(<typeName>);typeName表示类或接口的全限定名,支持使用通配符,例如:
/**
*匹配aopnew.service包中所有以MyTestService开头的类中的所有方法
*/
@Pointcut("within(aopnew.service.MyTestService*)") /**
*匹配所有实现anpnew.interface.IUserService接口的类的所有方法
*/
@Pointcut("within(anpnew.interface.IUserService+)")
2、方法原生切点函数execution
针对方法签名进行过滤,语法表达式如下:
/**
*scope:表示方法作用域,例如:public, private, protect
*return-type:表示返回类型
*fully-qualified-class-name:表示类的完全限定名
*method-name:表示方法名
*parameters:表示参数
*/
execution(<scope> <return-type> <fully-qualified-class-name><method-name>(<parameters>))
对于给定的作用域、返回值类型、完全限定类名、方法名以及参数匹配的方法将会应用切点函数指定的通知,支持使用通配符,例如:
/**
*匹配作用域为public,所在类全限定名为aopnew.service.MyTestService,方法名以doSomething开头的所有方法
*
*/
@Pointcut("execution(public * aopnew.service.MyTestService.doSomething*(..))")
3、类注释原生切点函数@within
用于匹配标注了指定注释的类型内的所有方法,与within是有区别的,within是用于匹配指定类型内的方法执行;语法如下:@within(<annotationName>)
annotationName表示注释类的全限定名,支持使用通配符,例如:
/**
*匹配标注了TestLogger的类中的所有方法
*/
@Pointcut("@within(aopnew.annotation.TestLogger)")
4、方法注释原生切点函数@annotation
用于匹配所有标注了指定注解的方法,语法如下:@annotation(<annotationName>)
annotationName表示注释类的全限定名,支持使用通配符,例如:
/** *匹配所有标注了TestTimer的方法 */ @Pointcut("@annotation(aopnew.annotation.TestTimer)")
1.1.2、通配符
上述的原生切点函数中都支持通配符,在示例中我们看到了很多如 * , .. , +等,它们的含义如下:
.. :匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包,例如:
/**
*匹配aopnew包及子包中的类名为MyTestService2中的以doSomething开头并且作用域为public的所有方法
*/
@Pointcut("execution(public * aopnew..MyTestService2.doSomething*(..))")
+ :匹配给定类的任意子类,例如:
/**
*匹配所有实现anpnew.interface.IUserService接口的类的所有方法
*/
@Pointcut("within(anpnew.interface.IUserService+)")
* :匹配任意数量的字符,例如:
/**
*匹配aopnew.service包中任意类中的所有方法
*/
@Pointcut("within(aopnew.service.*)")
1.1.3、逻辑表达式
切点指示符可以使用运算符语法进行表达式的混编,如and、or、not(或&&、||、!),例如:
/**
*匹配类上标注了TestLogger并且方法上标注了TestTimer的所有方法
*/
@Pointcut("@within(aopnew.annotation.TestLogger) && @annotation(aopnew.annotation.TestTimer)")
1.2、通知Advice
通知Advice描述了当符合某切点的方法调用时,在调用过程的哪个时机执行哪样的切面逻辑。Spring2.0引入了AspectJ的通知类型,主要分5种,分别是前置通知@Before、后置通知@AfterReturn、异常通知@AfterThrowing、最终通知@After以及环绕通知@Around。
单单解释通知Advice可能不是很直观,其子类拦截器Interceptor可能更直观更容易理解一些。AspectJ各个不同的通知注释最终会解析并构建成为不同类型的拦截器,它们的作用就是拦截方法并在方法调用的不同时机执行拦截器定义的切入逻辑。
1、前置通知@Before
前置通知通过@Before注解进行标注,可直接传入切点表达式的值也可以传入@Pointcut定义的切点函数名。该通知在目标函数执行前执行,其中传递的参数JoinPoint是运行时对方法调用过程的一个具体化,是一个运行时动态的概念,内部包含了被调用方法、方法所在的对象及拦截器链等信息。
2、后置通知@AfterReturning
通过@AfterReturning注解进行标注,该函数在目标函数执行完成后执行,并可以获取到目标函数最终的返回值returnVal,当目标函数没有返回值时,returnVal将返回null,必须通过returning = “returnVal”注明参数的名称而且必须与通知函数的参数名称相同。
请注意,在任何通知中这些参数都是可选的,需要使用时直接填写即可,不需要使用时,可以完全不用声明出来。
3、异常通知 @AfterThrowing
该通知只有在异常时才会被触发,并由throwing来声明一个接收异常信息的变量,同样异常通知也拥有Joinpoint参数,需要时加上即可
4、最终通知 @After
该通知有点类似于finally代码块,只要应用了无论什么情况下都会执行。
5、环绕通知@Around
环绕通知既可以在目标方法前执行也可在目标方法之后执行,更重要的是环绕通知可以控制目标方法是否指向执行。第一个参数必须是ProceedingJoinPoint,通过该对象的proceed()方法来传递拦截器(通知)链或执行函数,proceed()的返回值就是环绕通知的返回值。
同样的,ProceedingJoinPoint是运行时对方法调用过程的一个具体化,是一个运行时动态的概念,内部包含了被调用方法、方法所在的对象及拦截器链等信息,并且其相较于JoinPoint增加了proceed函数用于传递拦截器链或执行函数。
1.2.1、说明
通知的继承路径为:Advice<-Interceptor<-MethodInterceptor
其中MethodInterceptor的接口定义如下:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
也就是说方法拦截器的子类都需要实现一个方法,接受MethodInvocation类型的参数invocation。MethodInvocation顾名思义是动态概念方法调用的具体化,其本质上是一个运行时连接点JoinPoint。
MethodInterceptor子类在invoke方法中执行自己的业务逻辑并调用invocation.proceed()来传递拦截器调用链。例如:
Object invoke(MethodInvocation invocation) throws Throwable{
...do something before method invocation...
Object obj = null;
try{
obj = invocation.proceed();
}catch(Throwable e){
...do something after throwing...
}finally{
...do something after method invoke...
}
...do something after method return...
return obj;
}
上面的示例显示出了拦截器可以在方法调用的各个时机执行切入业务的大体实现,而前面的五种通知本质上都是上述代码的一个变种。
1.3、切面Advisor
当符合某切点条件的函数在被执行时,就产生了一个运行时连接点Joinpoint的概念。运行时连接点代表了一个在静态连接点(程序中的某个位置)上发生的事件。例如:一次调用就是一个对于方法(静态连接点)的运行时连接点。
在基于拦截器框架的上下文中,一个运行时连接点就是对于一个可访问对象的访问过程的具体化。Joinpoint接口定义如下:
public interface Joinpoint {
Object proceed() throws Throwable;
Object getThis();
AccessibleObject getStaticPart();
}
如上所述Joinpoint代表了运行时连接点,也就是代表了方法调用过程的具体化。因此是一个动态的概念,getThis()就是返回这个运行时连接点的动态部分(如方法所在的对象实例),而getStaticPart()就用于返回对应的静态连接点的信息(如方法定义本身)。
另外,proceed()用于执行本运行时连接点的拦截器链上的下一个拦截器。由此可知,运行时连接点中除了维护被调用方法,方法所在的对象实例外还应该维护定义于该方法的所有拦截器(通知)。Joinpoint接口的继承链为:
Joinpoint<-Invocation<-MethodInvocation<-ProxyMethodInvocation
从子类的名称上会更容易理解,运行时连接点更侧重的是描述一个调用的过程。其实现类为ReflectiveMethodInvocation,该类中维护的属性如下:
protected final Object proxy; protected final Object target; protected final Method method; protected Object[] arguments; private final Class<?> targetClass; /**
* Lazily initialized map of user-specific attributes for this invocation.
*/
private Map<String, Object> userAttributes; /**
* List of MethodInterceptor and InterceptorAndDynamicMethodMatcher
* that need dynamic checks.
*/
protected final List<?> interceptorsAndDynamicMethodMatchers; /**
* Index from 0 of the current interceptor we're invoking.
* -1 until we invoke: then the current interceptor.
*/
private int currentInterceptorIndex = -1;
其中interceptorsAndDynamicMethodMatchers就是我们上面所说的拦截器链,ReflectiveMethodInvocation的proceed方法如下所示:
@Override
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
} Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
结合前面对于MethodIntercepter子类中invoke函数的实现,运行时连接点中拦截器链的调用方式如下:
1、如果拦截器链尚未执行完,就执行拦截器链上的下一个拦截器并将this(本动态连接点)传递过去
2、每一个拦截器的拦截函数中都执行自己的前置逻辑并调用invocation.proceed()重复步骤1
3、档拦截器链执行完毕,则执行方法调用,并返回结果
4、在返回的过程中,按照前面调用顺序的反向顺序执行方法调用的后置逻辑,也就是在invocation.proceed()之后编写的逻辑
5、拦截器链反向执行完成后,最终返回结果。
由此可得,一个定义了切面的方法调用过程如下所示:
interceptor1.before()
interceptor2.before()
......
interceptorn.before()
method.invoke()
interceptorn.aft()
......
interceptor2.aft()
interceptor1.aft()
@Before定义的通知(拦截器)只有before()逻辑;@After、@AfterReturning、@AfterThrowing定义的通知(拦截器)只有after()逻辑;@Around定义的通知(拦截器)可以自己来定义before()和after()逻辑。
011-Spring aop 002-核心说明-切点PointCut、通知Advice、切面Advisor的更多相关文章
- Spring Aop(十六)——编程式的自定义Advisor
转发:https://www.iteye.com/blog/elim-2399437 https://www.iteye.com/blogs/subjects/springaop 编程式的自定义Adv ...
- Spring AOP表达式报错:Pointcut is not well-formed: expecting 'name pattern' at character position
问题现象: java.lang.IllegalStateException: Failed to load ApplicationContext at org.springframework.test ...
- Spring AOP 的实现方式(以日志管理为例)
一.AOP的概念 AOP(Aspect Oriented Programming),是面向切面编程的技术.AOP基于IoC基础,是对OOP的有益补充,流行的AOP框架有Sping AOP.Aspect ...
- spring aop方式配置事务中的三个概念 pointcut advice advisor
AOP的3个关键概念 因为AOP的概念难于理解,所以在前面首先对Java动态代理机制进行了一下讲解,从而使读者能够循序渐进地来理解AOP的思想. 学习AOP,关键在于理解AOP的思想,能够使用AOP. ...
- Java Spring AOP用法
Java Spring AOP用法 Spring AOP Java web 环境搭建 Java web 项目搭建 Java Spring IOC用法 spring提供了两个核心功能,一个是IoC(控制 ...
- Spring Aop 应用实例与设计浅析
0.代码概述 代码说明:第一章中的代码为了突出模块化拆分的必要性,所以db采用了真实操作.下面代码中dao层使用了打印日志模拟插入db的方法,方便所有人运行demo. 1.项目代码地址:https:/ ...
- Spring AOP @Aspect
spring提供了两个核心功能,一个是IoC(控制反转),另外一个便是Aop(面向切面编程),IoC有助于应用对象之间的解耦,AOP则可以实现横切关注点(如日志.安全.缓存和事务管理)与他们所影响的对 ...
- 关于 Spring AOP (AspectJ) 该知晓的一切
关联文章: 关于Spring IOC (DI-依赖注入)你需要知道的一切 关于 Spring AOP (AspectJ) 你该知晓的一切 本篇是年后第一篇博文,由于博主用了不少时间在构思这篇博文,加上 ...
- 最全的Spring AOP
1.什么是AOP? AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, ...
随机推荐
- 更改 Ubuntu 的 apt 源
1.在更改apt源之前要先备份官方自带的apt源 cd /etc/apt sudo cp sources.list sources.list.bak 2. 更改 sources.list 文件 sud ...
- Qt编译报错:The kit Desktop Qt...has configuration issues which might be the root cause for this problem.
报错:Cannot find file: E:\实验室\20180409_Qt跑马灯\QtTest\QtTest.pro. 17:03:11: 进程"D:\Qt\Qt5.8.0\5.8\ms ...
- Win10如何设置插入鼠标后自动禁用触摸板
首先按“Windows+R”键,调出运行窗口. 然后输入“control”然后点击“确定”! 打开Win10控制面板 在“控制面板”中,选择“硬件和声音”! 选择“AUSU Amart Ges ...
- Httpd服务进阶知识-基于Apache Modele的LAMP架构之PhpMyAdmin案例
Httpd服务进阶知识-基于Apache Modele的LAMP架构之PhpMyAdmin案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.常见LAMP应用 PhpMyAdm ...
- eclipse集成lombok插件
原文:https://my.oschina.net/u/3771868/blog/1837243 lombok官网:https://projectlombok.org/jar包下载路径:https:/ ...
- python 验证码识别示例(三) 简单验证码识别
今天介绍一个简单验证的识别. 主要是标准的格式,没有扭曲和变现.就用 pytesseract 去识别一下. 验证码地址:https://user.www.gov.cn/sso/verifyimg_ed ...
- 在swift项目中若要通过pod引入第三方的swift项目,必须加上use_frameworks!
因为swift没法打.a https://www.jianshu.com/p/ac629a1cb8f5
- [译] 2017 年比较 Angular、React、Vue 三剑客
原文地址:Angular vs. React vs. Vue: A 2017 comparison 原文作者:Jens Neuhaus 译文出自:掘金翻译计划 本文永久链接:github.com/xi ...
- 数据库join解释 与视图
数据库的视图是表运算的结果. 数据库的表是数据单元: join是运算符: 视图是运算结果. 数据库join解释 1.join:将两个表结构连接成一个视图 2.left.right.inner: 从基准 ...
- Pandas模块 -- 实操练习
如果对序列进行数学函数的运算,首选numpy模块: 如果对序列做统计运算,首选序列的“方法”,因为序列的“方法”更加丰富,如计算序列的偏度.峰度等,而Numpy模块是没有这样的函数. 手工构造数据框D ...