AOP in Principle: Cross-Cutting Concerns and Proxies

bee2026-10-0832 min read0 views
Logging, transactions and permission checks on every method — how does that repetition get extracted? From cross-cutting concerns to proxies, every AOP term explained with one diagram and one animation.
1 / 116
Section
0. Thirty seconds to get it
2 / 116

AOP in one sentence: take the code that "every method has to do on the side", pull it out of every method, write it once, and let the container quietly paste it back at runtime. What gets pulled out is usually logging, transactions, authorization or rate limiting; what pastes it back is the proxy object — a stand-in with your class's name and shape that secretly does a bit more work.

3 / 116
类比

what does a dynamic proxy actually proxy? Answer: nothing at all. Think of putting a case on a phone. The case has holes for every button and the charging port, feels identical, and pressing power still switches off the phone — but the case adds a drop-resistant buffer on the side. The phone itself never changed; what changed is which device gets handed to you: the container never hands you the bare phone, only the cased one. Your business code (the phone) does not lose a single line — the extra capabilities live on the case.

4 / 116
类比

an advice chain is an onion, or a sequence of security gates. One call passes inward layer by layer: gate 1 checks your badge (authorization), gate 2 stamps the clock (logging), gate 3 issues a work permit (begin transaction); only at the very core does the actual work happen (the target method). Coming back out is reversed — the innermost gate exits first: return the permit and commit the transaction, then finish the clock stamp with the duration. So "first in, last out" is the master key to every advice ordering question.

5 / 116
Diagram
Figure · The AOP knowledge map
Figure · The AOP knowledge map
6 / 116

After this article you should be able to answer three questions:

7 / 116
  1. What exactly is a "cross-cutting concern", and why does it not belong inside a business method?
  2. Which real-world action do aspect / pointcut / join point / advice / weaving each correspond to?
  3. Why is Spring AOP merely "method-level proxying", and what are its three failure scenarios?
8 / 116
Section
1. The scene: a Service drowned in boilerplate
9 / 116

Cut to a code review. The ask is ordinary: every Service method should log its inputs, outputs and duration; key write operations need a transaction; admin endpoints need a permission check. Three sentences to describe, but in code it looks like this:

