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

Сколько раз вызывается @PostConstruct?

2.2 Middle🔥 171 комментариев
#Spring Boot и Spring Data#Spring Framework

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

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

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

# @PostConstruct в Java: когда вызывается и как часто

@PostConstruct — это аннотация для инициализации bean'а после создания. Это важно понимать для корректной работы Spring приложений.

Короткий ответ

@PostConstruct вызывается ровно ОДИН раз — сразу после создания объекта, когда все зависимости инжектированы.

@Component
public class MyService {
    
    @PostConstruct
    public void init() {
        System.out.println("Инициализация"); // Выводится 1 раз
    }
}

// При старте приложения:
// - Spring создаёт MyService
// - Инжектирует зависимости
// - Вызывает init() ← @PostConstruct
// - MyService готов к использованию

Жизненный цикл Spring Bean

1. Instantiation (создание объекта через конструктор)
          ↓
2. Setting properties (установка @Autowired, @Value)
          ↓
3. BeanNameAware.setBeanName() (если реализует)
          ↓
4. BeanClassLoaderAware, ApplicationContextAware (interfaces)
          ↓
5. @PostConstruct ← Вызывается ОДИН раз
          ↓
6. InitializingBean.afterPropertiesSet() (если реализует)
          ↓
7. <bean init-method="..."> (XML конфиг, если есть)
          ↓
BEAN READY (готов к использованию)

Примеры использования

Пример 1: Инициализация ресурсов

@Service
public class DatabaseService {
    
    private DataSource dataSource;
    private Connection connection;
    
    @Autowired
    private ConfigProperties config;
    
    @PostConstruct
    public void init() {
        System.out.println("Инициализирую БД соединение...");
        try {
            // Используем уже инжектированный config
            this.dataSource = createDataSource(config.getDbUrl());
            this.connection = dataSource.getConnection();
            System.out.println("БД готова к работе");
        } catch (SQLException e) {
            throw new RuntimeException("Failed to initialize DB", e);
        }
    }
    
    @PreDestroy // Вызывается ОДИН раз при shutdown
    public void cleanup() {
        System.out.println("Закрываю БД соединение...");
        try {
            if (connection != null) connection.close();
            if (dataSource != null) dataSource.close();
        } catch (SQLException e) {
            log.error("Error closing DB", e);
        }
    }
}

Пример 2: Кэширование данных

@Component
public class ReferenceDataCache {
    
    private Map<String, Category> categoryCache;
    private Map<String, Country> countryCache;
    
    @Autowired
    private CategoryRepository categoryRepository;
    
    @Autowired
    private CountryRepository countryRepository;
    
    @PostConstruct
    public void loadCaches() {
        System.out.println("Загружаю reference data кэши...");
        
        // Загружаем при старте один раз
        this.categoryCache = categoryRepository.findAll()
            .stream()
            .collect(Collectors.toMap(Category::getCode, Function.identity()));
        
        this.countryCache = countryRepository.findAll()
            .stream()
            .collect(Collectors.toMap(Country::getCode, Function.identity()));
        
        System.out.println("Загружены " + categoryCache.size() + " категорий");
    }
    
    public Category getCategory(String code) {
        return categoryCache.get(code); // Очень быстро из памяти
    }
}

Пример 3: Валидация конфигурации

@Configuration
@ConfigurationProperties(prefix = "app")
public class AppConfig {
    
    private String apiKey;
    private String dbUrl;
    private int maxConnections;
    
    @PostConstruct
    public void validate() {
        if (apiKey == null || apiKey.isEmpty()) {
            throw new IllegalArgumentException("app.api-key must be set");
        }
        if (maxConnections <= 0) {
            throw new IllegalArgumentException("app.max-connections must be > 0");
        }
        System.out.println("Конфигурация валидна");
    }
}

Когда НЕ вызывается @PostConstruct

Случай 1: Без @Component/@Service/@Repository

// НЕПРАВИЛЬНО: @PostConstruct НЕ вызывается
public class MyService {
    @PostConstruct
    public void init() {
        System.out.println("Может быть не вызвано!");
    }
}

// ПРАВИЛЬНО: добавляем @Service
@Service
public class MyService {
    @PostConstruct
    public void init() {
        System.out.println("Вызовется гарантировано");
    }
}

Случай 2: Если bean лень инициализируется

@Service
public class LazyService {
    
    @PostConstruct
    public void init() {
        System.out.println("Когда вызовется?");
    }
}

// Если внедрить как lazy, @PostConstruct вызовется когда bean впервые запросят
@Bean
@Lazy // Отложенная инициализация
public LazyService lazyService() {
    return new LazyService();
}

Случай 3: Если @PostConstruct выбросит исключение

@Service
public class BadService {
    
    @PostConstruct
    public void init() {
        System.out.println("Начало инициализации");
        throw new RuntimeException("Initialization failed!");
        // Приложение не запустится!
    }
}

