1、通行理论
在软件中,有些行为是通用的。比如日志、安全和事务管理,他们有一个共同的特点,分布于应用中的多处,这种功能被称为横切关注点(cross-cutting concerns)。
DI(依赖注入)有助于应用对象之间的解耦,而AOP可以实现横切关注点与他们所影响的对象之间的解耦。
应用切面的常见范例:日志、声明式事务、安全和缓存。
下面涉及的内容包括Spring对切面的支持,包括如何把普通类声明为一个切面以及如何使用注解创建切面。
看完本文后,可以回过头来看下面这一段话。
面向切面编程在Spring AOP中有四种类型的调用:方法调用的之前、后、异常增加其他方法,方法调用的前和后调用其他方法,将方法中的参数传递给其他方法,将一个崭新的接口实现作为父类接口给其他方法所属的方法,这里要重点理解“其他方法”和你本来要调用的方法是分开编写的,即低耦合的。你在调用某个方法时,Sping AOP在其他类中就为这个方法额外做了其他事情。
这四种类型中的前三种都是容易理解和使用的,无非是本方法运行时,额外运行了其他方法,第四种需要注意的是,Spring AOP在不改变原有接口和实现的情况下,允许以将本接口强制转化为新接口类型的方式,然后通过Spring的DI(依赖注入)进行新方法的调用,详细看 8、通过页面引入新功能。
2、什么是面向切面编程?
继承和委托是最常见的实现通用功能的面向对象技术。切面提供了取代继承和委托的另一种选择,而且在很多场景下更清晰简洁。
在使用面向切面编程时,我们仍然在一个地方定义通用功能,但是我们可以提供声明的方式定义这个功能以何种方式在何处应用,而无需修改受影响的类。
横切关注点可以被模块化为特殊的类,这些类被称为切面。
3、切面术语:通知、连接点和切点、切面
我用自己的话来通俗的说一下,切面是一个类,这个类的作用是完成对指定位置的指定动作的附属动作,指定位置首先有一个选择范围,可以是创建对象、方法调用、变量改变等等,这些选择范围就是连接点,你可以决定选取其中的一部分,这些被选取的部分就是切点,当然选择的过程在代码中体现,选好点后,一旦这些点被运行了,比如调用了一个类,切面里的类中的某个方法就会被调用,以完成别的功能,这个被调用的方法就是通知。
所以,可以简化为下面的关系:
切面:类。
切点:切面关注的指定代码的动作。(尽管可以是创建对象,方法调用,变量改变,但是Spring 3.0中的Spring AOP仅支持方法调用,体现在切点就是某个方法的类路径+)
连接点:可以作为切点的代码动作。
通知:方法,切点处额外多出的方法。
3.1、通知(Advice)-5种类型
切面的工作被称为通知。
通知定义了切面的什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。
它应该应用于某个方法被调用之前?之后?之前和之后?还是只在方法抛出异常时?
通知的5种类型
·Before-在方法被调用之前调用通知。
·After-在方法被调用之后调用通知,无论方法是否执行成功。
·After-returning-在方法成功执行之后调用通知。
·After-throwing-在方法抛出异常后调用通知。
·Around-方法被调用之前和调用之后各执行一次自定义的行为,通知把被通知方法“包裹”起来了。
3.2、连接点(Joinpoint)
连接点是在应用执行过程中能够插入切面的一个点,准确的说是程序运行中的某种时机,比如调用方法时,抛出异常时、甚至是修改一个字段时。
切面代码可以利用这些点(时机)插入到应用的正常流程之中,并添加新的行为。
3.3、切点(Poincut)
一个切面(什么是切面?)仅仅需要通知有限的连接点而不是全部的连接点。切点定义了连接点中的那些是通知需要产生作用的。
切点会匹配通知所要织入的一个或者多个连接点。
我们通常使用明确的类和方法名称来指定这些切点。
通俗来说,切点是相对于不同通知而言的有效连接点的集合。
3.4、切面(Aspect)
切面是通知和切点的集合,通知决定做什么?何时?切点决定在那些地方进行通知。比如,我们要在火车站的售票窗口或者代售点(切点)买票,时间是早晨8点到下午5点(通知-什么时候),动作是付钱买票(通知-做什么)。
3.5、引入(Introduction)
引入是一个过程的描述,指我们向现有的类添加新方法或属性。我们可以把一个类作为通知,引入到被通知的类中,从而在不改变被通知的类的情况下改变这个类。
3.6、织入(Weaving)
切面在指定的连接点(即通知在切点通知被通知的方法,通知被引入了被通知的类)被织入到目标对象中,括号中一直说被通知类增加了方法,这种增加的方法就是织入,说到底,织入是为被通知类创建了代理类,在代理类中增加了方法。从表面看来,被代理类增加了方法,即织入。
织入是将切面应用到目标对象来创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。
在目标对象的多个点可以进行织入。
通过在代理类中包裹切面,Spring在运行期将切面织入到Spring管理的Bean中。
编译期——切面在目标类编译时被织入。
·这种方式需要特殊的编译器。
·AspectJ的织入编译器就是以这种方式织入切面的。
类加载期——切面在目标类加载到JVM时被织入。
·这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。
·AspectJ 5的LTW(load-time weaving)就支持以这种方式织入切面。
运行期——切面在应用运行期间的某个时段被织入。
·一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。
·Spring AOP 就是在运行期,为目标对象动态创建一个代理对象来实现织入的。当拦截到方法调用时。在调用目标Bean之前,代理会执行切面逻辑。直到应用需要被代理的Bean时,Spring才创建代理对象。
4、Spring对AOP的支持
创建切点来定义切面织入的连接点是AOP框架的基本功能。
4.1、目前AOP框架是三足鼎立的局面
·AspectJ (http://eclipse.org/aspectj);
·JBoss AOP (http://www.jboss.org/jbossaop);
·Spring AOP (http://www.springframework.org)。
4.2、Spring提供了4种各具特色的AOP支持
前3种都是Spring基于代理的AOP变体,因此,Spring对AOP的支持局限于方法拦截。如果AOP需求超过了简单方法拦截的范畴(如构造器或属性拦截),那么应该考虑在AspectJ里实现切面,利用Spring的DI(依赖注入)把Spring Bean注入到AspectJ切面中:
·基于代理的经典AOP;
·@AspectJ注解驱动的切面;
·纯POJO切面;
·注入式AspectJ切面(适合Spring各版本)。
经典Spring AOP使用ProxyFactoryBean,由于有更简单的方法,这里不再做介绍。
4.3、Spring只支持方法连接点
AspectJ和Jboss除了方法切点,还提供了字段和构造器接入点。所以,单单使用Spring我们无法构建细粒度的通知,也无法使用构造器连接点。对于这种情况,我们可以利用Aspect来协助Spring AOP。
4.4、切点规定了那些连接点
我们使用切点来选择连接点,然后是不同的通知匹配不同的切点。在Spring AOP中,需要使用AspectJ的切点表达式来定义切点。
4.5、Spring AOP所支持的AspectJ切点指示器:
arg() 限制连接点匹配参数为指定类型的执行方法
@args() 限制连接点匹配参数由指定注解标注的执行方法
execution() 用于匹配是连接点的执行方法
this() 限制连接点匹配AOP代理的Bean引用为指定类型的类
target() 限制连接点匹配目标对象为指定类型的类
@target() 限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解
within() 限制连接点匹配指定的类型
@within() 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)
@annotation 限制匹配带有指定注解连接点
只有execution切点指示器是唯一的执行匹配,而其他的切点指示器都是用于限制匹配的。
4.6、编写切点,最后这些语句会被添加到xml配置文件中
注意:所写的路径都是接口类的路径,切点是在接口类中而不是具体的实现类中的。
不关注返回类型,不关注入参类型,在方法执行的时候引入切点
execution(* com.springination.springidol.Instrument.play(…))
增加限制-并且限定了切点的包路径(后者是多余的吗?)
execution(* com.springination.springidol.Instrument.play(…))
and within(com.springinaction.springidol.*)
在spring2.5之后,可以通过id来限定bean
execution(* com.springination.springidol.Instrument.play(…))
and within(com.springinaction.springidol.*)
and bean(eddie)
当然也可以使用反向操作
execution(* com.springination.springidol.Instrument.play(…))
and within(com.springinaction.springidol.*)
and bean(eddie)
and !bean(eddie2)
4.7、在XML中声明切面
AOP配置元素 描述
aop:advisor 定义AOP通知器
aop:after 定义AOP后置通知(不管被通知的方法是否执行成功)
aop:after-returning 定义AOP after-returning通知
aop:after-throwing 定义after-throwing通知
aop:around 定义AOP环绕通知
aop:aspect 定义切面
aop:aspect-autoproxy 启用@AspectJ注解驱动的切面
aop:before 定义AOP前置通知
aop:config 顶层的AOP配置元素。大多数的AOP:*元素,必须包含在 aop:config元素内
aop:declare-parents 为被通知的对象引入额外的接口,并透明地实现
aop:pointcut 定义切点
<?xml version="1.0" encoding="UTF-8"?>
context:annotation-config/
<bean id="bookDao" class="com.ss.dao.BookDaolmpl"/>
<bean id="myAop" class="com.ss.aop.MyAop"/>
<aop:config >
<!--切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象 -->
<aop:aspect id="mop" ref="myAop">
<!--切入点(Pointcut):匹配连接点(Join point)的断言。 -->
<!-- 前置通知(Before advice):在某个连接点(Join point)之前执行的通知,但这个通知不能阻止连接点的执行(除非它抛出一个异常)。
返回后通知(After returning advice):在某个连接点(Join point)正常完成后执行的通知。例如,一个方法没有抛出任何异常正常返回。
抛出异常后通知(After throwing advice):在方法抛出异常后执行的通知。
后置通知(After(finally)advice):当某个连接点(Join point)退出的时候执行的通知(不论是正常返回还是发生异常退出)。
环绕通知(Around advice):包围一个连接点(Join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。 -->
<aop:pointcut expression="execution(public * com.ss..*.*(..))" id="pointcut"/>
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
<aop:after-returning method="returning" pointcut-ref="pointcut"/>
<aop:after-throwing method="throwing" pointcut-ref="pointcut" throwing="ex"/>
<!--<aop:around method="around" pointcut-ref="pointcut"/> -->
</aop:aspect>
</aop:config>