Будет ли @RestController создавать Prototype сервис при каждом обращении?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Будет ли @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
// Создана ОДИН раз, переиспользуется для всех запросов
}
Что происходит:
- Spring запускается
- Создаёт UserController (Singleton)
- Создаёт UserService (Singleton)
- Инжектирует Service в Controller
- При каждом запросе используется один и тот же 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 {
// Создана при каждом инжектировании?
// НЕ правильно!
}
Что происходит:
- Spring запускается
- Создаёт UserController (Singleton) - constructor called
- Инжектирует Service в Constructor (Prototype) - создаёт ONE instance
- UserController хранит ссылку на ОДИН Service
- При каждом запросе используется один и тот же 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.