Shahul Basha
From the archive · 2019

Getting Started with Spring Aspect-Oriented Programming (AOP)

What aspects, advice and pointcuts are, how Spring's @Transactional already uses them, and a small project with two aspects that log around a service and a DAO.

  • 7 min read
  • Spring, AOP, Java

Archive note I wrote this in 2019 on my old blog. It moved here in 2026 with the code and diagrams redone; library versions and APIs may have changed since. Original post.

Contents

In this article we’ll look at Spring AOP, one of Spring’s two core ideas. The other is dependency injection (DI), also called inversion of control (IoC).

What is AOP?

Let’s get the definition out of the way first:

AOP extends the traditional object-oriented programming (OOP) model to improve code reuse across different object hierarchies. The basic concept in AOP is an aspect, which is a common behaviour that’s typically scattered across methods or classes.

Bear with me; the examples will make this clearer.

Cross-cutting concerns

An aspect holds a cross-cutting concern: behaviour that isn’t part of your business logic, and that ends up scattered across the whole application when you don’t use AOP. A project can have any number of aspects.

Transaction management, security and logging are the most common examples, and there are others. To keep things simple, we’ll focus on these.

Here’s how a project might look without AOP:

Without AOP: every class mixes its business logic with the same logging, transaction and exception-handling code.

And with AOP:

With AOP: each concern lives in one aspect, reused by every class it applies to.

You’ve already been using AOP

If you haven’t worked with AOP directly, you might wonder why it isn’t used more. In fact, any application built on Spring uses AOP all the time; it’s just hidden from you in most cases. Here’s typical Hibernate code that manages the session and transaction by hand:

Manual transaction managementJava
public void saveUser(User user) {
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    session.save(user);
    transaction.commit();
    session.close();
}

Now let Spring manage the transaction, and focus on saving the User object. All it takes is the @Transactional annotation:

Spring-managed transactionJava
@Transactional
public void saveUser(User user) {
    Session session = sessionFactory.getCurrentSession();
    session.save(user);
}

In a real application you’d usually put @Transactional on the service method that calls the DAO rather than on the DAO itself, so that one transaction covers all the work the service does.

So how does Spring begin the transaction, and commit it once the object is saved? You guessed it: AOP.

Build a small AOP project

Enough theory; let’s write some code. We’ll build a basic project with our own aspects, which run automatically when certain methods are called.

1. Dependencies

Create a simple Maven project with these dependencies:

pom.xmlXML
<dependencies>
    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.5</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.2.2.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.2.RELEASE</version>
    </dependency>
</dependencies>

2. Project structure

The project should look like this. We’ll go through what each class does, then look at the aspects in detail.

SpringAOPDemoOutput
SpringAOPDemo
├── pom.xml
└── src/main/java
    └── org/aop/main
        ├── ApplicationMain.java
        ├── SpringConfig.java
        ├── aspect
        │   ├── ApplicationAspect.java
        │   └── UserAspect.java
        ├── dao
        │   └── UserAccountDAO.java
        ├── model
        │   └── UserAccount.java
        └── service
            └── UserService.java

3. The main class

The main class gets user details through the service class:

ApplicationMain.javaJava
public class ApplicationMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(SpringConfig.class);

        UserService service = context.getBean("userService", UserService.class);

        // Gets the list of all available users
        List<UserAccount> accountDetails = service.getAccountDetails();
        // Gets the details of one user, with the id 1
        UserAccount account = service.getUserAccountDetails(1);

        context.close();
    }
}

4. The service and the DAO

The service and DAO simply return user details. To keep things simple there’s no database; the DAO fakes one.

UserService.javaJava
@Component
public class UserService {

    @Autowired
    UserAccountDAO dao;

    public List<UserAccount> getAccountDetails() {
        return dao.getAccountDetails();
    }

    public UserAccount getUserAccountDetails(int userId) {
        return dao.getUserAccountDetails(userId);
    }
}

The DAO builds its list of users once the bean has been created:

UserAccountDAO.javaJava
@Component
public class UserAccountDAO {
    List<UserAccount> accList = new ArrayList<UserAccount>();

    @PostConstruct
    public void loadAccountList() {
        // Simulate a fetch from the database
        accList.add(new UserAccount(1, "jack@gmail.com", "Jack", "Ryan", 30));
        accList.add(new UserAccount(2, "sean@gmail.com", "Sean", "Mendes", 30));
    }

    public List<UserAccount> getAccountDetails() {
        return accList;
    }

    public UserAccount getUserAccountDetails(int userId) {
        return accList.get(userId);
    }
}

5. Configuration

In the Spring configuration class, enable AspectJ support explicitly with @EnableAspectJAutoProxy:

SpringConfig.javaJava
@Configuration
@ComponentScan("org.aop.main")
@EnableAspectJAutoProxy
public class SpringConfig {

}

