WHY does Spring “skip” the annotation? What is actually happening in memory?

🧠 Step 1 — What Spring really creates

You write:

@Service
public class OrderService {

@Transactional
public void placeOrder() { }

@CacheEvict(value = "productCache", key = "#productId")
public void updateProduct(int productId) { }
}

❗ What actually exists at runtime

Spring creates two objects:

1. Real Object:
OrderService

2. Proxy Object:
OrderService$$SpringProxy

👉 Important

When you do:

@Autowired
OrderService orderService;

👉 You are getting:

OrderService$$SpringProxy

NOT your real class.


🧩 Step 2 — What’s inside the proxy?

The proxy wraps your method calls like this:

public class OrderServiceProxy {

private OrderService target;

public void updateProduct(int productId) {

// 🔥 Cache logic injected here
evictCache("productCache", productId);

target.updateProduct(productId);
}

public void placeOrder() {

startTransaction();

try {
target.placeOrder();
commit();
} catch (Exception e) {
rollback();
}
}
}

👉 Your annotations are implemented HERE, not in your class.


🎯 Step 3 — Real Scenario (actual flow)

Scenario: Updating product + clearing cache


✅ Case 1 — External call (works)

@RestController
class ProductController {

@Autowired
OrderService orderService;

public void update() {
orderService.updateProduct(10);
}
}

🔁 Execution Flow

Controller

OrderServiceProxy.updateProduct(10)

🔥 Proxy sees @CacheEvict

CacheManager.evict("productCache", 10)

Calls real method:
OrderService.updateProduct(10)

✔ Cache cleared
✔ Everything works


❌ Case 2 — Internal call (your issue)

@Service
public class OrderService {

public void placeOrder() {
updateProduct(10); // ❌ internal call
}

@CacheEvict(value = "productCache", key = "#productId")
public void updateProduct(int productId) { }
}

🔁 Execution Flow

Controller

OrderServiceProxy.placeOrder()

calls real method:
OrderService.placeOrder()

inside method:
this.updateProduct(10)

DIRECT call to method

🚨 CRITICAL POINT

At this moment:

this = REAL OBJECT (OrderService)
NOT proxy

So call becomes:

OrderService.updateProduct(10)

NOT:

OrderServiceProxy.updateProduct(10)

❗ Result

  • Proxy is completely bypassed
  • @CacheEvict is never seen
  • Cache is NOT cleared

🧠 WHY does this happen?

Because of how Java works, not just Spring.


🔬 Inside the JVM

When you write:

updateProduct(10);

Compiler converts it roughly to:

this.updateProduct(10);

👉 That means:

“Call method on the current object in memory”


And what is this?

Inside your class:

this = OrderService (real object)

NOT:

OrderServiceProxy

🧠 Key Insight

The proxy only exists outside your object

Once execution enters your class:

You are inside the real object
Proxy is gone from the picture

🔁 Visual Timeline

External call

[Controller]

[Proxy] ← interception happens here

[Real Object]

Internal call

[Real Object]

[Real Object method]

🚫 Proxy never involved


🎯 Same thing with @Transactional

Example

@Service
public class PaymentService {

public void checkout() {
chargeCard(); // ❌
}

@Transactional
public void chargeCard() { }
}

Expected

checkout → chargeCard → transaction starts

Actual

checkout (proxy)

real checkout()

this.chargeCard()

real chargeCard()

🚫 No transaction
🚫 No rollback
🚫 No protection


🧠 Why Spring doesn’t “fix” this?

Because:

  • Spring uses proxies, not bytecode rewriting
  • It doesn’t modify your class
  • It wraps it externally

👉 So it cannot intercept internal calls


🔥 The Real Rule (technical wording)

Spring AOP is proxy-based, not self-invocation aware


🧪 If you want proof (try this)

Add this:

System.out.println(this.getClass());

Inside your method.


You’ll see:

class com.yourapp.OrderService

NOT:

OrderService$$SpringProxy

🧠 Final Mental Model

  • Proxy = outer wrapper
  • Your class = inner core

Rule:

👉 Only calls entering from outside hit the proxy

👉 Calls inside the class stay inside the core


🔥 One-line explanation

It “skips” because internal calls use this, and this refers to the real object — not the proxy where Spring put the annotation logic.

Comments

Popular posts from this blog

Kubernetes terms made easy

Anomalies in Database