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

Будет ли @RestController создавать Prototype сервис при каждом обращении?

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

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

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

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

# Будет ли @RestController создавать Prototype сервис при каждом обращении?

Ответ: Нет, не будет. Это распространённая ошибка в понимании Spring Bean scopes.

Объяснение

Разные Scopes, разные сценарии

Сценарий 1: Controller (Singleton) → Service (Singleton)

@RestController
public class UserController {
    private UserService userService;
    
    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;  // Injected ONCE (constructor)
    }
    
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);  // Используем один и тот же Service
    }
}

@Service
public class UserService {
    // По умолчанию Scope = Singleton
    // Создана ОДИН раз, переиспользуется для всех запросов
}

Что происходит:

  1. Spring запускается
  2. Создаёт UserController (Singleton)
  3. Создаёт UserService (Singleton)
  4. Инжектирует Service в Controller
  5. При каждом запросе используется один и тот же Service
Запрос 1: GET /users/1 → UserService#1 (один объект)
Запрос 2: GET /users/2 → UserService#1 (тот же объект)
Запрос 3: GET /users/3 → UserService#1 (тот же объект)

Сценарий 2: Controller (Singleton) → Service (Prototype)

@RestController
public class UserController {
    private UserService userService;
    
    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;  // Injected ONCE (constructor)
    }
    
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);  // Используем один и тот же Service!
    }
}

@Service
@Scope("prototype")  // Prototype!
public class UserService {
    // Создана при каждом инжектировании?
    // НЕ правильно!
}

Что происходит:

  1. Spring запускается
  2. Создаёт UserController (Singleton) - constructor called
  3. Инжектирует Service в Constructor (Prototype) - создаёт ONE instance
  4. UserController хранит ссылку на ОДИН Service
  5. При каждом запросе используется один и тот же Service
Запрос 1: GET /users/1 → UserService#1 (созданная при старте)
Запрос 2: GET /users/2 → UserService#1 (один и тот же)
Запрос 3: GET /users/3 → UserService#1 (один и тот же)

Проблема: Prototype scope не работает! Сервис создан один раз и переиспользуется.

Почему происходит это?

Потому что Singleton контейнер инжектирует зависимости в Constructor. После этого Spring забывает о Prototype scope, потому что ссылка уже была установлена.

Как ПРАВИЛЬНО использовать Prototype?

Способ 1: ObjectFactory

@RestController
public class UserController {
    private ObjectFactory<UserService> userServiceFactory;
    
    @Autowired
    public UserController(ObjectFactory<UserService> factory) {
        this.userServiceFactory = factory;
    }
    
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        // Каждый запрос получает НОВЫЙ Service
        UserService service = userServiceFactory.getObject();
        return service.findById(id);
    }
}

@Service
@Scope("prototype")
public class UserService {
    // Теперь создаётся для каждого запроса
}

Что происходит:

Запрос 1: GET /users/1 → userServiceFactory.getObject() → UserService#1 (NEW)
Запрос 2: GET /users/2 → userServiceFactory.getObject() → UserService#2 (NEW)
Запрос 3: GET /users/3 → userServiceFactory.getObject() → UserService#3 (NEW)

Способ 2: Provider (javax.inject.Provider)

@RestController
public class UserController {
    private Provider<UserService> userServiceProvider;
    
    @Autowired
    public UserController(Provider<UserService> provider) {
        this.userServiceProvider = provider;
    }
    
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        // Каждый запрос получает НОВЫЙ Service
        UserService service = userServiceProvider.get();
        return service.findById(id);
    }
}

Способ 3: @Lookup (Method Injection)

@RestController
public class UserController {
    
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        // Каждый запрос получает НОВЫЙ Service
        UserService service = getUserService();
        return service.findById(id);
    }
    
    @Lookup  // Spring переопределит этот метод в runtime
    protected UserService getUserService() {
        return null;  // Реальная реализация сгенерирована Spring
    }
}

@Service
@Scope("prototype")
public class UserService {
}

Разные Bean Scopes

ScopeОписаниеСоздание
Singleton (default)Один объект для приложенияПри старте Spring
PrototypeНовый при каждом getBean()При каждом запросе
RequestНовый для каждого HTTP запросаДля каждого запроса
SessionНовый для каждой HTTP сессииПри создании сессии
Application (Servlet)На уровень сервлет контекстаНа весь контекст

Сравнение Scopes при инжекции

@RestController (Singleton)
     ↓
@Service (Singleton) → ПЕРЕИСПОЛЬЗУЕТСЯ
     ↓
@Service @Scope("prototype") + constructor → СОЗДАНА ОДИН РАЗ
     ↓
@Service @Scope("prototype") + ObjectFactory → СОЗДАНА КАЖДЫЙ РАЗ
     ↓
@Service @Scope("request") → СОЗДАНА КАЖДЫЙ ЗАПРОС

Практический пример - Когда нужен Prototype?

Когда NOT нужен Prototype (95% случаев)

// Не нужен prototype
@Service
public class UserService {
    // Stateless сервис, безопасен для переиспользования
    public User findById(Long id) { ... }
}

Когда НУЖЕН Prototype (редко)

// Нужен prototype
@Service
@Scope("prototype")
public class ReportGenerator {
    private StringBuilder report = new StringBuilder();
    
    public void addLine(String line) {
        report.append(line);
    }
    
    public String generate() {
        return report.toString();
    }
}

// НЕЛЬЗЯ инжектировать в Singleton!
// У разных request будет одна StringBuilder!

Правильный способ

@RestController
public class ReportController {
    private ObjectFactory<ReportGenerator> generatorFactory;
    
    @Autowired
    public ReportController(ObjectFactory<ReportGenerator> factory) {
        this.generatorFactory = factory;
    }
    
    @GetMapping("/report")
    public String getReport() {
        // Каждый запрос получает НОВЫЙ generator со свежим StringBuilder
        ReportGenerator gen = generatorFactory.getObject();
        gen.addLine("Header");
        gen.addLine("Data");
        return gen.generate();
    }
}

Лучшие практики

✅ Делай сервисы Singleton (по умолчанию) и Stateless ✅ Если нужен Prototype, используй ObjectFactory или Provider ✅ Для Request scope используй @Scope("request") ✅ Помни о потокобезопасности при Singleton

❌ Не инжектируй Prototype в Singleton constructor ❌ Не используй Prototype просто так ❌ Не игнорируй Scope warnings

Короткий ответ на вопрос

Нет, @RestController не создаст Prototype сервис при каждом обращении, если инжектировать в constructor.

Потому что:

  • Constructor вызывается один раз (при создании Singleton контроллера)
  • Prototype scope проверяется только при инжекции
  • После инжекции ссылка закреплена

Для правильной работы Prototype используй ObjectFactory или Provider.

Будет ли @RestController создавать Prototype сервис при каждом обращении? | PrepBro