← Назад к вопросам
Как влияет Bean Scope на выполнение метода, помеченного аннотацией PostConstruct
2.8 Senior🔥 91 комментариев
#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Bean Scope и @PostConstruct в Spring
Основы
@PostConstruct — это lifecycle callback, который выполняется сразу после создания bean'а и инъекции зависимостей. Scope влияет на когда и сколько раз этот метод будет вызван.
Важно понимать
Порядок вызовов для всех scope'ов:
1. Создание объекта (new)
2. Инъекция зависимостей (@Autowired)
3. Вызов @PostConstruct методов
4. Bean готов к использованию
Вариант 1: Singleton Scope (по умолчанию)
@Component
@Scope("singleton") // Или просто @Component
public class UserService {
@PostConstruct
public void init() {
System.out.println("Init called");
}
}
// Поведение:
// - @PostConstruct вызывается ОДИН раз при запуске приложения
// - Все инъекции получают один и тот же экземпляр
public class Application {
public static void main(String[] args) {
ApplicationContext context =
SpringApplication.run(Application.class);
// Скрытно создаётся UserService, вызывается @PostConstruct
UserService service1 = context.getBean(UserService.class);
UserService service2 = context.getBean(UserService.class);
System.out.println(service1 == service2); // true
// @PostConstruct вызвана только один раз!
}
}
Вариант 2: Prototype Scope
@Component
@Scope("prototype")
public class RequestHandler {
@PostConstruct
public void init() {
System.out.println("Init called for new instance");
}
}
// Поведение:
// - @PostConstruct вызывается каждый раз при создании bean'а
// - Каждый getBean() создаёт новый экземпляр
public class Application {
public static void main(String[] args) {
ApplicationContext context =
SpringApplication.run(Application.class);
RequestHandler handler1 = context.getBean(RequestHandler.class);
// Вывод: "Init called for new instance"
RequestHandler handler2 = context.getBean(RequestHandler.class);
// Вывод: "Init called for new instance"
System.out.println(handler1 == handler2); // false
// @PostConstruct вызвана дважды!
}
}
Вариант 3: Request Scope (в Web приложениях)
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private String requestId;
@PostConstruct
public void init() {
this.requestId = UUID.randomUUID().toString();
System.out.println("RequestContext created with ID: " + requestId);
}
public String getRequestId() {
return requestId;
}
}
// Поведение:
// - @PostConstruct вызывается один раз на каждый HTTP запрос
// - Разные запросы получают разные экземпляры
@RestController
public class UserController {
@Autowired
private RequestContext context; // Proxy!
@GetMapping("/users")
public ResponseEntity<String> getUsers() {
// context.init() уже вызвана для этого запроса
return ResponseEntity.ok(context.getRequestId());
}
}
// Два разных HTTP запроса → две разные @PostConstruct вызова
Вариант 4: Session Scope
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession {
private String sessionId;
@PostConstruct
public void init() {
this.sessionId = UUID.randomUUID().toString();
System.out.println("Session created: " + sessionId);
}
}
// Поведение:
// - @PostConstruct вызывается один раз при создании session'а
// - Один пользователь (одна session) имеет один экземпляр
// - Разные пользователи получают разные экземпляры
Практический пример: разные scope'ы
// Singleton - инициализируется один раз
@Component
@Scope("singleton")
public class DatabaseConnectionPool {
private HikariDataSource dataSource;
@PostConstruct
public void init() {
dataSource = new HikariDataSource();
dataSource.setMaximumPoolSize(10);
System.out.println("Database pool created");
}
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
// Prototype - инициализируется каждый раз
@Component
@Scope("prototype")
public class EmailMessage {
private String subject;
private String body;
@PostConstruct
public void init() {
this.subject = "";
this.body = "";
System.out.println("New email message created");
}
}
// Request - инициализируется один раз на запрос
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class HttpRequestContext {
private HttpServletRequest request;
private String userId;
@Autowired
private HttpServletRequest httpRequest;
@PostConstruct
public void init() {
this.request = httpRequest;
this.userId = extractUserId(request);
System.out.println("Request context initialized for user: " + userId);
}
private String extractUserId(HttpServletRequest req) {
return req.getHeader("X-User-Id");
}
}
Таблица: Scope и @PostConstruct
| Scope | Когда вызывается | Сколько раз | Использование |
|---|---|---|---|
| singleton | На запуск приложения | 1 раз | DB pools, config, services |
| prototype | При каждом getBean() | n раз | Stateful objects |
| request | При каждом HTTP запросе | 1 раз за запрос | Request-scoped data |
| session | При создании session | 1 раз за session | User session data |
| application | На запуск приложения | 1 раз | Global app state |
Проблема: Prototype внутри Singleton
// ПРОБЛЕМА!
@Component
public class SingletonService {
@Autowired
private PrototypeService prototypeService; // Будет один экземпляр!
public void usePrototype() {
// prototypeService.init() вызвана один раз, даже если нужна новая
prototypeService.doSomething();
}
}
@Component
@Scope("prototype")
public class PrototypeService {
@PostConstruct
public void init() {
System.out.println("Init");
}
}
// Решение: используйте ObjectFactory или Provider
@Component
public class SingletonServiceFixed {
@Autowired
private ObjectFactory<PrototypeService> factory;
public void usePrototype() {
PrototypeService service = factory.getObject();
// @PostConstruct вызовется для каждого вызова!
service.doSomething();
}
}
Web-приложение с разными scope'ами
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class);
}
}
// Глобальный сервис
@Service
public class GlobalAnalytics {
@PostConstruct
public void init() {
System.out.println("Analytics initialized ONCE");
}
}
// На каждый запрос
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestMetrics {
@PostConstruct
public void init() {
System.out.println("Metrics initialized for REQUEST");
}
}
// На каждую session
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserPreferences {
@PostConstruct
public void init() {
System.out.println("Preferences initialized for SESSION");
}
}
@RestController
public class ApiController {
@Autowired
private GlobalAnalytics analytics; // Один экземпляр для приложения
@Autowired
private RequestMetrics metrics; // Новый на каждый запрос
@Autowired
private UserPreferences prefs; // Новый на каждую session
@GetMapping("/api/data")
public ResponseEntity<?> getData() {
// analytics.init() вызвана один раз при запуске
// metrics.init() вызвана для этого конкретного запроса
// prefs.init() вызвана один раз для этого пользователя
return ResponseEntity.ok("OK");
}
}
Важные моменты
-
Singleton (@PostConstruct один раз) — используйте для инициализации дорогостоящих ресурсов
-
Prototype (@PostConstruct каждый раз) — не лучший выбор для web приложений, используйте Request/Session
-
Request/Session (@PostConstruct один раз в scope'е) — идеально для web зависимых данных
-
ScopedProxyMode — используйте для web scope'ов, иначе будет ошибка
Итого
- Singleton: @PostConstruct один раз при запуске приложения
- Prototype: @PostConstruct каждый раз при создании bean'а
- Request: @PostConstruct один раз на HTTP запрос
- Session: @PostConstruct один раз на session пользователя
Выбор scope'а критичен для control-flow инициализации. На интервью объясните разницу и когда какой использовать.