AOP solves some problems, but is not useful in all cases.
Typically, AOP provides tools to manage separation of concerns. In many projects, you'll find yourself writing the same code repeatedly. A typical example is checking access:
if(!user.hasAccess(functionality)) throw new SecurityException();
// Functionality implementation.
This is not clean code, because the problem of checking access is spread across the whole code base. It is error prone, which is not what you want in general, but even worse here, because an error is a security flaw.
AOP is definitively useful here, because it will allow you to define security at one place and functionality at another.
You'll find other patterns, like connecting to a database, doing some operation, and disconnecting. You'll have to handle all special cases (SQL error, network error, etc . . .) and be sure you rollback cleanly if something happens and disconnect properly. AOP could also help you here, avoiding the need to have all the error handling boilerplate spread over multiple functionalities.
Another problem is synchronization. As the system gets more and more distributed (multicore, network, cloud, GPGPU, etc . . .) it is of increasing importance.
Definitively, AOP is useful in some cases.