`
softstone
  • 浏览: 478394 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

AOP != Interception

阅读更多

这是一篇“老”文章。最近在研究AOP,也尝试了JBoss的AOP解决方案,故而对AOP的核心思想、Interception体系结构在AOP中的地位产生了兴趣。Ted的这篇文章阐述精到,令我受益良多。

——————————

Setting the Story Straight: AOP != Interception
Recently, a number of authors and writers have been talking about AOP (Aspect-Oriented Programming), and how incredibly powerful and wonderful the whole thing is. And yet, for the vast majority of them, what they're really referring to is an old pattern called Interception. These people aren't stupid, nor are they misguided; while Interception is a powerful mechanism in its own right, it's not the same as AOP, and while they do share a number of defining characteristics, to understand AOP as Interception is like thinking OOP is data structs plus a bunch of function pointers. Just as we didn't understand objects until we got past the idea that objects are "just code and data", if we're to truly understand the potential power inherent in AOP, we need to get beyond this thinking that Interception and AOP are the same thing. They're obviously related at some points, but that's not the same thing as equality.

Definitions

For starters, because 90% of all arguments are based on differences in semantics, let's establish our terminology up front. Specifically,
  • Aspect-Oriented Programming:
    • "Aspect-oriented software development is a new technology for separation of concerns (SOC) in software development. The techniques of AOSD make it possible to modularize crosscutting aspects of a system." --AOSD homepage
    • "The central idea of AOP is that while the hierarchical modularity mechanisms of object-oriented languages are extremely useful, they are inherently unable to modularize all concerns of interest in complex systems. Instead, we believe that in the implementation of any complex system, there will be concerns that inherently crosscut the natural modularity of the rest of the implementation.
      "AOP does for crosscutting concerns what OOP has done for object encapsulation and inheritance--it provides language mechanisms that explicitly capture crosscutting structure. This makes it possible to program crosscutting concerns in a modular way, and achieve the usual benefits of improved modularity: simpler code that is easier to develop and maintain, and that has greater potential for reuse. We call a well modularized crosscutting concern an aspect." --An Overview of AspectJ
  • Interception: "The Interceptor architectural pattern allows services to be added transparently to a framework and triggered automatically when certain events occur." --Patterns of Software Architecture, Vol 2 by Schmidt et. al, p. 109
  • At first blush, these two sound amazingly similar--Interceptors fire at certain execution points in order to provide additional behavior to the object methods they hijack, and formalized AOP "weaves" code into declarative points in the source code to add additional behavior above and beyond the objects they crosscut. As with so many things, however, the difference is really in the details and the layer beneath what's immediately obvious. As part of this discussion, however, we need to define a few more terms in order to truly see why these two overlap so much:
    • Separation of concerns: A concern is essentially an atomic thing we want to model in our software system; for example, within an accounting system, the idea of an asset is a "concern". Therefore, we want to try and capture all ideas related to assets into a single coherent "thing" we call an object in OO systems. This allows us to keep better track of the abstractions within the system, allowing for better maintenance and functionality over time. Every programming language implicitly holds this as its central goal, even if what those concerns are and how they should be captured vary wildly.
    • Cross-cutting concern: A crosscutting concern, as the AspectJ literature states, is a part of the system that cannot be captured as a first-class cosntruct within normal object model semantics. For example, consider the act of tracing method entry and exit. Normally, this is something that can only be done directly within each class itself, via something like
      if (Debugging.isEnabled())    Debugging.trace("Method foo() entered");// . . .if (Debugging.isEnabled())    Debugging.trace("Method foo() exiting");
      As you can well imagine, this isn't behavior that can be inherited from a base class or provided via an internal reference to another class (the canonical way for OO systems to build clean separation of concerns is via inheritance or composition); each and every single method that wants to be traced is going to have to include these four lines of code. Other, more complex, crosscutting concerns common within enterprise systems include persistence (fighting the age-old object-relational impedance mismatch), synchronization behavior (how should my objects act in the face of being called from multiple threads?), and remoting (how should my objects allow for invocation from across process boundaries?). All of these are problems that aren't captured cleanly in a traditional OO environment.

    Interception and AOP

    The Interceptor pattern from POSA2, which I'm using as the canonical definition of interception until a better reference comes along, states that the problem is "Developing frameworks that can be extended transparently." The solution suggested starts by saying

    Allow applications to extend a framework transparently by registering 'out-of-band' services with the framework via predefined interfaces, then let the framework trigger these services automatically when certain events occur. (Footnore: In this context, "events" denotes application-level events such as the delivery of requests and responses within an ORB framework. These events are often visible only within the framework implementation.) In addition, open the framework's implementation so that the out-of-band services can access and control certain aspects of the framework's behavior.
    In other words, Interception implicitly relies on several things:
    1. Explicit support from the system for interception. POSA2 calls it the framework, COM+, .NET, servlets and EJB do it at container boundaries, but the underlying idea is the same--somewhere, some kind of construct (what I'll colloquially refer to as "The Plumbing") is what's providing the interception behavior, explicitly routing the call flow through the Interceptor as part of normal processing. This means that only those artifacts understood by the plumbing as first-class citizens can be so Intercepted--anything that's not within the purview of the plumbing cannot be intercepted. For example, in the servlet framework (starting from the 2.3 specification), Interception is provided via filters, allowing servlet authors the opportunity to "hook" the request and response to a servlet or JSP (or any other URL, for that matter). However, Interception can only apply to URL request/response semantics, so filters cannot, for example, intercept calls to ordinary Java classes or calls via other channels, like RMI, to other systems.
    2. The notion of a request/response channel. Interception implicitly relies heavily on the idea of request/response semantics, since interceptors almost always fire around method entry and exit. This means that a large number of object interaction semantics are left untouched and unavailable. (If this is a bit abstract, hang on--this will make more sense later.)
    3. Interception is almost universally a runtime construct. POSA2 makes explicit reference to this fact, citing the idea that these services won't always be desired or necessary for all cases, so the behavior from the Interceptor cannot be layered in statically--it needs to be registered at runtime. Unfortunately, this also implies a certain amount of runtime overhead, particularly if the interceptor's event model is fairly coarse- grained. For example, in the .NET context object architecture, method calls both into and out of an object inside of a context can be intercepted via the IMessageSink-and-friends architecture. However, once registered, a context interceptor must be invoked for every method call in and/or out of the context; there is no way to specify that the interceptor is only valid for "methods of this type".
    As a side note, thus far it would be possible to come away with the assumption that Interception is a remoting-only artifact, that it's only possible when control passes across process boundaries. This simply isn't true--for example, Java provides for a generic interception architecture via its Dynamic Proxy API, allowing Java programmers to create Interceptors around any object that implements any interface. (Dynamic proxies do this by constructing a concrete implementation of that interface that sits between the actual implementation and the caller; the caller only sees an interface reference, and has no idea that the thing on the other side of its reference isn't the original object.)

    AOP, on the other hand, relies on two fundamental constructs as part of its definition: join points, and advice. Join points are used to describe to the AOP system where exactly in the object model code should be "woven" into the original source base, and advice is the actual code to weave at those join points. The richness of the AOP system depends quite strongly on its join point model; for example, AspectJ defines a rich join point model that includes, among other things, field get/set operations, method call entry and exit, as well as method execution entry and exit. The difference between method call and method execution, by the way, is the difference of where the call is made, versus where the call is actually carried out. This means that:

    1. AOP systems need no underlying "plumbing". This statement is somewhat controversial, since obviously something needs to do the aspect weaving at some point in the execution lifecycle. However, this frequently isn't a runtime construct, but a compile-time or load-time operation; for example, AspectJ is a compiler, performing all the weaving at compile-time.
    2. AOP systems can weave against anything. An AOP system is limited only by its join point model--for example, AspectJ can easily define an object-relational persistence aspect that weaves around JavaBean classes, such that any field-get access against a non-transient field would immediately trigger a database access to obtain the latest version of that field, and any field-set access against a non-transient field would immediately trigger a database access to set that value into the database. While the performance of such an aspect system would be horrendous (think of all the round-trips!), the fact that this could be done against any JavaBean class, which could be used in a servlet system, a Swing app, or a J2ME device is powerful.
    As an example of where an AOP system is capable of capturing elements that a traditional Interception system couldn't, consider the very real problem today of SQL injection attacks in web-based systems.

    For those of you unfamiliar with this problem, the canonical example is that of the login screen of many webapps. If you're like me, the easiest way to authenticate somebody seems to be something like this:

    1. Present a form with the input elements "username" and "password" to the user. Submit the form to a servlet or HttpHandler.
    2. In that servlet or HttpHandler, construct a SQL query of the form "SELECT * FROM users WHERE username = '" + form.getParameter("username") + "' AND password = '" + form.getParameter("password") + "'" and execute it. If the query comes back with a row in the results, they're obviously OK; if not, they either don't exist in the system or they got the password wrong. Reject.
    The problem here is simple: what happens when somebody submits their username as "Bob' -- Ignore the rest of this line"? The SQL query turns into:
    SELECT * FROM users WHERE username = 'Bob' -- Ignore the rest of this line AND password = ''
    As the DBAs in the audience will tell us, the double-dash is the standard form of the SQL single-line comment, which means that we never even check if the data passed in the "password" field of the form was correct or not. It gets worse--what if the malicious attacker passes "Bob'; DROP TABLE users" as his input? Yikes!

    Solving this problem is obviously not something that we can fix simply by writing a base class or utility class that programmers can call--the necessary code to prevent this is already there, via the PreparedStatement in JDBC (similar support is there in .NET, too). The problem is that the programmer didn't use a PreparedStatement, whether out of ignorance or apathy or maliciousness.

    Clearly this isn't an area that an Interceptor can solve, since the problem doesn't fit cleanly into a request/response model--the code to do SQL access won't always fit around a single method call. It could be part of a larger method, like finding search hits. (Remember, this isn't just a problem on logins; it's going to be present everywhere user input is used to construct a SQL query or statement, so any INSERT, UPDATE, or DELETE statement is just as vulnerable.) But an AOP system, by virtue of its richer joinpoint model, could capture the call out to the JDBC Connection.createStatement() method and immediately add code to throw a runtime exception--in essence, crash the system so the faux pas gets caught during unit testing or QA and fixed. (Wes Isberg, of the AspectJ team, goes into much deeper detail about using AOP in this fashion.)

    As another point of consideration, thus far all discussion of these crosscutting concerns have been domain-independent: persistence, call tracing, security, and so forth. A large number of domain-specific crosscutting concerns creep up as part of any system, however; for example, the AspectJ team describes a simple example where an aspect is written for a simple graphical object system to track all "object moves", where the object is told to move to different coordinates on the screen, in order to force a repaint. This is obviously domain-dependent, and yet captures an obvious crosscutting concern, that of forcing the graphics system to refresh the screen contents after a change. Again, this is not something that can easily be captured using an Interception-based design.

    Conclusion

    Certainly, the line can be blurred between these two concepts without too much difficulty; for example, John Lam's CLAW implementation does runtime modification of .NET CIL to perform the necessary aspect code-weaving at load-time. Is this Interception or AOP? Arguably, it's somewhere in between the two--it has the benefits of being able to express itself in compiled form, only against those methods that are being explicitly named in the joinpoints, but suffers from a severely reduced join point model (again, just method entry and exit).

    Just as clearly, it's possible to build a system that is AOP in nature using Interception as the underlying implementation--EJB, servlets, ASP.NET, MTS/COM+ and the .NET context model all serve as standing testimony to that fact. The JBoss EJB implementation stands as quite possibly the most intense example of how Interception can provide AOP-motivated solutions, and definitely serves as a glowing testimony of how a well-constructed Interception framework can solve a whole host of problems.

    In summation, Interception can be seen as a simplified form of AOP (just as Visual Basic was long thought of as a simplified form of OOP), in that it defines an extremely simple join point model, that of method-entry and method-exit at component boundaries, with the advice woven in at runtime via interceptor callback registration. Certainly, Interception can be seen as the first big step to take to understanding AOP; it even makes sense in scenarios that would be horribly complicated by using an AOP system. But never be fooled into thinking that the story ends there. Interception is clearly best used at component boundaries, and AOP seems best used entirely within component boundaries.
    分享到:
    评论

    相关推荐

      Unity结合三导实现依赖注入跟AOP

      3. 创建拦截器:通过继承`IInterceptor`接口或使用`Unity.Interception`库中的`InterceptionBehavior`抽象类,我们可以创建自定义拦截器。例如,创建一个记录日志的拦截器: ```csharp public class ...

      Unity实现AOP所需要正确版本包

      2. **Unity.Interception.dll**:这个库提供了Unity对AOP的支持。它实现了拦截器模式,允许在调用对象的方法前后插入自定义的行为。在Unity中,拦截器通过实现`IInterceptor`接口或使用特性(如`...

      AOP_microsoft.practices包

      Microsoft提供了对AOP的支持,主要体现在Microsoft.Practices包中,包括Microsoft.Practices.EnterpriseLibrary系列和Unity.Interception。 Microsoft.Practices.EnterpriseLibrary是一套由微软开发的企业应用块...

      Unity结合三导实现依赖注入跟AOP Unity结合三导实现依赖注入跟AOP.zip

      container.AddNewExtension<Interception>(); container.RegisterType, Service>( new Interceptor(), new interceptionBehavior()); ``` 现在,每次调用`IService`的方法时,都会先执行`LoggingInterceptor`的`...

      _Interception__gg_

      在IT行业中,拦截(Interception)通常指的是动态代理或者AOP(面向切面编程)中的一个概念,它允许我们对程序执行过程中的某些特定行为进行拦截、修改或增强,而无需改动原始代码。这种技术广泛应用于日志记录、...

      aspecio:Aspecio,用于OSGi服务的AOP代理

      Aspecio,适用于OSGi的AOP代理 Aspecio是为OSGi R6提供AOP代理的“微框架”。 它为您的应用程序带来了面向组件的编程和面向方面的编程的混合。 通过Aspecio,您可以定义方面,以后可以选择这些方面以向组件添加行为...

      [EntLib]微软企业库5 0 学习之路 第十步 使用Unity解耦你的系统 PART4 Unity&PIAB

      PIAB是企业库的一部分,提供了一种实现面向切面编程(AOP)的方式,通过拦截器(Interception)来增强或修改对象的行为。 Unity.Interception是Unity的核心扩展,实现了拦截器功能,但它的具体实现并不包含在Unity基础...

      cglib-3.2.0.jar

      它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。最流行的OR Mapping工具hibernate也使用CGLIB来代理单端single-ended(多对一和一对一)关联(对集合的延迟抓取,是...

      Java的简单方法拦截库Tie : Java Method Interception-开源

      Java的Tie库是一个专注于简单方法拦截的开源库,它主要基于AOP(面向切面编程)的概念。在Java世界中,AOP是一种编程范式,允许开发者在不修改源代码的情况下,插入额外的逻辑,如日志、事务管理、性能监控等。Tie库...

      cglib必须导入的包和案例

      它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截) Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对...

      PostSharp2.0.8安装文件

      PostSharp是一款强大的.NET框架编译时代码处理工具,它引入了Aspect-Oriented Programming(面向切面编程,简称AOP)的概念,允许开发者在不改变主体业务代码的情况下,实现如日志、缓存、事务管理等跨切面的功能。...

      Microsoft.Practices包

      4. **Microsoft.Practices.Unity.Interception.dll**:这个DLL与Unity容器结合使用,提供了拦截器功能,进一步扩展了依赖注入的能力。它允许在调用对象方法之前和之后插入自定义逻辑,这在AOP实践中非常关键。通过...

      Puresharp:Puresharp是一个框架,可提供基本的API(AOP,IOC等),以通过可靠性,可伸缩性和性能而毫不妥协地高效构建高质量(.NET 4.5.2+和.NET Core 2.1+)应用程序。

      Puresharp API .NET Puresharp是.NET 4.5.2+ / .NET Core 2.1的一组功能,可通过生成灵活高效的应用程序来提高生产率。总览Puresharp主要提供用于构建专业应用程序基础的架构工具: 依赖注入容器面向方面的编程元...

      反射,监听,拦截问题

      在Java编程语言中,反射(Reflection)、监听(Listener)和拦截(Interception)是三个重要的概念,它们在软件设计和实现中发挥着关键作用。在这个小例子中,我们可以通过分析提供的文件来理解这三个概念。 首先,...

      京东-平台业务中心开源的iOS平台轻量级面向切面编程工具

      1. **方法拦截(Method Interception)**:允许开发者在方法调用前后插入自定义逻辑,例如添加日志记录、性能监控或异常处理。 2. **动态代理(Dynamic Proxy)**:通过创建对象的代理,可以在不修改原有代码的情况...

      AjaxPro及微软基础数据访问类库

      5. **Microsoft.Practices.Unity.Interception.dll**:Unity的拦截器扩展,用于AOP(面向切面编程),如日志、事务等。 6. **Microsoft.Practices.ServiceLocation.dll**:服务定位器接口,使得应用程序可以动态查找...

      通用权限 asp.net源码

      8. **AOP(面向切面编程)**:权限检查可能通过AOP实现,例如使用PostSharp或Unity interception,这样可以在不修改业务逻辑的情况下,全局处理权限验证。 9. **页面和控件授权**:ASP.NET允许在Web.config或页面...

      asp.net权限管理系统Demo源码_dotnet整站程序.rar

      7. **AOP(面向切面编程)**:在更复杂的系统中,可能会使用AOP来处理权限检查,例如使用PostSharp或Unity interception来在运行时动态地插入权限检查代码。 8. **MVC(模型-视图-控制器)框架**:如果Demo使用ASP...

      动态函数调用追踪方法

      2. **运行时拦截(Runtime Interception)**:使用AOP(面向切面编程)框架如Spring AOP,定义切点和通知,实现对特定函数调用的拦截。 3. **JVM内置API**:如Java的`java.lang.reflect.Method`类和`java.lang....

      RUCM用例图_v31

      2. **拦截(Interception)**:拦截是AOP(面向切面编程)的一种实现方式,它允许在方法调用前后插入额外的行为,比如日志记录、事务管理等。通过拦截器,开发者可以将这些横切关注点与业务逻辑解耦。 3. **请求...

    Global site tag (gtag.js) - Google Analytics