Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Можно ли создать бины со Scope Request?
Да, полностью можно. Request Scope — это один из встроенных scopes в Spring Framework, который создаёт новый экземпляр бина для каждого HTTP запроса. Это мощный инструмент для работы с request-specific данными.
Scopes в Spring
Spring предоставляет несколько встроенных scopes:
Scope | Жизненный цикл | Когда использовать
─────────────┼──────────────────────────────────────┼──────────────────────────────
singleton | Один для всего приложения | Stateless сервисы
prototype | Новый для каждого запроса к контексту | Mutable объекты
request | Новый для каждого HTTP запроса | Request-specific данные
session | Один на HTTP сессию | Session-specific данные
application | Один для всего веб-приложения | Глобальные данные
Пример 1: Базовый Request Scope
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("request") // Request Scope
public class RequestContext {
private String userId; // Специфичен для текущего запроса
private String transactionId; // Уникален для каждого запроса
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserId() {
return userId;
}
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
public String getTransactionId() {
return transactionId;
}
}
Пример 2: Request Scope в Controller
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final RequestContext requestContext; // Request Scope
private final UserService userService; // Singleton
// Constructor injection
public UserController(RequestContext requestContext, UserService userService) {
this.requestContext = requestContext; // Новый для каждого запроса
this.userService = userService; // Один на всё приложение
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
// requestContext — специфичен для этого конкретного запроса
requestContext.setTransactionId(UUID.randomUUID().toString());
requestContext.setUserId(getAuthenticatedUserId());
User user = userService.findUser(id);
// Используем request-specific data
logger.info("User fetched: " + user.getId() +
" in transaction: " + requestContext.getTransactionId());
return ResponseEntity.ok(user);
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody CreateUserRequest request) {
requestContext.setTransactionId(UUID.randomUUID().toString());
User newUser = userService.createUser(request);
return ResponseEntity.created(URI.create("/api/users/" + newUser.getId()))
.body(newUser);
}
}
Пример 3: Request Scope с Interceptor
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.stereotype.Component;
@Component
public class RequestIdInterceptor implements HandlerInterceptor {
private final RequestContext requestContext;
public RequestIdInterceptor(RequestContext requestContext) {
this.requestContext = requestContext;
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// Устанавливаем transactionId для текущего запроса
String requestId = request.getHeader("X-Request-ID");
if (requestId == null) {
requestId = UUID.randomUUID().toString();
}
requestContext.setTransactionId(requestId);
// Устанавливаем userId из Security Context
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()) {
requestContext.setUserId(auth.getName());
}
return true;
}
}
// Регистрируем interceptor
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final RequestIdInterceptor requestIdInterceptor;
public WebConfig(RequestIdInterceptor requestIdInterceptor) {
this.requestIdInterceptor = requestIdInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestIdInterceptor);
}
}
Пример 4: Request Scope для логирования
@Component
@Scope("request")
public class RequestLogger {
private String requestId;
private LocalDateTime startTime;
private String method;
private String path;
@PostConstruct
public void init() {
this.requestId = UUID.randomUUID().toString();
this.startTime = LocalDateTime.now();
}
public void setMethodAndPath(String method, String path) {
this.method = method;
this.path = path;
}
public void logCompletion(HttpStatus status) {
LocalDateTime endTime = LocalDateTime.now();
long duration = Duration.between(startTime, endTime).toMillis();
System.out.printf(
"[%s] %s %s - Status: %d - Time: %dms%n",
requestId, method, path, status.value(), duration
);
}
public String getRequestId() {
return requestId;
}
}
Пример 5: Request Scope VS Singleton
// ❌ Проблема: Singleton с изменяемыми данными
@Service
public class BadUserService { // По умолчанию Singleton
private String currentUser; // ЭТО ПРОБЛЕМА!
public void setCurrentUser(String user) {
this.currentUser = user; // Будет переписано для других запросов
}
public String getCurrentUser() {
return currentUser; // Может вернуть чужого пользователя!
}
}
// ✅ Решение: Request Scope для request-specific данных
@Service
@Scope("request")
public class GoodUserContext {
private String currentUser; // Безопасно — свой для каждого запроса
public void setCurrentUser(String user) {
this.currentUser = user;
}
public String getCurrentUser() {
return currentUser; // Всегда правильный пользователь
}
}
Пример 6: Request Scope с Filter
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
@Component
@Scope(WebApplicationContext.SCOPE_REQUEST) // Явное указание
public class SecurityContext {
private String userId;
private List<String> roles;
private boolean authenticated;
public void authenticate(String userId, List<String> roles) {
this.userId = userId;
this.roles = roles;
this.authenticated = true;
}
public boolean isAuthenticated() {
return authenticated;
}
public String getUserId() {
return userId;
}
public boolean hasRole(String role) {
return roles.contains(role);
}
}
@Component
public class SecurityFilter extends OncePerRequestFilter {
private final SecurityContext securityContext;
public SecurityFilter(SecurityContext securityContext) {
this.securityContext = securityContext;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
// Извлекаем данные из headers и инициализируем context
String userId = request.getHeader("X-User-Id");
String rolesHeader = request.getHeader("X-User-Roles");
if (userId != null) {
List<String> roles = Arrays.asList(rolesHeader.split(","));
securityContext.authenticate(userId, roles);
}
filterChain.doFilter(request, response);
} finally {
// Cleanup не требуется — бин удалится со сончом запроса
}
}
}
Пример 7: Request Scope в Service
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final RequestContext requestContext; // Request Scope!
public OrderService(OrderRepository orderRepository, RequestContext requestContext) {
this.orderRepository = orderRepository;
this.requestContext = requestContext;
}
public Order createOrder(CreateOrderRequest request) {
// requestContext уникален для этого запроса
Order order = new Order();
order.setUserId(requestContext.getUserId());
order.setTransactionId(requestContext.getTransactionId());
return orderRepository.save(order);
}
public List<Order> getUserOrders() {
// Автоматически фильтруем по userId из текущего запроса
return orderRepository.findByUserId(requestContext.getUserId());
}
}
Внимание: Проблемы при injection в Singleton
// ❌ ВНИМАНИЕ: Singleton сервис с Request Scope зависимостью
@Service
public class UserService { // Singleton!
private final RequestContext requestContext; // Request Scope!
public UserService(RequestContext requestContext) {
this.requestContext = requestContext; // ПРОБЛЕМА!
// При создании будет использован requestContext из первого запроса
// Для последующих запросов будет тот же объект!
}
}
// ✅ РЕШЕНИЕ: Используйте ObjectProvider для ленивого доступа
@Service
public class UserService {
private final ObjectProvider<RequestContext> requestContextProvider;
public UserService(ObjectProvider<RequestContext> requestContextProvider) {
this.requestContextProvider = requestContextProvider;
}
public String getCurrentUserId() {
// Получаем новый экземпляр для текущего запроса
return requestContextProvider.getObject().getUserId();
}
}
Таблица Scopes
| Scope | Создание | Удаление | Использование |
|---|---|---|---|
| singleton | При старте контекста | При завершении приложения | Stateless сервисы |
| prototype | При каждом запросе | После использования | Mutable объекты |
| request | При HTTP запросе | В конце HTTP запроса | Request-specific данные |
| session | При новой сессии | Когда сессия истечёт | Session-specific данные |
| application | При старте приложения | При завершении приложения | Глобальные данные |
Best Practices
✅ Правильно:
- Используйте Request Scope для request-specific данных (userId, transactionId)
- Инициализируйте Request Scope бины в Interceptor/Filter
- Используйте ObjectProvider<T> для injection в Singleton
- Документируйте, почему нужен Request Scope
❌ Неправильно:
- Не используйте Request Scope для stateless сервисов
- Не делайте все бины Request Scope из-за "просто в случае"
- Не забывайте инициализировать данные в бине
- Не используйте RequestContextHolder (если можно через injection)
Вывод
Да, можно создавать бины со Scope Request. Это полезно для:
- Хранения request-specific данных (userId, transactionId)
- Отслеживания запроса через все слои приложения
- Избежания thread-local переменных
- Логирования и аудита
Основное правило: используйте Request Scope только для данных, специфичных для текущего HTTP запроса. Для stateless бизнес-логики используйте Singleton.