← Назад к вопросам

Что такое Request Scope у Bean?

2.0 Middle🔥 201 комментариев
#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Request Scope в Spring: область видимости Bean

Request Scope — это одна из самых полезных, но и часто неправильно используемых сфер видимости Bean в Spring. Разберу подробно.

Что такое Scope

Scope определяет, как долго Bean живет в контексте приложения:

@Component // по умолчанию Singleton
public class MyService {
  // один экземпляр на все приложение
}

@Component
@Scope("request") // новый экземпляр на каждый HTTP запрос
public class UserContext {
  // разные экземпляры для разных пользователей
}

Встроенные Scope

1. Singleton (по умолчанию)
   Один экземпляр на весь контекст приложения
   
2. Prototype
   Новый экземпляр каждый раз, когда просишь у контекста
   
3. Request
   Новый экземпляр на каждый HTTP запрос
   
4. Session
   Один экземпляр на сессию пользователя
   
5. Application
   Один экземпляр на сервлет контекст

Request Scope: подробное объяснение

Пользователь 1 делает GET /api/profile
  → Spring создает новый Bean с @Scope("request")
  → Bean обрабатывает запрос
  → Запрос завершен
  → Bean удаляется (garbage collect)

Пользователь 2 делает GET /api/profile
  → Spring создает НОВЫЙ Bean с @Scope("request")
  → Это полностью отдельный объект!
  → Bean обрабатывает запрос
  → Запрос завершен
  → Bean удаляется

Когда использовать Request Scope

// Сценарий 1: User Context (информация о текущем пользователе)
@Component
@Scope("request")
public class UserContext {
  private User currentUser;
  private String language;
  private String timezone;
  
  public User getUser() { return currentUser; }
  public void setUser(User user) { this.currentUser = user; }
}

// Сценарий 2: Request-scoped Cache
@Component
@Scope("request")
public class RequestCache {
  private Map<String, Object> cache = new HashMap<>();
  
  public void put(String key, Object value) {
    cache.put(key, value);
  }
  
  public Object get(String key) {
    return cache.get(key);
  }
}

// Сценарий 3: Audit Info (логирование действий)
@Component
@Scope("request")
public class AuditInfo {
  private String requestId = UUID.randomUUID().toString();
  private long startTime = System.currentTimeMillis();
  private String ipAddress;
  private String userAgent;
}

Практический пример: Request Scope Bean

@RestController
@RequestMapping("/api/users")
public class UserController {
  private final UserService userService;
  private final UserContext userContext; // Request-scoped!
  
  public UserController(UserService userService, UserContext userContext) {
    this.userService = userService;
    this.userContext = userContext;
  }
  
  @GetMapping("/{id}")
  public UserDTO getUser(@PathVariable Long id) {
    User user = userService.findById(id);
    userContext.setUser(user); // сохраняю в контекст
    
    // Где-то в другом компоненте (в этом же запросе):
    // userContext.getUser() вернет ту же User
    
    return UserDTO.from(user);
  }
}

// В другом сервисе (автоматически инъектируется)
@Service
public class NotificationService {
  private final UserContext userContext;
  
  public NotificationService(UserContext userContext) {
    this.userContext = userContext;
  }
  
  public void sendNotification(String message) {
    User user = userContext.getUser(); // получаю User из контекста!
    emailService.send(user.getEmail(), message);
  }
}

Проблема: Singleton Bean + Request-scoped Dependency

Если Singleton Bean зависит от Request-scoped Bean, это проблема:

// НЕПРАВИЛЬНО!
@Component
public class SingletonService {
  @Autowired
  private UserContext userContext; // Request-scoped!
  
  public void process() {
    User user = userContext.getUser(); // какого пользователя получу?
    // Если это Singleton, userContext создается один раз
    // и потом используется для ВСЕх пользователей! ❌
  }
}

Решение 1: ObjectProvider (рекомендуется)

@Component
public class SingletonService {
  private final ObjectProvider<UserContext> userContextProvider;
  
  public SingletonService(ObjectProvider<UserContext> userContextProvider) {
    this.userContextProvider = userContextProvider;
  }
  
  public void process() {
    UserContext userContext = userContextProvider.getObject(); // new каждый раз!
    User user = userContext.getUser(); // правильный пользователь ✓
  }
}

Решение 2: Proxy (CGLIB)

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserContext {
  private User currentUser;
  
  public User getUser() { return currentUser; }
  public void setUser(User user) { this.currentUser = user; }
}

// Теперь Spring создает Proxy, который делегирует
// на правильный экземпляр UserContext для текущего запроса
@Component
public class SingletonService {
  @Autowired
  private UserContext userContext; // Proxy на Request-scoped Bean
  
  public void process() {
    User user = userContext.getUser(); // работает! ✓
  }
}

Как Spring узнает, какой запрос

Один из самых частых вопросов: как Spring понимает, какому запросу какой Bean? Ответ: RequestContextHolder

// Когда приходит HTTP запрос, Spring сохраняет его в ThreadLocal
RequestContextHolder.setRequestAttributes(
  new ServletRequestAttributes(request)
);

// Когда Spring создает Request-scoped Bean,
// он использует текущий RequestAttributes из ThreadLocal
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
String sessionId = attrs.getSessionId();

По этому работает примерно так:

Запрос 1 (thread-1) → RequestContextHolder.setRequestAttributes(req1)
  ↓
  Spring создает UserContext для req1
  ↓
  UserContext живет до конца req1
  ↓
  RequestContextHolder очищает req1
  ↓
  Bean удаляется (или реиспользуется если есть кэш)

Запрос 2 (thread-2) → RequestContextHolder.setRequestAttributes(req2)
  ↓
  Spring создает НОВЫЙ UserContext для req2
  ↓
  и т.д.

Важно: Thread-safety

Request-scoped Bean не нужно делать thread-safe:

@Component
@Scope("request")
public class UserContext {
  // Не нужен synchronized!
  // Каждый запрос имеет свой экземпляр на своем потоке
  private User user; // thread-safe автоматически
}

Потому что:

  • Каждый HTTP запрос = разные поток/процесс
  • Каждому потоку = свой экземпляр Bean
  • Разные потоки не видят друг друга

Session Scope (похоже на Request, но шире)

@Component
@Scope("session")
public class SessionData {
  // Один экземпляр на сессию пользователя (вся сессия жизни)
  // Если пользователь зайдет завтра — новый экземпляр
  private String sessionId;
  private List<String> recentViews;
}

// Различие:
// Request scope: новый Bean для каждого запроса
// Session scope: один Bean пока пользователь в сессии

Выводы

  1. Request Scope = новый Bean на каждый HTTP запрос
  2. Используй для user context, audit info, request cache
  3. Никогда не инъектируй Request-scoped Bean в Singleton без Proxy или ObjectProvider
  4. Используй proxyMode = ScopedProxyMode.TARGET_CLASS или ObjectProvider<T>
  5. Request-scoped Bean автоматически thread-safe
  6. Spring использует RequestContextHolder + ThreadLocal для связи запроса и Bean
Что такое Request Scope у Bean? | PrepBro