10 / 116
Code
Codejava
@Servicepublic class OrderService {    private static final Logger log = LoggerFactory.getLogger(OrderService.class);    public Order createOrder(Long userId, OrderDTO dto) {        long start = System.currentTimeMillis();        log.info("createOrder in: userId={}, dto={}", userId, dto);        // ↓↓↓ the only lines that actually matter ↓↓↓        Order order = new Order(userId, dto);        orderRepository.save(order);        // ↑↑↑ the only lines that actually matter ↑↑↑        log.info("createOrder out: orderId={}, took={}ms", order.getId(),                 System.currentTimeMillis() - start);        return order;    }    public void cancelOrder(Long orderId) {        long start = System.currentTimeMillis();        log.info("cancelOrder in: orderId={}", orderId);        Order order = orderRepository.findById(orderId).orElseThrow();        order.cancel();        orderRepository.save(order);        log.info("cancelOrder out: took={}ms", System.currentTimeMillis() - start);    }}
Notes
  • Every method opens by grabbing a timestamp and logging its inputs
  • Every method closes by logging its output and duration
  • The few middle lines are the method's actual job; everything else is copy-paste to serve logging
11 / 116

Ten Services with twelve methods each is 120 copies. Change the log format once and you edit 120 places — that is exactly what makes cross-cutting logic painful: it has almost nothing to do with the business, yet it changes just as often.

12 / 116
Tip

to tell whether code is a "cross-cutting concern", one signal suffices — does it appear in many methods with near-identical content? Logging, transactions, authorization, rate limiting, caching and monitoring all fit.

13 / 116

Set "keep copy-pasting" against "extract one aspect" and the payoff, plus the price, are obvious at a glance:

14 / 116
Diagram
Figure · Copy-paste try/catch+log vs one aspect
Figure · Copy-paste try/catch+log vs one aspect
15 / 116

So how do 120 copies actually get "moved out" of the business methods? This six-frame animation walks the whole collapsing process — watch frame ⑤ in particular: nothing in the source is rewritten, a shell is simply put on at runtime:

16 / 116
Animation
Animation · 120 copies collapse into one aspect
Animation · 120 copies collapse into one aspect
17 / 116
Section
2. Core concerns vs cross-cutting concerns
18 / 116

Sort that pile of code by "direction" and the problem becomes clear:

19 / 116
Diagram
Figure 1 · Vertical business vs horizontal aspects
Figure 1 · Vertical business vs horizontal aspects
20 / 116
  • Core concerns (vertical): Controller → Service → Repository answers "what is this business flow", running up and down the call chain
  • Cross-cutting concerns (horizontal): logging, transactions, authorization... they belong to no single business, yet must cut "across" nearly every method
21 / 116

The typical cross-cutting concerns and the cost of skipping them:

22 / 116
Table
Cross-cutting concernWhat it doesCost of skipping
LoggingRecords inputs, outputs, duration, errorsProduction incidents become untraceable
TransactionsAll-or-nothing over a group of operationsData inconsistency
AuthorizationDecides if the current user may actPrivilege escalation
Rate limitingCaps requests per unit timeOverwhelmed by traffic spikes
CachingReuses results, avoids recomputePoor performance, heavy DB load
MonitoringMetrics, tracing, instrumentationA system with no observability
23 / 116

In one line: core concerns decide what business to do; cross-cutting concerns decide what else must happen along the way. The former naturally lives inside methods; the latter naturally wants to be "pulled out" — and AOP exists to do the pulling.

24 / 116
Section
3. Every AOP term, in one pass
25 / 116

Many find AOP hard because the vocabulary scares them off first. Hold on to one thread: pointcuts select, advice executes, an aspect packages both, and a proxy makes it real.

26 / 116
Table
TermIn one sentenceIn Spring
AspectThe packaged cross-cutting logicA class annotated @Aspect
Join pointA point in execution where logic may be insertedEach interceptable method invocation
PointcutAn expression selecting which join points to intercept@Pointcut("execution(...)")
AdviceThe code that actually runs at a join point@Before / @After / @Around...
TargetThe original object being proxiedYour UserServiceImpl
ProxyTarget plus advice, standing in for itObjects built by JDK Proxy / CGLIB
WeavingThe act of applying aspects to targetsThe step that creates the proxy
27 / 116

String them into one sentence: an aspect uses a pointcut to select methods from the join points, attaches advice to them, and at runtime a proxy wraps the target to perform the weaving. Hold that chain and the terms stop being scattered nouns.

28 / 116
Section
4. Three moments for weaving
29 / 116

"Weaving" sounds mystical; it is simply the act of inserting advice into a target method's execution flow. It can happen at three moments:

30 / 116
Table
MomentWho does itTraitsExample
Compile timeAdvice is compiled into bytecodeNo runtime proxy, best performanceAspectJ compile-time weaving
Load time (LTW)Bytecode is rewritten as classes loadNeeds an agent / aop.xmlAspectJ load-time weaving
RuntimeA proxy object is generated while runningZero onboarding cost, non-invasiveSpring AOP dynamic proxies
31 / 116

Spring AOP takes the third path: it generates a proxy at runtime. That also fixes its boundaries — a proxy is essentially a look-alike stand-in and can only intercept calls that go through it.

32 / 116
Section
5. The proxy pattern: write a static proxy first
33 / 116

The fastest way to understand Spring AOP is to hand-write the "dumbest" proxy. Suppose there is a user service interface:

34 / 116
java
public interface UserService {    User findById(Long id);    void save(User user);}
35 / 116

Write a static proxy that forces logging in:

36 / 116
Code
Codejava
public class UserServiceLogProxy implements UserService {    private final UserService target;   // the real target object    private static final Logger log = LoggerFactory.getLogger(UserServiceLogProxy.class);    public UserServiceLogProxy(UserService target) {        this.target = target;    }    @Override    public User findById(Long id) {        log.info("findById start, id={}", id);        User result = target.findById(id);      // forward to the real target        log.info("findById end, result={}", result);        return result;    }    @Override    public void save(User user) {        log.info("save start, user={}", user);        target.save(user);        log.info("save end");    }}
Notes
  • UserServiceLogProxy implements the same interface as UserServiceImpl, so from outside it "looks identical"
  • Every method follows the same pattern: log before → call the same method on target → log after
  • The real work still happens in target; the proxy just "wraps" it, but it successfully moves logging out of the business code

Trap: the fatal flaw of static proxies is not that they are hard to write, but that you can never finish writing them. N methods on the interface means N proxy methods; add a method and every proxy must change. That is one aspect — add transactions and authorization and proxy classes explode combinatorially.

37 / 116

The key takeaway is that the caller actually holds the proxy, and the proxy quietly meddles before and after forwarding. Spring AOP simply swaps "hand-written proxy" for "runtime-generated proxy". The animation shows exactly this call chain:

38 / 116
Animation
Animation · How a call gets intercepted
Animation · How a call gets intercepted
39 / 116
Section
6. AOP vs OOP (a favorite interview question)
40 / 116

Interviewers love to ask: "Does AOP replace OOP?" The standard answer: it does not replace, it complements.

41 / 116
  • OOP uses inheritance and composition to express "is-a" and "has-a" — vertical structural relations
  • AOP expresses "where else something must happen" — horizontal behavioral relations
  • OOP slices the business into classes; AOP gathers the cross-cutting logic back into one place
42 / 116

Another analogy: OOP drew the company as a department tree; AOP installs one shared stamping machine for "the process every department must go through". The structure (OOP) is not overturned — it just gains a cross-department way to express things.

43 / 116
Key point

AOP complements OOP. It addresses precisely the gap in OOP — some logic genuinely belongs to "behavior shared by many classes" and fits badly inside any single one.

44 / 116
Section
7. Spring AOP's boundaries
45 / 116

Once you grasp "proxy", the boundaries follow naturally. It is not omnipotent magic; it is method-level proxying:

46 / 116
Table
CapabilitySupportedNotes
Method invocation interceptionYesThe only join point type in Spring AOP
Field access interceptionNoNeeds AspectJ compile-time weaving
Constructor interceptionNoSpring AOP cannot do it
Beans inside the containerYesOnly container-managed objects get a proxy
Objects you new yourselfNoNot in the container, so never enhanced
Self-invocation inside a methodNoBypasses the proxy; advice never runs
47 / 116

Three boundaries to remember:

48 / 116
  • Method level only: field reads and constructor calls are out of reach — that is AspectJ's compile-time territory
  • Container beans only: an object created with new has no proxy and thus no advice
  • Self-invocation fails: this.method() goes straight to the original object and bypasses the proxy — the most common cause of "my aspect is not firing"
49 / 116
Section
8. Try it: watch the advice chain attach
50 / 116

The demo below visualizes how the proxy inserts advice before and after a call. Switch between JDK / CGLIB / self-invocation to see exactly when the advice chain works and when it breaks:

51 / 116
Kernel lab
TeaVMWalk into a proxy: one full advice chainidle
Switch JDK / CGLIB / self-invocation and watch when the chain fails
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
52 / 116

Focus on the "self-invocation" mode: a this.xxx() call inside the target never passes through the proxy, so not a single piece of advice fires.

53 / 116
Section
9. Decision: when should you reach for AOP
54 / 116
Decision
Decisionyou must add "execution duration logging" uniformly to 8 Services with 40+ public methods, and new methods keep coming. What do you do?
55 / 116
Section
10. Hands-on labs: weaving, before and after
56 / 116

Run the four experiments in order, thirty seconds each. The first is the most striking — the same method with no aspect versus with one woven in produces completely different call stacks:

57 / 116
Kernel lab
TeaVMBefore and after weavingidle
Watch the clean stack in plain, the extra layers in aspect, then dive inside the chain
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
58 / 116

You already understood the hand-written static proxy above; now see how the advice chain runs when the container generates the proxy for you. Switch to "self-invocation" and not one piece of advice prints — live evidence for the boundary in Section 7:

59 / 116
Kernel lab
TeaVMAOP proxy · advice chainidle
Try jdk / cglib / self / error in turn; note every advice vanishes under self
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
60 / 116

Now the motivation question: why do we need Spring at all? Hand-writing twelve steps (managing object lifecycles and wiring yourself by hand) versus letting the container do it — the cost option lays out both the code you save and the complexity you buy:

61 / 116
Kernel lab
TeaVMWithout Spring vs with Springidle
Walk raw → spring → boot, then open cost to see price and payoff together
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
62 / 116

One layer deeper: who actually puts the case on the phone, and when? The auto-proxy creator — a BeanPostProcessor (a hook the container inserts around the creation of every bean) registered in the container that asks, at each bean's after-init stage, "do you match any pointcut?", and builds the proxy right there if you do:

63 / 116
Kernel lab
TeaVMAuto-proxy creatoridle
bpp shows where it intercepts, candidate shows the test, factory shows how the proxy is configured
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
64 / 116

Terms and shells are settled; the real hurdle left is that "the advice chain goes in, then out". This experiment hangs all five advice types on one method. Start on normal and copy the log order down — it contradicts most people's first intuition: the first half of @Around runs before @Before:

65 / 116
Kernel lab
TeaVMThe real execution order of the five advice typesidle
On normal, count the log lines in order; then switch to order and watch two aspects interleave layer by layer
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
66 / 116

Logs alone still blur, because a log is one-dimensional while an advice chain is nested. Spread it into a single-step run: eight lines on the left, live variables and call stack on the right. Press "next" and watch step ④ — the target method executes exactly once, and the other seven steps all spin around it:

67 / 116
Stepper
StepperStepping through the onion: where the five advice types actually land1 / 8
Click next from 1 to 8; note that step 2 runs before step 3, and step 7 unwinds after step 6
Code under debug
1orderService.create(dto); // (1) the call hits the proxy, entering the interceptor chain
2[@Around] long start = System.nanoTime(); // (2) the around part BEFORE proceed - actually first
3[@Before] log.info("enter"); // (3) before advice
4[target] orderRepository.save(order); // (4) the only line doing real business work
5[@AfterReturning] counter.add(result); // (5) runs only on a normal return
6[@After] MDC.remove("op"); // (6) finally semantics: success or failure
7[@Around] proceed() returns, log the cost // (7) the around part AFTER proceed - second to last
8return order; // (8) the onion is unwound; the caller finally gets a value
Variables now
orderServiceOrderServiceImpl$$SpringCGLIB$$0
threadmain
Call stack
1AopDemo.main
2proxy.create
1Establish the precondition: what got injected is not the object you newed, it is the container's stand-in. Only when the class name carries SpringCGLIB or $Proxy does any advice have a chance to run at all.
68 / 116

Re-watch the onion animation against that trace and the gate order sticks for good:

69 / 116
Animation
Animation · Advice chain like an onion: in, then out
Animation · Advice chain like an onion: in, then out
70 / 116

Those eight steps are one aspect. Real code stacks two or three, and who enters first gets fuzzy — so let's play a round: click an annotation on the left, its true trigger condition on the right. Wrong pairs explain themselves on the spot.

71 / 116
Match
MatchAdvice type ↔ when it actually runsMatched 0/6 · Missed 0
Six left entries are annotations and idioms, the right column is their real behaviour; finish this round and Section 8's log order needs no memorising
Pick a card on the left first
72 / 116

Two of the nastiest paths each deserve their own run. First, an @Around that never calls proceed():

73 / 116
Kernel lab
TeaVM@Around without proceed(): the target gets eatenidle
Pick around and see why the business line is missing from the log — and who replaced the return value
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
74 / 116

Second, which advice types fire when the target throws — @AfterReturning stays completely silent, which is why a lot of "failure metrics are always zero" dashboards exist:

75 / 116
Kernel lab
TeaVMWhich advice runs when it throwsidle
Pick error and compare with the previous run: only @AfterThrowing and @After fire on the exception path
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
76 / 116

From here you can type the commands yourself instead of clicking. This console talks to the same container running in your browser, and every answer is computed by the kernel — boot first, beans to see which beans got a shell, then the lab lines one by one:

77 / 116
Console
78 / 116
Section
11. Sandbox: will this method get advised at all
79 / 116

Five switches decide whether "my aspect is not firing". Flicking them beats memorising a checklist. This sandbox deliberately defines only combinations that differ decisively: once the failure cause is established (say the object is not in the container at all), stacking another problem on top produces no new behaviour, so any unlisted mix collapses onto the nearest cell's verdict.

80 / 116
Sandbox
SandboxWill this call be intercepted by advice
Result
[log] before save ...
# authorization -> logging -> tx -> target -> commit -> duration
return: ok
Verdict: the only fully effective combination
This is the baseline; every other row subtracts something from it
81 / 116

Suggested usage: park all four switches on the most correct cell first (the top output) as your baseline, then change exactly one switch at a time and see which step disappears. You will derive the "three requirements for proxying" yourself: the call must go through the proxy, the method must be rewritable, the object must live in the container.

82 / 116
Section
12. Error quick-reference
83 / 116

The nastiest thing about AOP is that most failures are silent. Read the table as symptom → cause → self-rescue.

84 / 116

One debugging law first: when an aspect seems dead, your first move is not editing the pointcut — it is printing getClass().getName(). If it says com.example.UserServiceImpl instead of ...$$EnhancerBySpringCGLIB$$... or $Proxy..., no proxy exists and everything else you check is wasted effort.

85 / 116
Table
Error text (searchable fragment)Real cause30-second self-rescueWhere to dig deeper
No exception, no log, the aspect simply never runs (most common)① plain Spring project missing @EnableAspectJAutoProxy; ② the bean is not in the container (hand-newed); ③ the method is not publicPrint getClass().getName() to confirm a proxy exists; verify the target class carries @Service/@Component and sits in the scan path; finally make the method publicSection 7 here + article 14
java.lang.NoClassDefFoundError: org/aspectj/lang/annotation/AspectYou used the @Aspect annotation without the AspectJ runtime dependencyAdd spring-boot-starter-aop (it brings aspectjweaver too)Article 13
BeanCreationException: Failed to instantiate [com.example.TimingAspect] + Caused by: java.lang.IllegalArgumentException: error The near term's relationship is malformed; a valid WildcardType is requiredThe pointcut expression is malformed, so the aspect bean fails at creation (thrown by the AspectJ expression parser); usually a wrong package name, a missing .., or a type that does not existCheck each segment of execution(...) — return type, package, parameters — and first prove the pipeline works with a coarse within(com.example..*)Article 13
A @Transactional method threw an exception yet the data was committedBy default only RuntimeException and Error trigger rollback; checked exceptions do notUse @Transactional(rollbackFor = Exception.class), or wrap explicitly in your aspectArticle 31
Cannot proxy target class because CGLIB-Proxies are disabled, or IllegalArgumentException: Cannot subclass final classThe target class is final, so CGLIB cannot generate a subclassDrop final, or let the class implement an interface so the JDK proxy path appliesArticle 12
Advice lost on intra-class calls; no proxy frame appears in the debugger stackthis.method() never goes through the proxy objectExtract to another bean, inject your own proxy, or ((MyService) AopContext.currentProxy()).method() with exposeProxy = trueSection 7 here + article 15
org.aspectj.weaver.reflect.ReflectionWorld$ReflectionWorldException: warning can't determine annotations of missing type on class pathThe pointcut is so broad that AspectJ's reflection world touches a type absent from the compile classpathNarrow it with within(com.example..*), or add the missing dependency to the classpathArticle 13
The aspect works in tests but not in production (or the reverse)The two environments use different proxies: one has an interface (JDK), the other forces CGLIBAlign spring.aop.proxy-target-class; inject by interface, never by implementationSection 6 of article 12
86 / 116
提示

spring.aop.proxy-target-class=true (the Boot default) routes every bean through CGLIB. The upside is that injecting by implementation class no longer explodes; the price is that final classes need special handling.

87 / 116

That Cannot subclass final class row deserves a real practice run — it is one of the few AOP failures that blow up at startup with a short stack, so the culprit frame is usually easy to point at. Do not peek: click the frame you think is responsible.

88 / 116
Triage
Error triageIllegalArgumentException: Cannot subclass final class
Scene of the crime: putting a CGLIB shell on a final class

You added a stopwatch @Aspect, touched no business code, and the application now refuses to start.

APPLICATION FAILED TO START
java.lang.IllegalArgumentException: Cannot subclass final class com.bee.order.service.OrderService
at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:667)
at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:522)
at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:208)
at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:483)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:352)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBeanAndFindTargetSource(AbstractAutowireCapableBeanFactory.java:2971)
Click the frame you blame — guessing is allowed
No pressure: guess the exception first, then which line actually made the call.
89 / 116
Section
13. Checkpoints
90 / 116
Quiz
Check yourselfWhich rewrite makes advice on b() fire when a() calls b() inside the same class?
Pick one — you get feedback right away
91 / 116
Quiz
Check yourselfWhat is the most accurate answer to "what does a dynamic proxy actually proxy?"
Pick one — you get feedback right away
92 / 116
Section
14. Exercises
93 / 116
Section
Tier 1 · Follow along
94 / 116

