The Big Problem When Injecting Session Scope Into A Singleton: And How Spring’s Proxy Mode Solves It

The Big Problem When Injecting Session Scope Into A Singleton: And How Spring’s Proxy Mode Solves It

1. Introduction: When You Need a "Personal" Bean in a "Shared" Application

In Spring Boot, Bean Scope is a fundamental concept, yet it often causes confusion as applications become more complex. A proper understanding of Scopes and their associated mechanisms is crucial for writing safe, performant code, and avoiding unexpected runtime errors.

We will focus on the two most common Scopes and their critical interaction: Singleton vs. Session.


2. The Default Scope: Singleton – The Application-Wide Bean

Singleton Scope is the default in Spring.

  • Characteristics: When Spring Boot starts up, all Singleton beans are eagerly created and a single instance exists throughout the entire application lifecycle.
  • Use Cases: Ideal for stateless components like Services, Repositories, and Controllers (by default).
  • Crucial Note: Because a Singleton is shared across multiple concurrent requests/threads, the bean must be Thread-Safe.

// Example: UserService is a Singleton (by default)
@Service 
public class UserService {
    // ...
}        

3. The Specific Scope: Session – The Per-User Bean

Session Scope is a web scope that creates a new bean instance for each user's HTTP Session.

  • Characteristics: The Session bean exists as long as the user's Session is active. When the Session is destroyed, the bean is destroyed as well.
  • Use Cases: Highly valuable for storing user-specific state such as:

- User Context (login information, roles).

- Shopping Cart (temporary items).

- Authentication detail.

@Component
@Scope(value = "session")
public class UserSessionContext {
    private String username;
    // ...
}        

4. The Big Problem: How to Inject a Session Bean into a Singleton?

This is a common and tricky situation:

Singleton Bean <--> Session Bean

  • Scenario: A Singleton Service needs to access the UserSessionContext (Session Scope) to retrieve the current user's details.
  • The Conflict: The Singleton Bean is created IMMEDIATELY when the application starts. At that point, NO HTTP Session exists.
  • Consequence: If you inject it directly, Spring will fail to start because it cannot provide a valid Session Scope instance during the Singleton's creation phase.

The code that causes a startup failure (without Proxy Mode):

@Service // Singleton
public class UserCartService {
    @Autowired // Startup Fails!
    private UserSessionContext sessionContext; // Session Scope
    
    // ...
}        

5. The "Magic" Solution: Proxy Mode

To resolve this conflict, Spring provides the Proxy Mode mechanism. Instead of injecting the REAL Session Bean into the Singleton, Spring injects a Proxy Bean (a stand-in)!

You only need to add the proxyMode attribute to the @Scope annotation:

@Component
@Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS) // <--- THE KEY
public class UserSessionContext {
    private String username;
    // ...
}        

How Proxy Mode Works:

  1. At Singleton Creation: Spring creates and injects a Proxy of UserSessionContext into the UserCartService.

2. At Runtime (Method Call): When the UserCartService calls a method on the Proxy (e.g., sessionContext.getUsername()), the Proxy takes over:

  • It identifies the current HTTP Session.
  • It finds (or creates) the REAL Session Bean corresponding to that Session.
  • It delegates the method call to the REAL Session Bean.

3. Result: Every request (from every user session) calling the Singleton Service will always receive the Session Bean appropriate for their user!

The takeaway: The Singleton holds the Proxy, and the Proxy is responsible for finding the correct Session Bean at the right time and place.

Article content

Flow Explanation:

  1. ApplicationContext (Singleton Creation Phase):

  • The Singleton Bean: UserCartService is created when the application starts up.
  • Instead of receiving a direct instance of UserSessionContext (which doesn't exist yet), it receives a Proxy: UserSessionContext. This proxy is the placeholder.

2. Runtime Interaction (The Call):

  • When the UserCartService (Singleton) calls a method on the injected UserSessionContext object at runtime (e.g., during an HTTP request), it actually calls the Proxy.

3. Proxy Lookup (The Magic):

  • The Proxy intercepts the call. It doesn't handle the logic itself; instead, it performs a lookup via RequestContextHolder.
  • The RequestContextHolder identifies the current execution context (i.e., the currently active HTTP Session).

4. SessionScope Delegation:

  • Based on the current context, the Proxy delegates the method call to the correct SessionScope instance.

Example:

  • If the call came from User A's Session, the Proxy routes the call to Session Bean: UserSessionContext - User A.
  • If the call came from User B's Session, the Proxy routes the call to Session Bean: UserSessionContext - User B.


Conclusion

Using proxyMode=ScopedProxyMode.TARGET_CLASS is the correct answer when you need to inject a narrower-scoped bean (like session, request) into a wider-scoped bean (like singleton). It ensures you always interact with the bean instance appropriate for the current context (session, request) without violating Spring's creation lifecycle rules.

To view or add a comment, sign in

More articles by Sang Ngo

Explore content categories