// Spring выбросит исключение при старте
// Exception in thread "main" java.lang.RuntimeException: Initialization failed!

@PostConstruct vs конструктор

@Service
public class OrderService {
    
    @Autowired
    private OrderRepository repository; // null в конструкторе!
    
    // НЕПРАВИЛЬНО: repository ещё не инжектирован
    public OrderService() {
        System.out.println("Конструктор, repository = " + repository); // null!
        // this.repository.findAll(); // NullPointerException!
    }
    
    // ПРАВИЛЬНО: repository уже инжектирован
    @PostConstruct
    public void init() {
        System.out.println("PostConstruct, repository = " + repository); // не null!
        this.repository.findAll(); // Работает!
    }
}

@PostConstruct vs InitializingBean

// Оба способа

// Способ 1: Аннотация @PostConstruct
@Component
public class Service1 {
    @PostConstruct
    public void init() {
        System.out.println("Init 1");
    }
}

// Способ 2: Интерфейс InitializingBean
@Component
public class Service2 implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Init 2");
    }
}

// Способ 3: XML <bean init-method="init">
@Component
public class Service3 {
    public void init() {
        System.out.println("Init 3");
    }
}

// Способ 4: @Bean(initMethod = "init")
@Configuration
public class Config {
    @Bean(initMethod = "init")
    public Service4 service4() {
        return new Service4();
    }
}

// Порядок вызова:
// 1. @PostConstruct
// 2. InitializingBean.afterPropertiesSet()
// 3. init-method (XML или @Bean)
// Рекомендуется: просто использовать @PostConstruct

Сколько раз вызывается в разных сценариях

Сценарий 1: Singleton (по умолчанию)

@Service // scope = SINGLETON по умолчанию
public class MyService {
    
    @PostConstruct
    public void init() {
        System.out.println("Вызывается 1 раз при старте приложения");
    }
}

// При старте Spring:
// - Spring создаёт MyService один раз
// - Вызывает @PostConstruct один раз
// Сколько раз использовать MyService — неважно, @PostConstruct вызовется только один раз

Сценарий 2: Prototype scope

@Service
@Scope("prototype") // Новый bean на каждый request
public class RequestScopedService {
    
    @PostConstruct
    public void init() {
        System.out.println("Вызывается каждый раз когда запрашивают новый bean");
    }
}

// При инжекции:
MyService service1 = applicationContext.getBean(RequestScopedService.class);
// → Создаёт новый bean → Вызывает @PostConstruct

MyService service2 = applicationContext.getBean(RequestScopedService.class);
// → Создаёт ДРУГОЙ новый bean → Вызывает @PostConstruct СНОВА

// Вывод:
// Вызывается каждый раз когда запрашивают новый bean
// Вызывается каждый раз когда запрашивают новый bean

Сценарий 3: Request scope (в web приложении)

@Service
@Scope("request") // Новый bean на каждый HTTP request
public class UserContextService {
    
    @PostConstruct
    public void init() {
        System.out.println("Инициализирую контекст пользователя");
    }
    
    @PreDestroy
    public void cleanup() {
        System.out.println("Очищаю контекст пользователя");
    }
}

// Для каждого HTTP request:
// 1. Spring создаёт новый UserContextService
// 2. Вызывает @PostConstruct
// 3. Обрабатывает request
// 4. После request вызывает @PreDestroy
// 5. Удаляет bean

// За день с 1000 запросов:
// @PostConstruct вызовется 1000 раз!

Практический пример: что вызовется сколько раз

// app.properties
app.max-retries=3

@Configuration
@ConfigurationProperties(prefix = "app")
public class AppConfig {
    
    private int maxRetries;
    
    @PostConstruct
    public void init() {
        System.out.println("AppConfig init, maxRetries = " + maxRetries);
        // Вызовется 1 раз при старте приложения
    }
}

@Service
public class UserService {
    
    @Autowired
    private AppConfig config;
    
    @PostConstruct
    public void init() {
        System.out.println("UserService init, maxRetries = " + config.getMaxRetries());
        // Вызовется 1 раз при старте приложения
    }
}

@RestController
public class UserController {
    
    @Autowired
    private UserService userService;
    
    // Нет @PostConstruct здесь
    
    @GetMapping("/users")
    public List<User> getUsers() {
        return userService.getAll(); // Вызывается много раз
    }
}

// Вывод при старте:
// AppConfig init, maxRetries = 3
// UserService init, maxRetries = 3

// После каждого GET /users:
// (@PostConstruct не вызывается, инициализация была один раз)

Выводы

  1. @PostConstruct вызывается ровно один раз для Singleton beans (по умолчанию)
  2. Вызывается после инжекции всех зависимостей (@Autowired)
  3. Используй для инициализации ресурсов (БД, кэши, конфигурация)
  4. Для Prototype/Request scope — вызывается каждый раз при создании нового bean'а
  5. Если выбросишь исключение — приложение не запустится
  6. Всегда paired с @PreDestroy для cleanup'а ресурсов