Write a "naked" Service first, then add one aspect, and prove the aspect fires by counting log lines. Fully runnable code:

95 / 116
java
package com.example.aop;// 1) business interface and impl: not a single log statement insidepublic interface OrderService {    String create(String sku);}@Servicepublic class OrderServiceImpl implements OrderService {    @Override    public String create(String sku) {        return "order-" + sku;          // only its own job    }}
96 / 116
java
package com.example.aop;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;@Aspect                       // tell the container: this is an aspect@Component                    // make it a bean — without this the aspect is never foundpublic class TimingAspect {    @Pointcut("execution(* com.example.aop.OrderService.*(..))")    public void orderApi() {}           // pointcut: the filter over join points    @Around("orderApi()")    public Object timeIt(ProceedingJoinPoint pjp) throws Throwable {        long start = System.nanoTime();        System.out.println("[around] in  -> " + pjp.getSignature().getName());        Object result = pjp.proceed();               // travel deeper into the onion, eventually reaching the target        System.out.println("[around] out -> " + pjp.getSignature().getName()                + " result=" + result                + " cost=" + (System.nanoTime() - start) / 1000 + "us");        return result;    }}
97 / 116
java
package com.example.aop;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplicationpublic class AopDemo {    public static void main(String[] args) {        try (ConfigurableApplicationContext ctx = SpringApplication.run(AopDemo.class, args)) {            OrderService svc = ctx.getBean(OrderService.class);            System.out.println("[class] " + svc.getClass().getName());            System.out.println("[call ] " + svc.create("A1001"));        }    }}
98 / 116

