Introduction to AOP - Spring Boot
1. A Simple Business Method
Consider a student-management application:
@Service
public class StudentService {
private final StudentRepository studentRepository;
public StudentService(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
public Student createStudent(Student student) {
return studentRepository.save(student);
}
}
The responsibility of createStudent() is easy to understand:
- Receive student
- Save student
- Return saved student
This is the business logic of the method.
A business method answers a domain-related question, such as:
- How is a student created?
- How is an order placed?
- How is money transferred?
- How is inventory updated?
- How is a payment processed?
The method is currently focused on one responsibility, so its intent is immediately visible.
2. Real Applications Need More Than Business Logic
A production application may also need to:
- Log when a method begins
- Log whether it succeeds or fails
- Measure execution time
- Check whether the caller has permission
- Record an audit event
- Start, commit, or roll back a transaction
- Collect application metrics
- Retry temporary failures
- Cache a result
- Mask sensitive information
These requirements are important, but they are not part of the core process of creating a student.
If all of them are added directly to createStudent(), the method becomes difficult to read because most of its code no longer represents student-related business logic.
3. Business Logic vs Supporting Logic
A typical application contains two broad categories of responsibility.
Business Logic
Business logic represents the rules and operations of the application's domain.
Examples:
- Creating a student
- Placing an order
- Processing a payment
- Updating inventory
- Approving a loan
Business logic differs from one feature or domain to another.
Supporting or Infrastructure Logic
Supporting logic helps the application operate safely, reliably, securely, and observably.
Examples:
- Logging
- Security checks
- Transaction management
- Auditing
- Performance monitoring
- Caching
This logic is usually not specific to students, orders, payments, or inventory. The same supporting behaviour may be required across many unrelated features.
4. What Is a Concern?
A concern is a responsibility or area of interest within an application.
Examples:
- Student management is one concern.
- Payment processing is another concern.
- Logging is another concern.
- Security is another concern.
- Transaction management is another concern.
Core Business Concerns
Core business concerns normally belong to a particular feature or module.
- Student concern →
StudentService - Order concern →
OrderService - Payment concern →
PaymentService
These concerns are organized vertically. Each feature has its own classes and methods.
Cross-Cutting Concerns
Some concerns are required across many business modules. These concerns cut horizontally across otherwise unrelated features. They are therefore called cross-cutting concerns.
A concern becomes cross-cutting when the same kind of behaviour must be applied in many different parts of the application.
5. Common Examples of Cross-Cutting Concerns
Logging
The application may need to record:
- Which method was called
- When it started
- Whether it completed successfully
- Whether it failed
- Which arguments were supplied
The same logging requirement may apply to student, order, payment, inventory, and user services.
Security
Suppose only administrators are allowed to delete students. A permission check must run before the delete operation. Similar checks may also be needed before refunding payments, changing user roles, or approving requests.
Performance Measurement
Suppose we want to identify slow methods. A timer must run before the method, and the elapsed time must be calculated after the method finishes. Without a reusable mechanism, this timer would have to be added manually to every method that needs monitoring.
Auditing
Certain business operations may need a permanent record, including:
- Who performed the operation
- What operation was performed
- When it happened
- Whether it succeeded or failed
6. The Two Major Problems: Scattering and Tangling
Cross-cutting concerns create two major architectural problems when they are implemented directly inside business classes.
6.1 Scattering
Scattering means that the implementation of one concern is spread across many locations.
For example, logging code may appear in:
StudentServiceOrderServicePaymentServiceInventoryServiceUserServiceReportService
The logging concern is now scattered throughout the application.
Imagine an application containing 20 service classes with five methods each. Logging, auditing, or performance code may have to be added and maintained in approximately 100 different methods.
If the logging format or auditing policy changes, many classes may need to be modified.
Problems Caused by Scattering
- The same concern is repeated in many places.
- Developers may implement it differently.
- A required method may be missed.
- A small policy change may require edits across the entire codebase.
6.2 Tangling
Tangling means that a single method contains code belonging to multiple unrelated concerns.
public Payment processPayment(PaymentRequest request) {
// Logging concern
log.info("Processing payment");
// Security concern
securityService.checkPermission("PROCESS_PAYMENT");
// Performance concern
long start = System.currentTimeMillis();
try {
// Business concern
Payment payment = gateway.charge(request);
// Auditing concern
auditService.record("PAYMENT_COMPLETED");
return payment;
} catch (Exception exception) {
// Logging concern
log.error("Payment failed", exception);
// Auditing concern
auditService.record("PAYMENT_FAILED");
throw exception;
} finally {
// Performance concern
metrics.record(System.currentTimeMillis() - start);
}
}
This one method is responsible for:
- Business logic
- Logging
- Security
- Auditing
- Performance measurement
- Exception-related behaviour
The problem is not that these responsibilities are unnecessary. The problem is that they are implemented together in the same method.
Problems Caused by Tangling
- The business intent becomes difficult to see.
- Supporting behaviour may be applied inconsistently.
- Changes become more expensive.
- Developers can forget mandatory behaviour.
- Testing becomes more complicated.
- The method becomes harder to maintain.
Compare the two versions below.
Tangled Version
public Student createStudent(Student student) {
log.info("Method started");
securityService.checkPermission("CREATE_STUDENT");
long start = System.currentTimeMillis();
try {
Student result = studentRepository.save(student);
auditService.record("STUDENT_CREATED");
return result;
} catch (Exception exception) {
log.error("Method failed", exception);
throw exception;
} finally {
metrics.record(System.currentTimeMillis() - start);
}
}
Focused Version
public Student createStudent(Student student) {
return studentRepository.save(student);
}
The second version communicates the business operation immediately. The challenge is to keep the method this clean while still applying the required supporting behaviour.
7. First Attempt: Utility Methods
A common first solution is to centralize repeated code inside a utility class.
public class LoggingUtil {
public static void logStart(String methodName) {
System.out.println(methodName + " started");
}
public static void logEnd(String methodName) {
System.out.println(methodName + " completed");
}
}
The service can then call the utility methods:
public Student createStudent(Student student) {
LoggingUtil.logStart("createStudent");
Student result = studentRepository.save(student);
LoggingUtil.logEnd("createStudent");
return result;
}
This is better than duplicating the complete logging implementation, but it does not solve the fundamental problem.
The Invocation Is Still Scattered
Every business method must still contain calls such as:
LoggingUtil.logStart(...);
LoggingUtil.logEnd(...);
The logging implementation is centralized, but the decision about where and when to invoke it remains scattered across business methods.
Developers Must Remember to Call It
Nothing automatically guarantees that every required method invokes the utility.
A developer may:
- Forget to call
logStart() - Call
logStart()but forgetlogEnd() - Use inconsistent method names or messages
- Handle failure differently in different methods
Success and Failure Paths Still Require Boilerplate
LoggingUtil.logStart("createStudent");
try {
Student result = studentRepository.save(student);
LoggingUtil.logSuccess("createStudent");
return result;
} catch (Exception exception) {
LoggingUtil.logFailure("createStudent", exception);
throw exception;
}
The utility avoids repeating the internal logging implementation, but every business method still contains control-flow boilerplate.
Utility methods centralize what the concern does, but not where and when the concern is applied.
8. Second Attempt: Inheritance
Another possibility is to place common behaviour inside a base class.
public abstract class BaseService {
protected void logStart(String methodName) {
System.out.println(methodName + " started");
}
protected void logEnd(String methodName) {
System.out.println(methodName + " completed");
}
}
A service can extend this class:
@Service
public class StudentService extends BaseService {
public Student createStudent(Student student) {
logStart("createStudent");
Student result = studentRepository.save(student);
logEnd("createStudent");
return result;
}
}
Why Inheritance Does Not Solve the Problem
Inheritance shares the implementation of logStart() and logEnd(), but every method must still invoke them manually.
It also creates additional design problems:
- Java supports only single class inheritance.
- A service may already need to extend another class.
- Unrelated classes are forced into an artificial inheritance hierarchy.
- Logging, security, auditing, and other unrelated concerns may get mixed into one large base class.
- The parent-child relationship does not naturally represent these concerns.
Inheritance provides access to shared behaviour; it does not automatically execute that behaviour around selected methods.
9. Third Attempt: Manual Wrappers
A stronger solution is the wrapper or decorator approach.
Start with an interface:
public interface StudentOperations {
Student createStudent(Student student);
}
The real service implements the interface:
public class StudentService implements StudentOperations {
private final StudentRepository studentRepository;
public StudentService(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
@Override
public Student createStudent(Student student) {
return studentRepository.save(student);
}
}
Now create a wrapper that implements the same interface:
public class LoggingStudentService implements StudentOperations {
private final StudentOperations target;
public LoggingStudentService(StudentOperations target) {
this.target = target;
}
@Override
public Student createStudent(Student student) {
long start = System.currentTimeMillis();
System.out.println("Method started");
try {
return target.createStudent(student);
} finally {
System.out.println("Execution time: " + (System.currentTimeMillis() - start));
}
}
}
The caller receives the wrapper instead of directly receiving the real service:
The original business class remains clean because the wrapper surrounds the real method invocation.
The wrapper performs three fundamental actions:
- Run supporting behaviour before
- Call the real business object
- Run supporting behaviour after
This is close to the basic execution model used by Spring AOP.
AOP did not invent the idea of wrapping behaviour. It provides infrastructure for creating and applying such wrappers systematically.
10. Limitations of Manual Wrappers
Manual wrappers can work in a small application, but they become expensive at scale.
A Wrapper Is Needed for Every Service Type
A wrapper written for StudentOperations cannot automatically wrap unrelated service interfaces such as OrderOperations or PaymentOperations.
Separate wrappers may be required for:
- Student services
- Order services
- Payment services
- Inventory services
- User services
Multiple Concerns Require Multiple Layers
Suppose a service needs:
- Security
- Logging
- Performance measurement
- Auditing
The object chain may become:
Manual Object Construction Becomes Complicated
Someone must create all wrappers in the correct order and inject the final outer wrapper wherever the service is needed.
The Same Boilerplate Appears Again
Each wrapper contains similar delegation and exception-handling code. The concern has moved out of the business class, but wrapper creation and maintenance remain manual.
11. What a Better Solution Must Provide
A scalable solution should satisfy four requirements.
Requirement 1: Keep Common Behaviour in One Place
Logging, security, auditing, or performance logic should have a dedicated module rather than being repeated in business classes.
Requirement 2: Select Where the Behaviour Should Apply
We need a way to express rules such as:
- Apply to all service methods
- Apply only to methods annotated with
@TrackExecutionTime - Apply to every method whose name starts with
save
Requirement 3: Control When the Behaviour Should Run
Supporting logic may need to run:
- Before the method
- After the method
- Only after successful completion
- Only after an exception is thrown
- Around the complete method execution
Requirement 4: Avoid Modifying Every Business Method
We do not want every method to contain infrastructure calls:
public Student createStudent(Student student) {
startLogging();
startTimer();
checkSecurity();
// Business logic
}
The application should apply cross-cutting behaviour without requiring the caller or business method to manage it manually.
The mechanism should remain largely transparent to the business code and its caller.
12. Introduction to Aspect-Oriented Programming
This is the problem that Aspect-Oriented Programming, or AOP, is designed to solve.
AOP provides a way to modularize cross-cutting concerns instead of mixing them into business classes.
The essential idea is:
- Keep business logic in business classes
- Keep cross-cutting logic in a separate module
- Define where and when that logic should apply
- Combined behaviour at runtime
With AOP, the business method can remain focused:
public Student createStudent(Student student) {
return studentRepository.save(student);
}
13. OOP and AOP Solve Different Dimensions
Object-Oriented Programming and Aspect-Oriented Programming are not competitors. They organize different dimensions of an application.
OOP Organizes Business Capabilities Vertically
Each class groups behaviour related to a particular business capability.
AOP Organizes Behaviour Horizontally
AOP captures behaviour that spans several business capabilities.
OOP organizes the core business capabilities. AOP organizes behaviour that cuts across those capabilities.
AOP complements OOP by modularizing concerns that do not fit neatly inside one business class.
14. The Three Fundamental Questions in AOP
AOP can be understood through three questions.
Question 1: What Should Happen?
Examples:
- Log method execution
- Measure execution time
- Check permission
- Create an audit record
The code that performs this additional action is called advice.
Question 2: Where Should It Happen?
Examples:
- All methods in
StudentService - All service-layer methods
- Methods whose names begin with
save - Methods annotated with
@TrackExecutionTime
The rule that selects the relevant method executions is called a pointcut.
Question 3: When Should It Happen?
Examples:
- Before the method
- After the method
- After successful completion
- After an exception
- Around the complete method execution
The timing determines the type of advice.
A Simple Mental Model
- What should happen? → Advice
- Where should it apply? → Pointcut
- When should it run? → Advice type
15. What Is an Aspect?
An aspect is a module that contains a cross-cutting policy.
It normally brings together:
- The cross-cutting action that must run
- The rule that decides where it should run
- The timing of that action
For example:
PerformanceAspect
- Which methods should be measured?
- How should their execution time be measured?
Another example:
AuditAspect
- Which operations require auditing?
- What information should be recorded?
- Should both success and failure be recorded?
An aspect therefore represents a complete cross-cutting concern as a separate module.
16. How Spring AOP Applies an Aspect
Spring AOP normally creates a proxy around a Spring-managed target object.
Target Object
The target is the real object containing the business logic.
StudentService target object
Proxy Object
When AOP is applied, the caller normally receives a proxy instead of directly receiving the target.
StudentService proxy
The proxy looks compatible with the target from the caller's perspective, but it can intercept method calls and apply additional behaviour.
Runtime Flow
Conceptually, the proxy behaves like this:
public Student createStudent(Student student) {
logBefore();
try {
Student result = target.createStudent(student);
logSuccess(result);
return result;
} catch (Exception exception) {
logFailure(exception);
throw exception;
}
}
The actual business method remains unchanged. Spring creates and manages the interception layer instead of requiring us to write a separate wrapper for every service.
17. Manual Wrapper vs Spring AOP Proxy
18. Important Spring AOP Terms at a Glance
19. Final Mental Model
Without AOP:
Each business class contains its own cross-cutting logic.
With AOP:
The complete idea can be summarized as:
- Spring creates a proxy around the target bean
- When a method is called, the proxy intercepts it
- Advice runs before and/or after the business method
- The target business method executes
- The result returns to the caller
20. Key Takeaways
- Business methods should primarily express business intent.
- Logging, security, auditing, transactions, and monitoring are common cross-cutting concerns.
- Implementing these concerns directly creates scattering and tangling.
- Utility methods centralize implementation but still require manual invocation.
- Inheritance shares code but does not automatically apply behaviour.
- Manual wrappers demonstrate the right basic idea but do not scale well.
- AOP keeps cross-cutting logic in separate modules.
- Advice defines what happens.
- A pointcut defines where it happens.
- The advice type determines when it happens.
- An aspect combines the cross-cutting policy.
- Spring AOP commonly uses proxies to intercept calls to Spring-managed objects.
- OOP organizes business capabilities vertically, while AOP organizes shared behaviour horizontally.