AOP != Interception (译文)

 

         最近,一堆作者都在谈论AOP(Aspect-Oriented Programming),说AOP是如何如何的NB。但是,他们中的大部分主要谈论的实际上是一种原有的模式,叫做拦截器(Interception)。他们不笨,也没有被误导。拦截器(Interception)本身就是一个挺NB的机制,但是它不同于AOP。他们的确有很多共同的特性,但是把AOP理解成拦截器就像把面向对象理解成一堆数据结构和一堆函数指针的结合。就像我们不挥去对象就是“数据和代码的集合”这种想法就无法理解什么是面向对象一样,如果我们要真正理解AOP的潜力的话,我们必须超越认为“AOP和拦截器是一码子事”的这种想法。他们明显有一些共同点,但是他们不是一码子事。

 

定义 

 

         首先,因为90%的争论都来自于语义的分歧,我们把它们定义在先。

 

         AOP

 

                   AOP软件开发是一种新的关注点分离( separation of concerns, SOC )技术。AOSD技术使系统中各个切面的模块化成为可能。--AOSD homepage

                   AOP的中心思想是:尽管OO语义支持的模块化机制已经非常有用,但是它机制的局限性仍然无法满足将贯穿于复杂系统中的所有的关注点全部模块化的需求。我们相信在任何庞大复杂的系统中,这些无法满足模块化的关注点有必要被进一步模块化。

                   AOP对于切面关注点(Crosscutting concern)的实现就像OOP已经实现的对于对象封装或继承的语言支持一样——它提供语言机制完全获取结构中的各种切片(Crosscutting)。这使贯穿于系统中的各种关注点的模块化成为可能,并且实现了一般来说模块化所带来的优势:更简单更易于开发和维护的代码,更好的重用性。我们将这样一个“很好的模块化后的贯穿于系统的关注点”称为一个方面(Aspect, AOP中的A)。

 

         拦截器(Interception

 

                   “拦截器架构模式允许无形的将服务添加至框架中,并且当特定的事件被触发时自动调用。”--Patterns of Software Architecture, Vol 2 by Schmidt et. al, p. 109

 

         乍看之下,它们惊人的相似——拦截器在特定的执行点触发,为它所寄宿的对象方法提供额外的功能。而AOP形式上也是将代码“编织”进声明好的切入点来在它切入的对象方法的前后提供额外的行为。然而就像许多事情一样,区别总是在细节之上与表面现象之下。作为这个讨论的一部分,我们有必要再声明一些术语的定义便于真正看清它们的重叠面为何如此之大:

 

                   关注点分离( separation of concerns SOC : 一个关注点主要指我们想将之模块化的一个相对独立的事物。例如:在一个会计系统中,关于财产的考虑就是一个关注点。然后,我们便希望在一个对象中捕捉所有和财产有关的事物。这样我们就可以在系统中捕捉一些抽象的概念,从而随着时间的推移增进系统的维护性和功能性。尽管这些抽象概念很难定义并且分布于系统的各个角落,各种语言还是或多或少的将捕捉它们当做主要目标之一。

 

                   切面关注点( Cross-cutting concern ):AspectJ的文本状态就是一种切面关注点,它无法在一个普通的OO语义的系统中作为一个高阶构造(first-class cosntruct,译注:可参考高阶函数)被捕获。例如,跟踪一个方法的进入和返回。一般来说,这是只能每个对象自己来做的事情,就像:

if (Debugging.isEnabled())

    Debugging.trace("Method foo() entered");

 

// . . .

 

if (Debugging.isEnabled())

    Debugging.trace("Method foo() exiting");

我们可以想象,这不是一个可以很容易的通过继承基类或者通过一个内部引用用其他类可以实现的事情(一个OO系统解决关注点分离的规范途径就是继承与组合)。反正所有需要被如上代码跟踪的方法都需要复制粘贴这四行代码。还有更复杂的,切面关注点一般更明显的存在于企业级系统中,而这样的系统一般都包括持久化(将长期存在于系统中的对象存储于物理载体),同步(多线程并发访问中我的对象如何正常工作),远程(我的对象如何支持跨越进程边界的访问)。这些都不能被一个传统的面向对象环境所完全捕捉。

 

拦截器与AOP

 

         我以往一直引用POSA2(Pattern-Oriented-Software-Architecture 2)中对于拦截器的一段说法作为它的规范定义,其中这样说道:“框架开发可以被无形的扩展。”此解决方案是这样说的:

         通过预先定义接口并在框架中注册为“编外”服务将允许应用程序无形扩展,然后通过特定的事件使框架自动触发这些服务。(在这里,“事件”指得是像对象请求代理(ORB)框架中发送请求或响应这样的应用级的事件,这些事件一般属于框架内部可以掌握的。)此外,将框架的实现开放有助于“编外”服务可以访问和控制框架行为的特定方面。

         另一方面,拦截器暗含一些事项作为基础:

                   1.系统为拦截器提供支持。POSA2称为框架,COM+,.Net,Servlets 和 EJB 在容器边界提供对拦截器的支持,这些支持拦截器行为的底层机制都差不多——一种XX结构在一个XX地方(我总亲切的叫它们“下水道”),它们将拦截器与常规进程连结为一体。这意味着只有家里有厕所下水的“一等市民”才有资格使用拦截器——没有厕所的便无法拦截到任何东西。举个例子,在Servlet框架中(2.3版本以后)通过filter实现拦截器机制,为程序员提供了拦截对JSP页面(或者任何URL或者类似的东西)的请求与响应。尽管拦截器在这里可以处理URL 请求或响应,但是filter无法拦截一个普通的Java对象调用,或者RMI这样的通过其他通道的远程调用。

                   2.对于请求响应语义的支持。拦截器隐含着对于请求响应语义的依赖,所以拦截器通常在方法开始或结束时触发。这意味着很大一部分的对象交互语义被忽视并且是不能被支持的。(如果这个有点抽象,我们等等会再解释清楚。)

                   3.拦截器总体来说是一个运行时结构。POSA2明确的提到了这个事实,这些服务并不需要在所有的情况下进行调用,所以拦截器提供的服务并不能静态集成到系统中——它们需要在运行时注册。不幸的是,这同时明显增加了一定量的运行时开销,尤其是当拦截器的拦截事件模型粒度很大的时候。例如,在.Net上下文架构中,在上下文中的所有对象方法的调用均可以通过IMessageSink体系结构被拦截。一旦拦截器在这样的环境中被注册,每一次上下文中的方法调用都会触发这个上下文拦截器。我们没办法实现只针对于某一类型方法可用的拦截器。

 

 

         从侧面来说,说到现在似乎可以这样假设:拦截器是一种仅支持在远程交互中起到作用的机制,它仅在操作穿越进程边界时才起作用。这种假设明显不成立。举个例子,Java通过动态代理API提供了通用的拦截器架构,为Java程序员提供了在继承了接口的类上创建拦截器的机制。(动态代理通过基于原有实现在调用者与实现之间再做一层实现的方式实现拦截器,同时调用者只接触到调用的接口,不知道真正调用的是原有实现还是什么别的东西。)

 

         我们现在来说说AOP,它基于两个最基本的结构:接入点(Join points)与建议(Advice)。接入点通常在AOP系统中被解释为一个代码模块被“编织”进入原有代码的具体的点。而建议则是需要被编织的具体的代码模块。一个AOP系统是否丰富取决于它是否支持很强的接入点模型。例如,AspectJ定义了一个很丰富的接入点模型,包括:捕捉字典取值赋值过程,捕捉方法调用返回,以及捕捉方法执行返回等事件。方法调用和方法执行有啥区别?你懂的。。。

         这意味着:

                   1.AOP系统不需要“地下管道”。这一条的确有些争议,因为在整个软件执行过程中的确有些东西需要被编织进接入点。但是,通常情况下这不应该是一个在运行时执行的操作,而应该是在编译时或加载时执行。例如,AspectJ是一个编译器,把所有的“编织”行为在编译时完成。

                   2.AOP系统可以对系统中任何事物进行“编织”。AOP系统仅局限于它的接入点模型。例如,AspectJ可以简单的在一个JavaBean的基础上定义一个对象相关的持久化方面策略。当一个字段被访问时立即将数据库中最新的字段值同步入该字段,并且当字段值改变时将数据库中的值更新。尽管这策略的效率那是相当的低(数据库交互太频繁了)。。。但是这个方面的策略可以被附加到任何JavaBean以及Servlet,Swing控件,或者J2ME对象上,这就是这机制比较NB的地方。

 

         举一个AOP可以处理但是其他拦截器机制却无法处理的例子吧。知道有个很现实的问题叫做SQL注入攻击不?(译注:真不知道啊。。。。)

         好吧,我亲切的为不知道这个攻击方式的童鞋解释一下,比较经典的列子是这样的:我们实现一个登录Web界面简单来说一般两步:

 

         1.显示一个有用户名,密码和提交按钮的窗体,在用户点提交的时候把窗体提交至Servlet或者HttpHandler。

         2.在服务端代码中拼个SQL"SELECT * FROM users WHERE username = '" + form.getParameter("username") + "' AND password = '" + form.getParameter("password") + "'"执行它。然后得到个结果,接下来我们对结果做什么大家都懂的。。。

 

         关键问题在这里,如果有个孙子在UserName字段中填入了这样的东西 "Bob' -- Ignore the rest of this line" 会怎么样?拼出来的SQL就变成了这样:

 

SELECT *

FROM users

WHERE username = 'Bob' -- Ignore the rest of this line AND password = ''

 

         如果有童鞋是DBA的话就会告诉我们,双横杠在SQL语句中是个合法字符,并且意味着这之后的语句都会被注释掉。我们就在不检查BOB的密码是什么的情况下允许了他的登录。这还算好的,如果这个孙子传入了"Bob'; DROP TABLE users --",哦耶。(译注:喜欢拼SQL的童鞋注意了,以后用参数的方式处理这种问题)

 

         我们明显不能简单的用写个基类或者Util类让开发人员调用这样的方式解决这个问题,将拼SQL这种方式变成传参数进入SQL语句(Java的JDBC和.NET的ADO.NET都支持)才能正确的解决这个问题。问题就出在这个开发人员在写这部分代码的时候大意了,或者他对他自己的公司的网站很不爽还是什么其他的原因。

 

         明显这不是一个拦截器可以解决的问题,因为这个问题不是在请求响应模式可以解决的问题——SQL语句的执行不一定总是在一个方法的调用返回时才能捕捉,它可能是一大段代码的一部分(像查询这样的操作。这并不是一个只有登录才会发生的问题,而是任何有用户输入的地方都需要防止发生这样的事情,所以每一个Insert,Update或者Delete都是我们系统的弱点。)但是在AOP系统中,我们可以通过它提供的丰富的接入点模型来捕捉每一个JDBC Connection.createStatement()方法来添加语句针对此问题来抛出一个运行时异常。然后我们就很华丽的解决了这个问题(AspectJ team 的 Wes Isberg,描述了更多用AOP来解决这类问题的方法)

 

         另一方面考虑,到现在为止我们讨论比较多是一些“领域独立(domain-independent)”的方面,如:持久化,调用跟踪,安全认证,等等等等。尽管有这么多特定的领域贯穿于我们的系统,我们还是可以找出些例子:AspectJ Team在为一个图形展示系统提供支持时遇到过这样一个问题,他们需要跟踪“所有展示对象的移动”,以便于当对象移动至一个新的载体时强制界面刷新。这明显是一个不独立特定领域的问题,要在捕捉到此贯穿于整个系统的关注点时,触发界面刷新。这又是一个不容易被以拦截器为基础的系统捕捉的需求。

 

结论

 

         当然,AOP与拦截器的分界线的确很模糊,举例来说, John Lam 的 CLAW实现了编译时在.NET CIL(公共语言)中添加必要的方面模块。这算AOP还是拦截器。的确有争议,这像是介于两者之间的东西。它有它在编译时加载方面的优势,但也仅限于那些在接入点显式命名的方法,这消减了很大一部分的接入点灵活性(又是只能捕获方法的进入和返回。)

 

         可以明确的说,以拦截器作为底层机制构建一个AOP系统是可能的——EJB, servlets, ASP.NET, MTS/COM+ 和 .NET 上下文模型都验证了这一点。JBoss EJB更是这方面的典范。它证明了一个良好设计的基于拦截器的结构在复杂系统中的巨大作用。

 

         总的来说,拦截器提供简单的捕捉进入和返回方法的接入点机制,运行时动态的“编织”机制,所以拦截器可以被视为AOP的一个雏形(就像VB是OOP的雏形一样)。当然,理解拦截器是我们理解AOP过程中的一大步,甚至在有些系统中用AOP就好比杀鸡用牛刀,还不如用拦截器来的解决问题和方便。但是不要认为理解了拦截器便结束了。拦截器适用于在组件边界上处理边界明确的行为,而AOP更适用于处理边界内的特定问题。

 

译者注:

 

         其实的确没必要争论拦截器与AOP谁比较NB,就拦截器来说:很大一部分我们关注的企业级应用都可以通过拦截器来进一步模块化。就AOP来说:虽然支持更强大的处理机制,但是.Net世界好像还没有非常成型的解决方案,只能等微软啦,谁让我们是吃“软”饭的呢。

         通过这篇文章,我们更清楚了它们各自的意义,从而清楚了它们所处的阶段是不同的,也明确了我们继面向对象之后的道路是怎样的。

         这样,我们就不会被一些炒的很热的概念所迷惑了,哎,虽然这也是N年前的概念了。

 

  转载啥的就标明一下出处和作者就转吧,N年前的概念和文章了,随意了。

posted on 2010-11-05 21:18  ZetaZ  阅读(775)  评论(1编辑  收藏  举报