Expected log (the four key lines):

99 / 116
text
[class] com.sun.proxy.$Proxy78          # or ...OrderServiceImpl$$SpringCGLIB$$0[around] in  -> create[around] out -> create result=order-A1001 cost=xxxus[call ] order-A1001
100 / 116

If [class] prints a name you never wrote, that is proof the container handed you a stand-in; the paired in/out lines prove the advice chain really wraps the target method in the middle.

101 / 116
Section
Tier 2 · Variant
102 / 116

Goal: add no new aspect, change only the call path, and make those two [around] lines disappear.

103 / 116

Hint: add public String createTwice(String sku) { return create(sku) + "/" + create(sku); } to OrderServiceImpl, then call svc.createTwice("A1001") from main.

104 / 116

You will observe: createTwice is intercepted (one pair of [around]), but the two inner create(...) calls print no [around] at all, while the result is still order-A1001/order-A1001. That is hard evidence of self-invocation failure — paste these logs in your notes; it beats reciting the conclusion ten times.

105 / 116
Section
Tier 3 · Build one
106 / 116

Build a "three-in-one method aspect" for every public method under com.example..service..*: ① log arguments and duration, ② record exceptions and rethrow them unchanged, ③ count invocations per method in a ConcurrentHashMap and expose a read-only endpoint returning those counts.

