spring core
面向切面的Spring
1.AOP:面向切面编程
在OOP中模块化的关键单元是类(classes),而在AOP中模块化的单元则是切面。
AOP框架是Spring的一个重要组成部分。但是Spring IoC容器并不依赖于AOP,这意味着你有权利选择是否使用AOP,AOP做为Spring IoC容器的一个补充,使它成为一个强大的中间件解决方案。
AOP在Spring Framework中的作用
- 提供声明式企业服务,特别是为了替代EJB声明式服务。最重要的服务是声明性事务管理。
- 允许用户实现自定义切面,用AOP来完善OOP的使用。
横切关注点:散布于应用中多处的功能。横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect)。
AOP可以实现横切关注点与它们所影响的对象之间的解耦。
2.AOP定义
通知(Advice):
切面的工作被称为通知。通知定义了切面是什么及何时使用。
许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
Spring切面可以应用5种类型的通知:
-- 前置通知(Before):在目标方法被调用之前调用通知功能;
-- 后置通知(After):在目标方法被调用之后调用通知,此时不会关系方法的输出是什么(不论是正常返回还是异常退出);
-- 返回通知(After-returning):在目标方法成功执行之后调用通知;
-- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
-- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义行为。包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
连接点(Join Point):
在应用执行过程中能够插入切面的一个点。可以是调用方法时、抛出异常时、修改字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
切点(Point):
定义了何处。切点的定义会匹配通知所要织入的一个或多个连接点。通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名来指定这些切点。有些AOP框架允许我们创建动态的切点,可以根据运行时的决策(如方法的参数值)来决定是否应由通知。
切点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切点语法。
切面(Aspect):
切面是通知和切点的结合。通知和切点共同定义了切面的全部内容:是什么,在何时何处完成其功能。
引入(Introduction):
允许我们向现有的类添加新方法或属性。从而可以在无需修改这些现有的类的情况下,让他们具有新的行为和状态。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。
织入(Weaving):
把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。
在目标对象的生命周期里有多个点可以进行织入:
-- 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入到切面的。
-- 类加载期:切面在目标类加载到JVM时被织入。需要特殊类加载器,可以在目标类被引入应用之前增强该目标类的字节码。
-- 运行期:切面在运行的某个时刻被织入。一般情况下,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。
综上,通知包含了需要用于多个应用对象的横切行为;
连接点世程序执行过程中能够应用通知的所有点;
切点定义了通知被应用的具体位置(在哪些连接点);
其中关键的概念是切点定义了哪些连接点会得到通知。
3.Spring对AOP的支持
创建切点来定义切面所织入的连接点是AOP框架的基本功能。
Spring提供了4种类型的AOP支持:
-- 基于代理的经典Spring AOP;
-- 纯POJO切面;
-- @AspectJ注解驱动的切面;
-- 注入式AspectJ切面(适用于Spring各版本);
前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。
Spring在运行时通知对象:在代理累中包裹切面,Spring在运行期把切面织入到Spring管理的bean中,如下图,代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。
直到应用需要被代理的bean时,Spring才创建代理对象。
Spring只支持方法级别的连接点,如果需要方法拦截之外的连接点拦截之外的连接点拦截功能,可以利用Aspect来补充Spring AOP功能。
3.通过切点来选择连接点
Spring AOP使用AspectJ的切点表达式语言来定义切点。
Spring AOP所支持的AspectJ切点指示器:
AspectJ指示器 | 描述 |
arg() | 限制连接点匹配参数为指定类型的执行方法 |
@args() | 限制连接点匹配参数由指定注解注标的执行方法 |
execution() | 用于匹配时连接点的执行方法 |
this() | 限制连接点匹配AOP代理的bean引用为指定类型的类 |
target | 限制连接点匹配目标对象为指定类型的类 |
@target() | 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解 |
within() | 限制连接点匹配指定的类型 |
@within() | 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类型) |
@annotation | 限定匹配带有指定注解的连接点 |
Spring AOP配置元素以非侵入性方式声明切面:
AOP配置元素 | 用途 |
<aop:advisor> | 定义AOP通知器 |
<aop:after> | 定义AOP后置通知(不管被通知的方法是否执行成功) |
<aop:after-returning> | 定义AOP返回通知 |
<aop:after-throwing> | 定义AOP异常通知 |
<aop:around> | 定义AOP环绕通知 |
<aop:aspect> | 定义一个切面 |
<aop:aspectj-autoproxy> | 启用@AspectJ注解驱动的切面 |
<aop:before> | 定义一个AOP前置通知 |
<aop:config> | 顶层的AOP配置元素,大多数的<aop:*>元素必须包含在<aop:config>元素内 |
<aop:declare-parents> | 以透明的方式为被通知的对象引入额外的接口 |
<aop:pointcut> | 定义一个切点 |
在XML中使用<aop:around>元素声明环绕通知:
<aop:config> <aop:aspect ref="audience"> --引用audience Bean <aop:pointcut --定义切点 id="performance" expression="execution(* concert.Performance.perform(..))" /> <aop:around --声明环绕通知,引用切点 pointcut-ref="performance" method="watchPerformance" /> </aop:aspect> </aop:config>
配置参数化的切面:
<aop:config> <aop:aspect ref="trackCounter"> --将TrackCounter声明为切面 <aop:pointcut --定义切点 id="performance" expression="execution(* soundsystem.CompactDisc.playTrack(int)) && args(trackNumber)" /> <aop:before --声明前置通知,引用切点 pointcut-ref="trackPlayed" method="countTrack" /> </aop:aspect> </aop:config>
通过切面引入新功能:
<aop:aspect> <aop:declare-parents types-matching="concert.Performance+" implement-interface="concert.Encoreable" delegate-ref="encoreableDelegate" /> </aop:aspect>
delegate-ref属性引用了一个Spring bean作为引入委托。这需要在Spring上下文中存在一个ID为encoreableDelegate的bean。
4.注入AspectJ切面
如果在执行通知时切面依赖于一个或多个类,我们可以在切面内部实例化这些协作的对象。但更好的方式是,我们可以借助Spring的依赖注入把bean装配进AspectJ切面中。
AspectJ切面不需要Spring就可以织入到应用中。想使用Spring的依赖注入为AspectJ切面注入协作者,就需要在Spring配置中把切面声明为一个Spring配置中的<bean>。如下的<bean>声明会把criticismEngine bean注入到CriticAspect中:
<bean class="com.springinaction.springidol.CriticAspect" factory-method="aspectOf"> --获取引用 <property name="criticismEngine" ref="criticismEngine"/> </bean>
Aspect切面是由AspectJ在运行期创建的。所有的AspectJ切面都提供了一个静态的aspectOf()方法,该方法返回切面的一个单例。所以为了获得切面的实例,必须使用factory-method来调用aspectOf()方法而不是调用CriticAspect的构造方法。
Spring不能像之前那样使用<bean>声明来创建一个CriticAspect实例--它已经在运行时由AspectJ创建完成了。Spring需要通过aspectOf()工厂方法获得切面的引用,然后像<bean>元素规定的那样在该对象上执行依赖注入。
原文地址:http://blog.51cto.com/turnsole/2072944