6. The aspects

None of the code so far does any logging. All the logging happens in two aspects, ApplicationAspect and UserAspect. Let’s start with ApplicationAspect:

ApplicationAspect.javaJava
@Aspect
@Component
@Order(1)
public class ApplicationAspect {

    @Pointcut("execution(* org.aop.main.dao.*.*(..))")
    public void appPointcut() {}

    @Before("appPointcut()")
    public void executeDAOAdvice(JoinPoint joinpoint) {
        System.out.println("Started Logging for Application Aspect and user is authorized to get details from database");
    }
}

First, @Aspect declares the class as an aspect: a class that holds a cross-cutting concern. Here we want to log a line before every DAO method call, wherever the call comes from.

@Order(1) sets precedence. If more than one aspect applies to the same method, the one with the lower order value runs first.

Advice is the action taken before or after the method runs: the actual code invoked while the program executes. Here, executeDAOAdvice() is the advice, and it runs before the target method.

A pointcut decides where in the application an advice runs. It’s written in AspectJ’s pointcut expression language. We want to log before every DAO method call, so our expression is:

Java
@Pointcut("execution(* org.aop.main.dao.*.*(..))")

Read it from left to right: any return type (*), any class in the org.aop.main.dao package (dao.*), any method name (.*), with any arguments ((..)).

Don’t worry if that doesn’t click straight away; pointcut expressions are a topic of their own.

That says which methods the advice applies to. This line says it should run before them:

Java
@Before("appPointcut()")

UserAspect shows another way to use aspects:

UserAspect.javaJava
@Component
@Aspect
@Order(2)
public class UserAspect {

    @Pointcut("execution(* org.aop.main.service.*.getUserAccount*(..))")
    public void userPointcut() {}

    @AfterReturning(pointcut = "userPointcut()", returning = "result")
    public void executeAdvice(JoinPoint joinPoint, UserAccount result) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Object[] args = joinPoint.getArgs();
        System.out.println("Arguments passed to the method " + methodSignature.getName() + " is " + args[0]);
        System.out.println("Logging details of user returned from database " + result);
    }
}

Here we want to intercept user details whenever a method returns them, so we can act on them before they reach the caller. Compare that with the first aspect, which only logs before DAO calls.

@Order(2) means that if both aspects applied to the same method, ApplicationAspect would run first. In this example they never overlap: ApplicationAspect matches methods in the dao package, and UserAspect matches methods in the service package.

Java
@Pointcut("execution(* org.aop.main.service.*.getUserAccount*(..))")

This pointcut matches any return type (*), any class in the org.aop.main.service package, and any method whose name starts with getUserAccount, with any arguments.

This time we want the advice to run after the method returns, so we can see its result:

Java
@AfterReturning(pointcut = "userPointcut()", returning = "result")

Instead of @Before, we use @AfterReturning, which gives us the returned value as well as the arguments passed to the method.

Types of advice

  • Before advice runs before a join point. It can’t stop execution from reaching the join point, unless it throws an exception.
  • After returning advice runs after a join point completes normally, for example when a method returns without throwing.
  • After throwing advice runs if a method exits by throwing an exception.
  • After (finally) advice runs however the join point exits, normally or with an exception.
  • Around advice surrounds a join point such as a method call. It’s the most powerful kind: it can run custom behaviour before and after the call, and it decides whether to proceed to the join point at all or to short-cut it by returning its own value or throwing an exception.

The output

Running ApplicationMain prints:

Output
Started Logging for Application Aspect and user is authorized to get details from database
Started Logging for Application Aspect and user is authorized to get details from database
Arguments passed to the method getUserAccountDetails is 1
Logging details of user returned from database UserAccount [userId=2, email=sean@gmail.com, firstName=Sean, lastName=Mendes, age=30]

ApplicationAspect logs twice, once for each DAO call. UserAspect logs only for getUserAccountDetails, the one service method whose name matches its pointcut.

Back to @Transactional: it works like around advice. Spring’s transaction interceptor begins a transaction before your method runs, then commits it when the method returns, or rolls it back if the method throws.

Spring AOP vs AspectJ

AspectJ is the original AOP implementation for Java. Spring AOP is a lighter, proxy-based framework that reuses AspectJ’s annotations and pointcut language, but not its compiler or weaver.

Weaving is how aspect code gets wired into your application. Spring AOP weaves at runtime, by wrapping beans in proxies. AspectJ can weave at compile time, after compilation (on bytecode) or at class-load time.

Runtime proxies add a small cost to each advised method call. For most applications it’s negligible.

Conclusion

AOP is a powerful concept. Even if you never write an aspect yourself, understanding it helps you understand Spring-based frameworks much better. This was a simple introduction; the best way to learn more is to explore and experiment.

The sample project is on GitHub: shahulbasha/SpringAOP.

References

← All writing