🍃 Раздел 5 · Вопрос #24

Что такое @Configuration класс

В то время как стереотипные аннотации (@Service, @Repository, @Component) ставятся прямо над классами вашего проекта, аннотация @Configuration необходима для:


🟢 Junior Level

@Configuration — это аннотация Spring, которой помечается Java-класс, выступающий в роли фабрики и источника определений бинов (BeanDefinition). Внутри такого класса методы помечаются аннотацией @Bean.

Зачем нужен класс @Configuration

В то время как стереотипные аннотации (@Service, @Repository, @Component) ставятся прямо над классами вашего проекта, аннотация @Configuration необходима для:

  1. Подключения сторонних библиотек: вы не можете поставить @Service над классом из чужого JAR-архива (например, над ObjectMapper из Jackson, AmazonS3 из AWS SDK или HikariDataSource).
  2. Сложной программной настройки: когда создание объекта требует вызова нескольких сеттеров, условий, чтения переменных среды или вызова фабричных методов.

Базовый пример

package com.example.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        // Тонкая программная настройка стороннего класса
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        return mapper;
    }
}

🟡 Middle Level

Разрешение зависимостей: Вызов метода vs Параметры метода

Если один бин зависит от другого, определенного в том же конфигурационном классе, есть два способа передачи зависимости:

@Configuration
public class SecurityAppConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // Способ 1: Прямой вызов метода (Inter-Bean Reference)
    @Bean
    public UserService userService() {
        return new UserService(passwordEncoder()); // CGLIB перехватит вызов!
    }

    // Способ 2: Передача через параметры метода (Рекомендуемый способ)
    @Bean
    public AuthService authService(PasswordEncoder passwordEncoder) {
        return new AuthService(passwordEncoder); // Spring сам внедрит синглтон
    }
}

Full Mode vs Lite Mode: Главное архитектурное различие

Spring поддерживает два режима работы конфигурационных классов, определяемых параметром proxyBeanMethods:

Критерий Full Mode (proxyBeanMethods = true) Lite Mode (proxyBeanMethods = false)
Как объявляется @Configuration (по умолчанию) @Configuration(proxyBeanMethods = false) или @Component с @Bean
Проксирование Класс оборачивается в CGLIB-прокси Прокси не создается (чистый POJO)
Прямой вызов b(a()) Прокси перехватывает вызов a() и возвращает существующий синглтон из кэша Выполняется обычный вызов Java-метода → создается новый дублирующий объект!
Ограничения Java Класс и методы не могут быть final Классы и методы могут быть final
Скорость старта Медленнее (генерация CGLIB-байткода) Быстрее на 30–50%, меньше расход Metaspace
GraalVM Native Требует подсказок отражения Полностью Native-friendly из коробки

🔴 Senior Level

Внутренний механизм Full Mode: ConfigurationClassPostProcessor

На самом раннем этапе старта приложения системный процессор ConfigurationClassPostProcessor (реализующий BeanDefinitionRegistryPostProcessor) сканирует все классы с @Configuration:

  1. Если proxyBeanMethods == true, Spring задействует класс ConfigurationClassEnhancer.
  2. С помощью CGLIB генерируется подкласс: SecurityAppConfig$$SpringCGLIB$$0 extends SecurityAppConfig.
  3. В подкласс встраивается перехватчик BeanMethodInterceptor:
// Упрощенная логика работы CGLIB BeanMethodInterceptor в Spring Framework:
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) {
    String beanName = getBeanName(method);

    // Если контейнер прямо сейчас создает этот бин через фабрику:
    if (isCurrentlyInvokedFactoryMethod(method)) {
        // Вызываем оригинальный конструктор в суперклассе
        return proxy.invokeSuper(obj, args);
    }

    // Если метод вызван из другого @Bean-метода: перенаправляем запрос в BeanFactory!
    return this.beanFactory.getBean(beanName);
}
Вызов new UserService(passwordEncoder()):
   1. userService() начинает выполняться.
   2. Вызывается passwordEncoder().
   3. Вызов перехватывается CGLIB BeanMethodInterceptor.
   4. Проверяется singletonObjects cache в BeanFactory.
   5. Возвращается УЖЕ СОЗДАННЫЙ ранее экземпляр BCryptPasswordEncoder.
   6. Новый объект BCryptPasswordEncoder НЕ создается!

Опасность Lite Mode: случайное нарушение Singleton-семантики

Если в конфигурации с proxyBeanMethods = false разработчик по привычке связывает бины прямым вызовом:

@Configuration(proxyBeanMethods = false) // Lite Mode
public class BadLiteConfig {

