Question 19 · Section 5

How to Solve the Self-Invocation Problem?

4. Avoid AopContext.currentProxy() 5. AspectJ - requires additional weaving setup (compile-time or load-time), complicates debugging. Use only if other solutions do not fit. 6....

Language versions: English Russian Ukrainian

Junior Level

Solution: Call the method through another bean!

// Self-invocation
@Service
public class UserService {
    public void outer() {
        inner();  // Around Proxy!
    }
    @Transactional public void inner() { }
}

// Separation into two beans
@Service
public class UserService {
    @Autowired private InnerService innerService;

    public void outer() {
        innerService.inner();  // Through Proxy!
    }
}

@Service
public class InnerService {
    @Transactional public void inner() { }
}

Middle Level

Solution 1: Separation (best)

// Extract logic into a separate bean
@Service
public class OrderService {
    @Autowired private NotificationService notifier;

    public void createOrder() {
        repository.save(order);
        notifier.send(order);  // Through Proxy of another bean!
    }
}

Solution 2: ObjectProvider

@Service
public class MyService {
    private final ObjectProvider<MyService> selfProvider;

    public void outer() {
        selfProvider.getObject().inner();  // Through Proxy!
    }

    @Transactional
    public void inner() { }
}

Solution 3: TransactionTemplate

@Service
public class MyService {
    private final TransactionTemplate transactionTemplate;

    public void outer() {
        transactionTemplate.execute(status -> {
            return inner();  // In transaction without Proxy!
        });
    }
    // If inner() returns void, use:
    // transactionTemplate.executeWithoutResult(status -> { inner(); });

    public void inner() { }
}

Solution 4: @Lazy Self-Injection

@Service
public class MyService {
    @Autowired @Lazy
    private MyService self;  // Proxy of itself!

    public void outer() {
        self.inner();  // Through Proxy!
    }

    @Transactional
    public void inner() { }
}

Senior Level

Why @Lazy Works

@Lazy -> Spring creates a Proxy stub
-> On first call, Proxy initializes the real bean
-> self = Proxy -> inner() goes through Proxy!
// Fragile code, tied to Spring
((MyService) AopContext.currentProxy()).inner();

// Requires: exposeProxy = true
// -> Additional overhead
// -> Do not use!

// Problems with AopContext.currentProxy():
// (1) requires exposeProxy = true in configuration,
// (2) adds ThreadLocal overhead on every call,
// (3) code is tightly coupled to Spring AOP, hard to test.

AspectJ Weaving

LTW (Load-Time Weaving):
  -> Java Agent weaves aspects on class load
  -> Self-invocation works!

CTW (Compile-Time Weaving):
  -> Maven/Gradle plugin weaves at compile time

-> Complex setup
-> Complicates debugging

Production Experience

Real scenario: ObjectProvider in legacy

Large service with self-invocation
-> Refactoring too expensive
-> ObjectProvider solved the problem in 10 minutes

Best Practices

  1. Separation -> best solution
  2. TransactionTemplate -> targeted control
  3. ObjectProvider + @Lazy -> for legacy
  4. Avoid AopContext.currentProxy()
  5. AspectJ - requires additional weaving setup (compile-time or load-time), complicates debugging. Use only if other solutions do not fit.
  6. Code Review -> check for self-invocation

Summary for Senior

  • Separation -> clean architecture
  • ObjectProvider -> modern approach
  • TransactionTemplate -> no proxy problems
  • @Lazy self-injection -> for legacy
  • AopContext -> do not use
  • AspectJ -> complex, but solves

Interview Cheat Sheet

Must know:

  • 4 main solutions: (1) Split into different beans, (2) ObjectProvider, (3) TransactionTemplate, (4) @Lazy self-injection
  • Bean separation is the best solution from a clean architecture perspective
  • ObjectProvider.getObject() - gets a proxy of itself, modern approach
  • TransactionTemplate - programmatic transaction control, no proxy problems
  • @Lazy self-injection - Spring creates a Proxy stub, initializes real bean on first call
  • AopContext.currentProxy() - NOT RECOMMENDED: requires exposeProxy=true, ThreadLocal overhead, Spring coupling
  • AspectJ Weaving (LTW/CTW) - solves the problem, but complex setup and debugging

Common follow-up questions:

  • Why does @Lazy work? -> Spring defers initialization, injects a Proxy stub which delegates to the real bean on first call
  • When to choose TransactionTemplate? -> When you need a targeted transaction inside a method, without creating a separate bean
  • Why is AopContext.currentProxy() bad? -> (1) exposeProxy=true, (2) ThreadLocal overhead, (3) hard coupling to Spring, (4) hard to test
  • What is better: ObjectProvider or @Lazy? -> ObjectProvider - more explicit and testable; @Lazy - shorter, but less obvious

Red flags (DO NOT say):

  • “AopContext.currentProxy() is the recommended solution” - not recommended
  • “AspectJ is not needed, Spring AOP is always enough” - for self-invocation without refactoring, AspectJ is an option
  • “TransactionTemplate = the same as @Transactional” - no, it is programmatic control, without proxy
  • “@Lazy creates a new bean” - creates a proxy stub to the same bean

Related topics:

  • [[18. Why Transactional does not work with self-invocation]]
  • [[17. What does the Transactional annotation do]]
  • [[13. What is a proxy in Spring]]
  • [[12. Difference between singleton and prototype scope]]