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

Можно ли создать бины со Scope Request?

2.0 Middle🔥 121 комментариев
#Основы Java

Комментарии (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. Это полезно для:

  1. Хранения request-specific данных (userId, transactionId)
  2. Отслеживания запроса через все слои приложения
  3. Избежания thread-local переменных
  4. Логирования и аудита

Основное правило: используйте Request Scope только для данных, специфичных для текущего HTTP запроса. Для stateless бизнес-логики используйте Singleton.