AOP介绍

  一、 AOP 介绍
AOP 提供了另外一种思考程序结构的角度,弥补了 OOP (面向对象编程)的不足。大家可以参看夏昕编写的《 Spring 开发指南》中的 AOP 部分,有简单明晰的对比和解释。就像刚开始理解 OO 概念一样,对于新手来说 AOP 也是非常抽象难以理解的,不能仅从一个概念上去定义 AOP 。假如我们有一个系统,分为好多个模块,每个模块都负责处理一项重要的功能。但是每个模块都需要一些相似的辅助功能如安全、日志输出等等。这就是一种交叉业务,而这种 交叉 非常适合用 AOP 来解决。(在 AOP 刚出现的时候被大家翻译成面向方面,而后来一部分人翻译成面向切面。谁更正确?也许在真正理解了 AOP 以后才能判断出来。)
1 AOP 术语
l          切面( Aspect ):切面是你要实现的交叉功能。它是应用系统模块化的一个切面领域。
l          连接点( Join point ):连接点是应用程序执行过程中插入切面的地点(在程序执行过程中某个特定的点)。在 Spring AOP 中一个连接点代表一个方法的执行。这个点可以是方法调用,异常抛出或者是要修改的字段。通过申明一个 import org.aspectj.lang.JoinPoint 类型的参数可以使通知( Advice )的主体部分获得连接点信息。
l          通知 Advice ):通知切面的实际实现(参考手册: Action taken by an aspect at a particular join point. )。它通知应用系统新的行为。通知包括好多种类,在后面单独列出。( Advice 一词在夏昕的《 Spring 开发指南》中被翻译为 处理逻辑
l          切入点( Pointcut ):切入点定义了通知应该应用在哪些连接点。通知( Advice )可以应用到 AOP 的任何连接点。通知( Advice )将和一个切入点表达式关联,并在满足这个连接点的切入点上运行(例如:在执行一个特定名称的方法时)切入点表达式如何和连接点匹配是 AOP 的核心 Spring 使用缺省的 AspectJ 切入点的语法。
l          引入( Introduction ):(也被叫做内部类型声明 “inter-type declaration” )引入允许你为已经存在的类添加新的方法和属性。 Spring 允许引入新的接口(以及一个对应的实现)到任何被代理的对象。
l          目标对象( Target Object ):目标对象是被通知对象。 Spring AOP 是运行时代里实现的,所以这个对象永远是一个被代理对象。
l          AOP 代理( AOP Proxy ):代理是将通知( Advice )应用到目标对象后创建的对象。在 Spring2.0 中对于使用最新引入的基于模式( schema-based )风格和 @AspectJ 风格切面声明的用户来说,代理的创建是透明的。(要理解这里的代理 “Proxy” ,需要理解代理 “Proxy” 模式: http://blog.csdn.net/qutr/archive/2006/07/27/987253.aspx
l          织入( Weaving ): 把切面( Aspect )连接到其它的应用程序类型或者对象上来创建一个被通知( advised )的对象。可以在编译时做这件事(例如使用 AspectJ 编译器),也可以在类加载或运行时完成。 Spring 和其他纯 Java AOP 框架一样, 在运行时完成织入。
通知( Advice )类型:
l          前置通知( Before Advice ):在一个连接点之前执行的通知,但这个通知不能阻止连接点钱的执行(除非它抛出异常)
l          返回后通知( After returning advice ):在一个连接点正常完成后执行的通知。例如:一个方法正常返回,没有抛出任何异常。
l          抛出后通知( After throwing advice ):在一个方法抛出异常时执行的通知。
l          Finally 后通知( After finally advice ):当某连接点退出的时候执行的通知(不论是正常返回还是抛出异常)。
l          环绕通知( Around advice ):包围一个连接点的通知,就像方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回他们自己的返回值域或抛出异常来结束执行。
2 Spring AOP 实现
Spring 中所有的通知都以 Java 类的形式编写。所以你可以像普通 Java 开发那样在 IDE Eclipse NetBeans )中开发切面。而且,定义在什么地方应用通用的切入点通常在 Spring 的配置文件中配置。
Spring 有两种代理创建方式:
1             如果目标对象 Target Object )实现了一个或多个接口暴露的方法, Spring 将使用 JDK java.lang.reflect.Proxy 类创建代理。
2             如果 目标对象 Target Object )没有实现任何接口, Spring 使用 CGLIB http://cglib.sourceforge.net/ )库生成目标对象的子类。在创建这个子类的时候, Spring 将通知织入,并且将对目标对象的调用委托给这个子类。注意用这种方式创建代理时需要将 Spring 发行包中 lib/cglib 文件加下的 jar 文件加载到工程文件中。使用这种代理时注意两点:
l          对接口创建代理优于对类创建代理,这样会产生更加松耦合的系统。
l          标记为 final 的方法不能被通知。
通过上面的一大堆名词解释应该对 AOP 以及 Spring 中的 AOP 有了一个大概的了解了。下面进一步详细解释 Spring 中的 AOP
 
