Practical AOP: Performance & Security
Practical AOP: Performance & Security
The previous lesson applied AOP to logging and auditing — concerns that are purely observational. This lesson tackles two concerns that can actively change the outcome of a method call: performance measurement (which informs optimization decisions) and method-level security checks (which can block execution entirely). Both are canonical cross-cutting concerns that belong in aspects rather than scattered across every service method.
Timing Aspects: Measuring Method Performance
A timing aspect wraps a method call, records the elapsed wall-clock time, and either logs it or stores it for aggregation. The natural tool is @Around advice, because it is the only advice type that controls when the actual method runs and therefore can bracket the call with timestamps.
Start with a marker annotation so you can opt individual methods in without wildcards:
Now write the aspect itself:
Apply it to any service method that you want measured:
StopWatch instead of System.nanoTime()? Spring's StopWatch is not thread-safe and should not be shared across threads, but within a single advice invocation it is perfect: it hides the raw-time arithmetic, labels the task, and formats the result cleanly. For production-grade metrics, publish to Micrometer (Timer.record(...)) instead of logging — that gives you Prometheus/Grafana dashboards for free.
Aggregating Timing Data with Micrometer
Logging individual timings is useful during development, but in production you want percentiles and histograms. Swap the log statement for a Micrometer Timer:
With Spring Boot Actuator on the classpath, the metric appears automatically at /actuator/metrics/{name} and is scraped by Prometheus if you include micrometer-registry-prometheus.
Method-Level Security with AOP
Spring Security already provides @PreAuthorize, @Secured, and @RolesAllowed — all implemented as AOP advice internally. Understanding how to build your own security aspect teaches you what Spring Security does under the hood and lets you implement custom access-control logic (business rules, ownership checks, feature flags) that Spring Security's expression language cannot express cleanly.
Define a custom annotation that carries a required permission string:
A simple security context (replace with your real principal source):
Now the aspect that enforces the annotation:
@Before and not @Around? Security checks are a binary gate: either the caller is allowed to proceed or they are not. @Before expresses that intent directly — throw an exception to block, return normally to allow. @Around would work too but adds unnecessary boilerplate (pjp.proceed(), return value handling) for a concern that never needs to inspect the return value.
Annotate any service method that needs protection:
Aspect Ordering When Multiple Aspects Compose
When a method is annotated with both @RequiresPermission and @Timed, two separate proxies (or one proxy with two interceptors) wrap the method. You must ensure the security check runs before the timer starts — you do not want to record timing for calls that are refused.
Use @Order on the aspect class to control precedence. Lower numbers execute in the outer position for @Before / @Around advice:
@Order, Spring may run it in any position relative to the others, and the position can change between restarts. Always apply @Order to any aspect whose relative position matters — security and transaction aspects are the most common examples.
Trade-offs and Pitfalls
- Self-invocation bypasses aspects. If
deleteUser()calls another method on the same bean, that inner call goes directly to the target object — not through the proxy — so any aspects on the inner method are silently skipped. Restructure to inject the bean into itself, or useAopContext.currentProxy()(requiresexposeProxy = trueon@EnableAspectJAutoProxy). - Overhead per invocation. AOP reflection adds a small overhead per call (typically a few microseconds). For methods called thousands of times per second, prefer Micrometer's
@Timedannotation (which uses a dedicated optimised path) over a custom aspect. - Exception transparency. Your advice must re-throw any checked exception that the target method declares. If you catch a
Throwablein@Around, always re-throw it — or the caller loses the original exception type. - Testing aspects in isolation. Annotate the aspect class with
@SpringBootTestand a minimal slice, or use AspectJ'sAspects.aspectOf()to obtain the aspect instance directly and call its advice methods in a plain unit test.
Summary
Timing aspects and security aspects are the two most impactful practical applications of AOP. A @Timed-driven @Around aspect gives you zero-noise performance instrumentation across any number of methods — plugging into Micrometer turns it into production-grade observability. A @RequiresPermission-driven @Before aspect gives you declarative, centrally-enforced access control without polluting business logic. Combine them with explicit @Order declarations and you have a composable, maintainable cross-cutting concern layer.