SpringAOP详解
1、Spring AOP 中的基本概念
连接点(JoinPoint):
在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中
一个连接点总是表示一个方法的执行。
通俗讲:
层与层之间的调用过程中,目标层中可调用的方法,就称之为连接点。
切入点(PointCut):
匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如:
当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ
切入点语法。
通俗讲:
在连接点的基础上,增加上切入规则,选择出需要进行增强的切入点,这些基于切入规则选出来的连接点
就称之为切入点。
切面(Aspect):
一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子
在Spring AOP中,切面可以使用基于模式或者基于@Aspect注解的方式来实现。
通俗讲:
狭义上就是当Spring拦截下切入点后,将这些切入点交给处理类进行功能增强,这个处理类就称为切面。
广义上来讲,将Spring底层的代理、切入点和处理类加在一起实现的对层与层之间调用过程进行增强的机制,称之为切面。
通知(Advise):
在切面的某个特定的连接点上执行的动作。其中包括了“around”,“before”,“after”等不同类型的通知(通知将在后面祥论)。
许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
通俗讲:
在Spring底层的代理拦截下切入点后,将切入点交给切面类,切面类中就要有处理这些切入点的方法,
这些方法就称之为通知(也叫增强、增强方法)。针对于切入点执行的过程,通知还分为不同的类型,
分别关注切入点在执行过程中的不同的时机。
目标对象(Target Object):
被一个或者多个切面所通知的对象。也被称做通知(Advised)对象。既然Spring AOP是通过运
行时代理实现的,这个对象永远是一个被代理(proxied)对象。
通俗讲:
就是真正希望被访问的对象。Spring底层的动态代理对他进行了代理,具体能不能真的访问到目标对象,或在目标对象
真正执行之前和之后是否做一些额外的操作,取决于切面。
高内聚是指某个模块或关注点内部一系列相关功能的联系程度的高低。
低耦合是指软件系统中,模块与模块之间的直接依赖程度。
如果一个系统中存在A、B两个模块进行交互,如果修改A模块,不影响B模块的的工作,那么认为A模块有足够的内聚。
设计一个高内聚的模块应该是:要确定模块要完成的功能点,除了完成本职功能外,不提供其他功能,要满足可读性,可扩展性,
可复用性,可维护性要求,向外提供服务是最好是面向接口编程。
2、Spring AOP入门案例
a、导入AOP相关开发包
b、创建一个切面类
c、定义通知
d、定义一个连接点
e、配置切入点
在MyEclipse中导入AOP的schema约束文件,以便于在配置文件中可以提示标签。
在其中配置切入点:
f、定义切面(ref获取切面bean)
g、执行方法,发现切面确实起作用
3、切入点表达式
a、within表达式
通过类名进行匹配 粗粒度的切入点表达式
whthin(包名.类名)
则这个类中的所有方法都会被表达式识别,成为切入点。
在within表达式中可以使用*号匹配符,匹配指定包下的所有的类,注意,只匹配当前包
不包括当前包的子孙包。
在within表达式中也可以使用*号匹配符,匹配包(第一个*匹配的是包,第二个是包中的方法)
在within表达式中也可以使用 ..* 匹配符,匹配指定包下及其子孙包下的所有的类
b、execution()表达式
语法:execution(返回值类型 包名.类名.方法名(参数类型,参数类型。。。))
例子1:
该切入点表达式规则表示,切出指定包下指定类下指定名称指定参数指定返回值的方法
例子2:
该切入点规则表示,切出指定包下所有类中的query方法,要求无参,但返回值类型不限
例子3:
该切入点规则表示,切出指定包及其子孙包所有的类中的query方法,要求无参,返回值类型不限
例子4:
该切入点规则表示,切出指定包及其子孙包下所有的类中的query方法,
要求参数为int java.langString类型,但返回值类型不限。
例子5:
该切入点规则表示,切出指定包及其子孙包下所有的类中的query方法,,参数数量及类型不限
返回值类型不限。
例子6:
该切入点规则表示,切出指定包下及其子孙包下所有类中的所有方法,参数数量及类型不限
返回值类型不限。这种写法等价于within表达式的功能。
4、Spring AOP的原理
Spring会在用户获取对象时,生成目标对象的代理对象,之后根据切入点规则,匹配用户连接点,得到切入点
当切入点被调用时,不会直接去找目标对象,而是通过代理对象拦截之后交由切面类中的指定的通知执行来
进行增强。
Spring自动为目标对象生成代理对象,默认情况下,如果目标对象实现过接口,则采用Java的动态代理机制
如果目标对象没有实现过接口,则采用CGLIB动态代理。开发者可以再Spring中进行配置,要求无论对象是
否实现过接口,都强制使用CGLIB动态代理。
5、Spring的五大通知类型
a、前置通知
在目标方法执行之前执行的通知。
前置通知方法,可以没有参数,也可以额外接收一个JoinPoint,Spring会自动将该对象传入,代表当前的连接点,通过该对象可以获
取目标对象 和 目标方法相关的信息。
注意,如果接收JoinPoint,必须保证其为方法的第一个参数,否则报错。
b、环绕通知
在环绕通知之前和之后都可以执行额外代码的通知。
在环绕通知中必须显式的调用目标方法,目标方法才会执行,这个显式调用时通过ProceedingJoinPoint来实现的,
可以在环绕通知中接收一个此类型的形参,spring容器会自动将该对象传入,注意这个参数必须处在环绕通知的第一个形参位置。
**要注意,只有环绕通知可以接收ProceedingJoinPoint,而其他通知只能接收JoinPoint。
环绕通知需要返回返回值,否则真正调用者将拿不到返回值,只能得到一个null。
环绕通知有
控制目标方法是否执行、有控制是否返回值、甚至改变返回值
的能力
环绕通知虽然有这样的能力,但一定要慎用,不是技术上不可行,而是要小心不要破坏了软件分层的“高内聚 低耦合”的目标。
c、后置通知
在目标方法执行之后的通知。
在后置通知中也可以选择性的接收一个JoinPoint来获取连接点的额外信息,但是这个参数必须处在参数列表的第一个。
在后置通知中,还可以通过配置获取返回值
一定要保证JoinPoint处在参数列表的第一位,否则抛异常
d、异常通知
在目标方法抛出异常时执行的通知
配制方法:
可以配置传入JoinPoint获取目标对象和目标方法相关信息,但必须处在参数列表第一位
另外,还可以配置参数,让异常通知可以接收到目标方法抛出的异常对象
e、最终通知
是在目标方法执行之后执行的通知。和后置通知不同之处在于,后置通知是在方法正常返回后执行的通知,如果方法没有正常返-例如抛出
异常,则后置通知不会执行。而最终通知无论如何都会在目标方法调用过后执行,即使目标方法没有正常的执行完成。另外,后置通知可
以通过配置得到返回值,而最终通知无法得到。
最终通知也可以额外接收一个JoinPoint参数,来获取目标对象和目标方法相关信息,但一定要保证必须是第一个参数。
f、五种通知执行的顺序
如果有多个通知想要在同一连接点运行会发生什么?Spring AOP遵循跟AspectJ一样的优先规则来确定通知执行的顺序。
在“进入”连接点的情况下,最高优先级的通知会先执行(所以给定的两个前置通知中,优先级高的那个会先执行)。
在“退出”连接点的情况下,最高优先级的通知会最后执行。(所以给定的两个后置通知中, 优先级高的那个会第二个执行)。
当定义在不同的切面里的两个通知都需要在一个相同的连接点中运行, 那么除非你指定,否则执行的顺序是未知的。
你可以通过指定优先级来控制执行顺序。
在标准的Spring方法中可以在切面类中实现org.springframework.core.Ordered 接口或者用Order注解做到这一点。
在两个切面中, Ordered.getValue()方法返回值(或者注解值)较低的那个有更高的优先级。
当定义在相同的切面里的两个通知都需要在一个相同的连接点中运行, 执行的顺序是未知的
(因为这里没有方法通过反射javac编译的类来获取声明顺序)。
考虑在每个切面类中按连接点压缩这些通知方法到一个通知方法,或者重构通知的片段到各自的切面类中 - 它能在切面级别进行排序。
1、无异常情况
所有通知order一样,执行顺序:around start -> before ->around start -> afterreturning -> after
before.order < around.order,执行顺序: before -> around start
afterreturning.order > around.order,执行顺序:afterreturning -> around end
after.order > around.order,执行顺序:after -> around end
after.order >afterreturning.order,执行顺序: after -> afterreturning
2、异常情况
所有通知order一样,执行顺序:around start -> before -> afterthrowing -> after
before.order < around.order,执行顺序: before -> around start
after.order > afterthrowing .order,执行顺序:after -> afterthrowing
g、五种通知的常见使用场景
前置通知 | 记录日志(方法将被调用) |
环绕通知 | 控制事务 权限控制 |
后置通知 | 记录日志(方法已经成功调用) |
异常通知 |
异常处理 控制事物 |
最终通知 | 记录日志(方法已经调用,但不一定成功) |
6、AOP的注解方式实现
Spring也支持注解方式实现AOP,相当于配置文件方式,注解配置更加轻量级,配置、修改也更加方便。
a、开启AOP的注解配置方式
b、将指定的类标志为一个切面
c、配置通知,制定切入点规则
前置通知:@Before
环绕通知:@Around
后置通知:@AfterReturning
异常通知:@AfterThrowing
最终通知:@After
**通过注解的配置等价于配置文件的配置
d、如果一个切面中多个通知重复使用同一个切入点表达式,则可以将该切入点表达式单独定义,后续引用
注意,在当前切面中通过注解定义的切入点只在当前切面中起作用,其他切面看不到。
e、在后置通知的注解中,也可以额外配置一个returning属性,来指定一个参数名接收目标方法执行后的返回值
f、在异常通知的注解中,也可以额外配置一个throwing属性,来指定一个参数名接受目标方法抛出的异常对象