We used AOP a lot at Ludi Labs. We were using Spring AOP. I started thinking, could we have use an interceptor pattern as a replacement for AOP? First off, the only reason I would even go down this line of thinking was because AOP presented some problems for us at Ludi. There were the annoying-but-trivial problems such as bloated logs and obfuscated stack traces. There were much more significant problems around performance.
Let's take a concrete example. Any time we store data, there was some "boilerplate" metadata we would store as well. For new data, this would be things like creation time and created by. For updates to existing data, it would be things like update time and updated by. Everyone stores this stuff, and often do it via database table definitions or even database triggers. We weren't storing our data in a traditional database, so we couldn't rely on those common solutions to this problem. Enter AOP.
We created advice for adding this metadata. We were storing user information in a thread local that could be accessed via an injectable stateless service. So it was easy to write advice that could figure out the created or updated info, and it was easy to write a pointcut to specify when to apply this advice.
Obviously this could have been done with an interceptor. But wait, there's more. Once we had this advice in place, there was more boilerplate stuff we wanted to keep track of. For any object we had some common metadata that could be applied to it. This included related objects (for example we could have a jpeg that was the cover art for an mp3,) user comments, and typical web 2.0 tags. We maintained all this together in an atomic object, but that could imply a lot of baggage to pass around.
So we added advice to disconnect this metadata when an object was read. We then re-used the previous advice to re-attach the metadata when the object was being written.
Again, this would have been easy to accomplish with an interceptor as well. There's one more wrinkle. The above metadata could be altered, and everything still worked. For example, an extra comment could be added by one person while somebody else was changing the description of a photo. This just wound up being more advice to write, no big deal.
So is this easy with an interceptor as well? I think this one could be a little trickier. Before we had a clear event in our component lifecycle: updating object data (say the title of a photo.) Now we're updating its data still (adding a comment) but it's data we want to manage separately even though it's persisted in the same data structure. If we are using the same component model, we probably need some kind of bifurcation of logic since these will be the same events. I guess the more OO way to do thing would be to model the events in the component model explicitly, i.e. something like saveData() and saveMetadata() not just save(). Or you could move your interceptor up the stack to distinguish when between these events. That's what would correspond more to the AOP way: define your point cut higher up.
The nice thing with AOP is that you don't have to change any component code to handle this refinement. It's that extra flexibility from having "a huge software solution." If you're able to plan out your needs ahead of time, then interceptors are fine.
This brings up the other bad part of interceptors. Take a look at the Interceptor interface in Hibernate. It has 15 methods. Of course there's the concrete class EmptyInterceptor which provides empty implementations of all 15, so you can extend it and only implement what you need. That's still ugly. You can imagine introducing interceptors on your own components, and then constantly having to expand the interceptor interface and any corresponding helper classes for it.