二、创建通知
1 .前置通知( Before Advice
创建前置通知需要实现 org.springframework.aop.MethodBeforeAdvice 接口,该接口只有 before(Method method, Object[] args, Object target) throws Throwable 这个方法。我们可以这样理解前置通知:比如你到一个比较高档的饭店去吃饭,进门时会有礼仪小姐给你开门并且向您问好,这个礼仪小姐的行为就可以视为 前置通知
package springinaction.chapter03.createadvice;
 
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
 
publicclass BeforeAdvice implements MethodBeforeAdvice
{
         // 实现 MethodBeforeAdvice before 方法
         publicvoid before(Method method, Object[] args, Object target)
         {
                   System. out .println( "befor advice" );
         } //end before
        
} //end class BeforeAdvic e
2 .返回后通知( After returning advice
创建返回后通知需要实现 org.springframework.aop.AfterReturningAdvice 接口,该接口也只有一个方法: void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable 。同样我们也可以这样理解 After returning advice :当你在这家饭店用餐完后,礼仪小姐还会为您开门,并且欢迎您下次再来。这时礼仪小姐的行为就是 “After returning advice”
package springinaction.chapter03.createadvice;
 
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
 
publicclass AfterReturningAdviceImp implements AfterReturningAdvice
{
         publicvoid afterReturning(Object returnValue, Method method, Object[] arg2, Object target) throws Throwable
         {
                   System. out .println( "afterReturning" );
         }
}
3 .环绕通知( Around advice
创建环绕通知需要实现 org.aopalliance.intercept.MethodInterceptor 接口,同样要实现一个 invoke 方法,该方法有一个 MethodInvocation 类型的参数。 MethodInterceptor 能够控制目标方法是否真的被调用。通过调用 MethodInterceptor.proceed() 方法来调用目标方法。 MethodInterceptor 让你可以控制返回的对象,你可以返回一个与 proceed() 方法返回对象完全不同的对象。我们可以这样理解环绕通知:当你用餐完后,饭店要确保你已经付款了,在付款后停止再出售食物给你。
package springinaction.chapter03.createadvice;
 
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
 
publicclass AroundAdvice implements MethodInterceptor
{
         public Object invoke(MethodInvocation mi)
         {
                   Object obj = null ;
                   //do something....
                   return obj;
         } //end invoke(...)
        
} //end class AroundAdvice( )
4 .抛出后通知( After throwing advice
创建抛出后通知需要实现 org.springframework.aop.ThrowsAdvice 接口,该接口没有任何方法,但是要实现这个接口的类必须实现 afterThorwing(Throwable throwable) 或者 afterThrowing(Method method, Object[] args, Object target) 形式的一种,根据抛出的异常的类型恰当的方法将被调用。如果你在该饭店用餐后没有付钱就走或者多给钱他们没有找给你大概异常通知也就发生了。 J
package springinaction.chapter03.createadvice;
 
import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;
 
publicclass AfterThorwsAdvice implements ThrowsAdvice
{
         publicvoid afterThorwing(Throwable throwable)
         {
                   //do something....
         }
        
         publicvoid afterThrowing(Method method, Object[] args, Object target)
         {
                   //do something....
         }
}
一般来说环绕通知是用的最广泛的一个通知类型,但是他们( Spring 小组)鼓励我们用最合适的通知。例如我们有一个简单的验证身份的功能,那么我们只需要前置通知就可能实现我们要求的功能了。
 
三、定义切入点
在上面我们定义了各种通知。可以看到一个通知是要被执行的一个方法。光把通知定义出来不行,我们还要确定这些通知在我们的系统什么地方应用,否则通知是毫无用处的。这就是切入点的用处。切入点决定了一个特定的类的特定方法是否满足一条特定规则。如果确实符合,通知就应用到该方法上。
Spring 根据需要织入通知的类和方法来定义切入点。 Advice 根据他们的特性织入目标类和方法。 Spring 的切入点框架的核心接口是 Pointcut
publicinterface Pointcut
{
         ClassFilter getClassFilter();
         MethodMatcher getMethodMatcher();
}
         ClassFilter 接口决定了一个类是否符合通知的要求:
package org.springframework.aop;
publicinterface ClassFilter
{
         boolean matches(Class clazz);
         ClassFilter TRUE = TrueClassFilter. INSTANCE ;
}
       实现这个接口的类决定了以参数传入进来的类是否应该被通知。 ClassFilter. TRUE 是规范的的适合任何类的 ClassFilter 实例,他适用于创建只根据方法决定时候符合要求的切入点。
         ClassFilter 接口利用类过滤切面, MethodMactcher 接口可以通过方法过滤切面:
package org.springframework.aop;
import java.lang.reflect.Method;
 
publicinterface MethodMatcher
{
         boolean matches(Method method, Class targetClass);
         boolean isRuntime();
         boolean matches(Method method, Class targetClass, Object[] args);
        
         MethodMatcher TRUE = TrueMethodMatcher. INSTANCE ;
}
       MethodMactcher 接口有三个方法。 matches(Method, Class) 根据目标类和方法决定一个方法是否该被通知。 isRuntime() 方法被调用来决定 MethodMatcher 的类型。有两种类型:静态类型和动态类型。静态 pointcut 的意思是 Advice 总是被执行,此时的 isRuntime() 方法返回 false 。动态 pointcut 根据运行时方法的参数值决定通知是否需要执行, isRuntime() 方法返回 true
有一个非常重要的名词 Advisor :大多数切面是由定义切面行为的通知和定义切面在什么地方执行的切入点组合而成的。在 Spring 中把通知和切入点组合到一个对象中。 PointcutAdvisor 提供这些功能。
package org.springframework.aop;
publicinterface PointcutAdvisor extends Advisor
{
         Pointcut getPointcut();
}
         Spring 中的 RegexpMethodPointcut 让你利用正则表达式来定义切入点。如果你对正则表达式不了解的话,赶快去学学吧,非常重要的内容。(开个玩笑的说,用了正则表达式能使你的代码上档次 J , 下面列出定义切入点时经常使用的符号:
符号
描述
示例
·
(英文点 “.” )匹配任何单个字符
setFoo. 匹配 setFooB ,但不匹配 setFoo setFooBar
+
匹配前一个字符一次或多次
setFoo.+ 匹配 setFooB setFooBar ,但不匹配 setFoo
*
匹配前一个字符 0 次或多次
setFoo.* 匹配 setFooB setFooBar setFoo
/
匹配任何正则表达式符号
/.setFoo.+ 匹配 setFoo ,但不匹配 setFoo
 
下面引用《精通 Srping 》中的例子,这个例子讲的还是比较清晰的。如果前面的 Ioc 一章理解的很好的话,理解这个例子不是很难。难点在于 ProxyFactoryBean 这个类。
package jingtongspring;
 
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
 
publicclass LoggingBeforAdvice implements MethodBeforeAdvice
{
         protectedstaticfinal Log log = LogFactory. getLog (LoggingBeforAdvice. class );
        
         publicvoid before(Method arg0, Object[] arg1, Object arg2) throws Throwable
         {
                   log .info( "before: The invocation of getContent()" );
         }
}
下面是配置文件:
<? xml version = "1.0" encoding = "UTF-8" ?>
<! DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd" >
< beans >
         < bean id = "helloworldbean"
               class = "org.springframework.aop.framework.ProxyFactoryBean" >
                   < property name = "proxyInterfaces" >
                            < value > jingtongspring.IHelloWord </ value >
                   </ property >
                   < property name = "target" >
                            < ref local = "helloworldbeanTarget" />
                   </ property >
                   < property name = "interceptorNames" >
                            < list >
                                     < value > loggingBerforeAdvisor </ value >
                            </ list >
                   </ property >
         </ bean >
        
         < bean id = "helloworldbeanTarget"
                   class = "jingtongspring.HelloWord" />
        
         < bean id = "loggingBeforAdbisor"
                   class = "org.springframework.aop.support.RegexpMethodPointcutAdvisor" >
                   < property name = "advice" >
                            < ref local = "loggingBeforeAdvice" />
                   </ property >
                   < property name = "pattern" >
                            < value > .* </ value >
                   </ property >
         </ bean >
        
         < bean id = "loggingBeforeAdvice"
                   class = "jingtongspring.LoggingBeforeAdvice" />
</ beans >
 
四、创建引入
在前面我们列出了 Spring 的四种通知类型,并且有相应的简单例子。但是引入( Introduction )通知与其他类型的通知有所不同,它影响整个类。通过给需要消息的类添加方法和属性来实现。(看上去很神气,因为说道类那就是已经封装好了的,给类添加方法除非是继承该类。 Spring 中怎样实现?)
Spring 通过一个特殊的方法拦截器接口 IntroductionInterceptor 来实现引入。这个接口添加一个方法: boolean implementsInterface(Class intf) 这个方法对于引入如何工作非常关键。如果 IntroductionInterceptor 是为了实现指定接口,那么方法 implementsInterface 应该返回 true 。也就是说,对用这个接口声明的方法的任何调用将被委托给 IntroductionInterceptor invoke() 方法。 invoke() 方法负责实现这个方法,不能调用 MethodInvocation.proceed() 。它引入了新的借口,调用目标对象是没有用的。(注意:在本书中提到了一个 IntroductionMethodInterceptor 接口,我查看 Spring2.0 rc3 的源代码并没有该类,可能是在 Spring1.x 中存在的, IntroductionInterceptor 接口的定义形式是这样的: publicinterface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {} invoke() 方法估计是在它的两个父接口之一中存在。
下面看例子:
先实现一个接口:
package springinaction.chapter03.createadvice;
 
import java.util.Date;
 
publicinterface Auditable
{
         void setLastModifiedDate(Date date);
         Date getLastModifiedDate();
}
 
再实现一个类:
package springinaction.chapter03.createadvice;
 
import java.util.Date;
 
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.IntroductionInterceptor;
 
publicclass AuditableMixin implements IntroductionInterceptor, Auditable
{
         private Date lastModifiedDate ;
        
         publicboolean implementsInterface(Class intf)
         {
                   return intf.isAssignableFrom(Auditable. class );
         } //end implementsInterface(...)
        
         public Object invoke(MethodInvocation m) throws Throwable
         {
                   if ( this .implementsInterface(m.getMethod().getDeclaringClass())){
                            return m.getMethod().invoke( this , m.getArguments());
                   }
                   else {
                            return m.proceed();
                   }
         } //end invoke(...)
        
         public Date getLastModifiedDate()
         {
                   returnthis . lastModifiedDate ;
         } //end getLastModifiedDate()
        
         publicvoid setLastModifiedDate(Date lastModifiedDate)
         {
                   this . lastModifiedDate = lastModifiedDate;
         }
        
        
} //end class AuditableMixi n
 
AuditableMixin 类中实现了 IntroductionInterceptor 接口和业务接口 Auditable 。如果被申明调用方法的类型是 Auditable 类型, implementsInterface 方法返回 true 。这说明,对于 Auditable 的两个方法,拦截器必须提供实现,在 invoke 方法中实现。对于任何 Auditable 接口方法的调用,调用我们的拦截器。对于其他方法的调用我们让 Method Invocation 处理。
         Spring 提供了一个一个方便的类来处理我们的大多数应用,这个类是: DelegatingIntroductionInterceptor 应用这个类我们不再需要实现 invoke 这个方法了因为在 DelegatingIntroductionInterceptor 类中已经实现好了(可以参看一下源代码)。看下面的例子:
package springinaction.chapter03.createadvice;
 
import java.util.Date;
 
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
 
publicclass ImmutableMixin extends DelegatingIntroductionInterceptor implements Auditable
{
         private Date lastModifiedDate ;
        
         public Date getLastModifiedDate()
         {
                   returnthis . lastModifiedDate ;
         } //end getLastModifiedDate()
        
         publicvoid setLastModifiedDate(Date lastModifiedDate)
         {
                   this . lastModifiedDate = lastModifiedDate;
         } //end setLastModifiedDate(...)
        
} //end class ImmutableMixi n
    DelegatingIntroductionInterceptor 类也要实现你的混合类暴露的任何方法,并且将任何对这些方法的调用委托给这个混合类。因为 ImmutableMixin 类实现了 Auditable 接口,对这个接口的方法的所有调用都将调用我们的拦截器。任何其他方法委托给目标对象。如果你的拦截器要实现一个接口,你不想把它暴露成一个混合体,那么只要简单的将这个接口传递给 DelegatingIntroductionInterceptor 类的 suppressInterface() 方法就可以了(该方法在 DelegatingIntroductionInterceptor 类的父类 IntroductionInfoSupport 中实现)。还有一点:如果你的混合体要改变任何目标对象方法的行为的话,你就需要实现 invoke() 方法。
 
有了自己的引入通知后,我们需要创建 Advisor 。因为引入通知之应用在类层次上,所以引入有他们自己的 Advisor IntroductionAdvisor Spring 也提供了一个适合大多数情况的缺省实现。名为: DefaultIntroductionAdvisor ,它有一个以 IntroductionInterceptor 作为参数的构造函数。
 
在本章中使用了大量的 ProxyFactoryBean 来创建一个被通知的类。当你想明确的控制你的通知类如何组合时,这种是方法最好、最简单的选择。 ProxyFactoryBean 有很多控制行为的属性。在书中作者列出了一个详细的表格进行了说明,这里就不罗列了。详细的 ProxyFactoryBean 大家也可以看一下它的源代码。
 
 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值