这是一篇“老”文章。最近在研究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.
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. 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:
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:
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:
"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. 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.
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.