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

Как влияет 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При создании session1 раз за sessionUser 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");
    }
}

Важные моменты

  1. Singleton (@PostConstruct один раз) — используйте для инициализации дорогостоящих ресурсов

  2. Prototype (@PostConstruct каждый раз) — не лучший выбор для web приложений, используйте Request/Session

  3. Request/Session (@PostConstruct один раз в scope'е) — идеально для web зависимых данных

  4. ScopedProxyMode — используйте для web scope'ов, иначе будет ошибка

Итого

  • Singleton: @PostConstruct один раз при запуске приложения
  • Prototype: @PostConstruct каждый раз при создании bean'а
  • Request: @PostConstruct один раз на HTTP запрос
  • Session: @PostConstruct один раз на session пользователя

Выбор scope'а критичен для control-flow инициализации. На интервью объясните разницу и когда какой использовать.

Как влияет Bean Scope на выполнение метода, помеченного аннотацией PostConstruct | PrepBro