    @Bean
    public DatabasePool pool() {
        return new DatabasePool(); // Экземпляр 1
    }

    @Bean
    public OrderRepo orderRepo() {
        // ОШИБКА: Прямой вызов pool() создаст ВТОРОЙ независимый пул соединений!
        return new OrderRepo(pool()); 
    }

    @Bean
    public UserRepo userRepo() {
        // ОШИБКА: Создаст ТРЕТИЙ независимый пул соединений!
        return new UserRepo(pool()); 
    }
}

Результат: вместо одного общего пула соединений приложение откроет 3 независимых пула в БД, исчерпав все доступные коннекты к серверу!

Как писать правильно в Lite Mode:

Все зависимости передаются только через параметры методов:

@Configuration(proxyBeanMethods = false)
public class CorrectLiteConfig {

    @Bean
    public DatabasePool pool() {
        return new DatabasePool();
    }

    @Bean
    public OrderRepo orderRepo(DatabasePool pool) { // Spring передаст один и тот же синглтон
        return new OrderRepo(pool);
    }

    @Bean
    public UserRepo userRepo(DatabasePool pool) {
        return new UserRepo(pool);
    }
}

@Bean методы внутри обычного @Component

Если объявить @Bean-метод внутри @Service, @Component или @Controller, Spring обработает его строго в Lite Mode. CGLIB-проксирование для таких классов не настраивается. Поэтому связывать бины прямым вызовом методов внутри @Component категорически нельзя.


🎯 Шпаргалка для интервью

30-секундный ответ

Класс с аннотацией @Configuration — это источник бинов Spring, содержащий фабричные методы с аннотацией @Bean. Применяется для интеграции сторонних библиотек и комплексной программной настройки. Имеет два режима:

  1. Full Mode (proxyBeanMethods = true, по умолчанию): класс оборачивается в CGLIB-прокси. Прямые вызовы @Bean-методов друг из друга перехватываются и возвращают зарегистрированный синглтон из контейнера. Класс и методы не могут быть final.
  2. Lite Mode (proxyBeanMethods = false): CGLIB-прокси не создается, старт быстрее, меньше памяти. Прямые вызовы методов порождают новые дубликаты объектов, поэтому зависимости необходимо передавать через аргументы методов.

4 каверзных вопроса с ответами

  1. В чём разница между Full Mode и Lite Mode в @Configuration? Ответ: В Full Mode Spring генерирует CGLIB-прокси для класса конфигурации, перехватывая прямые вызовы @Bean-методов для сохранения singleton-семантики (beanFactory.getBean()). В Lite Mode CGLIB-прокси отсутствует: методы являются обычными Java-методами, а их прямой вызов порождает новый экземпляр класса в обход контекста.

  2. Почему класс с @Configuration в Full Mode не может быть объявлен как final? Ответ: Full Mode использует CGLIB для генерации прокси. CGLIB работает через наследование (создание класса-потомка $$SpringCGLIB$$). В спецификации Java ключевое слово final запрещает наследование от класса и переопределение методов. Попытка объявить @Configuration класс как final приведет к ошибке старта контекста.

  3. Что произойдет, если объявить метод @Bean внутри класса с аннотацией @Component или @Service? Ответ: Метод будет успешно обработан, и возвращаемый объект зарегистрируется как бин. Однако такой класс будет работать в режиме Lite Mode: прямые вызовы соседних @Bean-методов не будут перехватываться CGLIB, и вызовы создадут новые неконтролируемые объекты.

  4. Зачем в Spring Boot 2.2+ добавили параметр proxyBeanMethods = false? Ответ: Для оптимизации старта и расхода памяти. Отключение CGLIB-проксирования ускоряет запуск на десятки процентов, экономит память Metaspace и является обязательным требованием для эффективной AOT-компиляции в GraalVM Native Image.

Красные флаги (чего категорически нельзя говорить)

  • ❌ «В Lite Mode аннотация @Bean перестает создавать бины» — бины создаются штатно; отключается только CGLIB-перехват межбиновых вызовов.
  • ❌ «В Full Mode вызов @Bean-метода всегда заново выполняет тело метода» — интерцептор CGLIB возвращает кэшированный синглтон из BeanFactory.
  • ❌ «@Configuration класс можно смело делать final» — в Full Mode это вызовет ошибку JVM при создании подкласса CGLIB.
  • ❌ «В Lite Mode нельзя передавать зависимости между @Bean-методами» — можно и нужно, но через аргументы методов (@Bean public B b(A a)).

Связанные темы