107 / 116

Acceptance checklist:

108 / 116
  • [ ] One @Aspect class does all three jobs, and the business code contains no log / try-catch / counter at all
  • [ ] @AfterThrowing receives the exception object and the exception still propagates to the caller (never swallow it)
  • [ ] You deliberately create one self-invocation, explain why the counter under-counts, and give the fix
  • [ ] You write the pointcut twice, once with within and once with execution, and note the readability difference
  • [ ] You can explain why turning these counts into a production metric belongs to Micrometer, not to the aspect
109 / 116
Section
15. Self-check
110 / 116
自检

can you explain what a dynamic proxy actually proxies using the phone case, ending with "the phone never changed; the device handed to you did"?

111 / 116
自检

can you assign a everyday role to each of aspect, pointcut, join point, advice and weaving (rulebook / filter condition / every doorway / the concrete action / the casing operation)?

112 / 116
自检

what are Spring AOP's three failure scenarios? (self-invocation; non-public / final / static; the object is not in the container)

113 / 116
自检

can you recite the advice-chain ordering? (first in, last out — the innermost advice exits first)

114 / 116
自检

when an aspect does not fire, what is your very first move? (print getClass().getName() to confirm a proxy exists)

115 / 116
口诀

pointcut picks the doors, advice sets the action, the aspect packages it, the proxy delivers it; a self-call uses no door, so nothing rings.

116 / 116
Summary

AOP solves "cross-cutting concerns copied into every method". Three layers capture it — conceptually it complements OOP and handles horizontal behavior; mechanically it generates a runtime proxy to "weave in" advice; and its boundaries are method level, container beans only, with self-invocation failing. Next we open up the proxy itself to see how JDK and CGLIB actually build that stand-in.