JavaRush /Курсы /Spring Core /Финальный conceptual checkpoint

Финальный conceptual checkpoint

Spring Core
25 уровень , 4 лекция
Открыта

1. Conceptual checkpoint: цель

Conceptual checkpoint — это не список терминов, которые нужно выучить как таблицу умножения, а проверка, что у вас в голове сложилась причинно-следственная цепочка. Типичный провал новичка выглядит так: он помнит @Component, @Bean, @Profile, но не может связать их в историю «зачем это нужно и что реально происходит при старте приложения». Чекпоинт — это попытка собрать из “слов” модель, которая держит Spring в руках, а не держит вас.

Чтобы было проще, представьте ситуацию «вас разбудили в 3 ночи» (не рекомендую, но методически полезно). Вам задают вопрос: «Почему сервис стал proxy?» или «Почему приложение падает на старте, хотя метод placeOrder() даже не вызвали?» Если вы отвечаете не фразой “ну Spring такой”, а связываете ответ с контейнером, фазами старта, lifecycle и wiring — вы прошли чекпоинт.

Здесь полезнее не коллекционировать термины, а на каждом узле быстро проверять себя: видите ли вы причину появления механизма, место этого механизма в старте приложения и типичную путаницу рядом с ним.

Ниже — компактная «карта разговора». Это не шпаргалка «как заклинание», а ориентир: какой смысловой блок за какой тянется.

Блок понимания Что вы должны уметь проговорить одним абзацем Где это видно в ContextFlow
Manual wiring → DI Почему new внутри сервисов ломает масштабирование и тестируемость ранние версии OrderPlacementService и bootstrap
IoC / composition root Где должна собираться система и почему это одна точка AppConfig, затем модульные конфиги
ApplicationContext Почему это “центр приложения”, а не просто фабрика объектов main стартует контекст, достаёт ScenarioRunner
BeanDefinition и фазы Что происходит до создания объектов и почему ошибки возникают “ещё до бизнеса” startup failures, post-processors
Registration (scanning/@Bean) Как класс превращается в bean и почему @Configuration — не просто @Component @ComponentScan, @Import, @Bean
Dependency resolution Что происходит при нескольких реализациях интерфейса NotificationSender, DiscountPolicy
Lifecycle / scopes Когда bean “готов”, как работает destroy, и почему scope — свойство контейнера NotificationTemplateCatalog, prototype sessions
Environment / properties Почему конфигурация — часть runtime, а не “строки в коде” contextflow.properties, @Value
Profiles / conditions Как менять состав бинов без if-else в бизнес-логике dev/demo/test режимы
Resource / MessageSource Чем “ресурс приложения” отличается от файла и зачем i18n без web templates, message bundles
Events Зачем события внутри приложения и почему они синхронны по умолчанию OrderCreatedEvent, listeners
Extension points BFPP/BPP/FactoryBean/Aware: что это и почему нельзя тащить в бизнес support.* пакет
Proxies / AOP Откуда берётся поведение “вокруг метода” и почему self-invocation больно ServiceTimingAspect
XML + tests XML как тот же контейнер и тесты как проверка сборки legacy xml bridge, smoke tests

2. Большая схема ContextFlow

В финале курса важно не утонуть в деталях и удержать “одну большую картинку”. Spring Core — это история о том, как приложение стартует, как контейнер читает метаданные, создаёт объектный граф, применяет инфраструктурные механики и только потом отдаёт вам точку входа уровня бизнес-сценариев. Если вы видите эту схему, почти любая аннотация перестаёт быть магией и становится просто меткой в нужном месте пайплайна.

Ниже — условная блок-схема (в жизни шагов больше, но нам важны смысловые вехи):

flowchart TD
    A["main()"] --> B[создаём ApplicationContext]
    B --> C[регистрируем конфигурацию / scanning]
    C --> D["refresh(): читаем BeanDefinition"]
    D --> E[post-processors: BFPP/BPP]
    E --> F[создаём singleton beans]
    F --> G[готовый контекст]
    G --> H["ScenarioRunner.run()"]
    H --> I["use-case публикует events"]
    I --> J[listeners делают side effects]

Если эта схема действительно у вас в голове, вы должны без подсказки восстановить короткий pure Spring scaffold:

1. создать AnnotationConfigApplicationContext;
2. до refresh() выбрать активный profile;
3. зарегистрировать AppConfig;
4. вызвать refresh(), чтобы контейнер прочитал definitions и собрал singleton-граф;
5. получить ScenarioRunner и запустить сценарий;
6. закрыть context, чтобы отработали destroy-callbacks.

Если на этом месте путаются шаги 2, 3 и 4, значит ещё не до конца собралась причинно-следственная цепочка между конфигурацией, startup pipeline и runtime-поведением приложения.

3. Manual wiring и IoC/DI

Первый вопрос к себе: можете ли вы за полминуты объяснить, почему new внутри сервиса ломает рост проекта и где после этого должен жить composition root? Если ответ сводится к “Spring потом подставит зависимость”, значит узел ещё не собран.

Смысл не в том, что new как слово запрещён. Смысл в том, что business-class не должен сам решать, какую реализацию ему создавать: при росте проекта это превращается в tight coupling и делает тесты дороже.

Плохая версия выглядит примерно так: бизнес-сервис сам решает, какие реализации ему нужны, и сам их создаёт. Это прямая дорога к tight coupling.

public class OrderPlacementService {

    public void placeOrder(String orderId) {
        // Плохо: сервис сам создаёт зависимость и жёстко привязывается к конкретной реализации.
        // Это усложняет тестирование (нельзя подменить sender) и масштабирование (появляются if-else и копипаста).
        NotificationSender sender = new ConsoleNotificationSender();

        // Плохо: бизнес-логика начинает «знать», как именно отправлять уведомления.
        sender.send("Order created: " + orderId);
    }
}

Хорошая версия — зависимости видны в конструкторе. Класс честно говорит: «Я работаю через контракт NotificationSender, а не через конкретный Console...». И вот это уже DI-friendly дизайн, который Spring потом просто автоматизирует.

public class OrderPlacementService {

    private final NotificationSender sender;

    public OrderPlacementService(NotificationSender sender) {
        // Хорошо: зависимость приходит извне (DI), её можно подменить в тесте или в другом профиле.
        this.sender = sender;
    }
}

Хороший короткий ответ здесь звучит так: зависимости становятся явными, класс проще подменять в тестах, а Spring лишь автоматизирует сборку такого графа. Если в ответе нет слов “явные зависимости”, “подменяемость” и “testability”, фундамент ещё шатается.

4. BeanFactory и две фазы старта

Следующий вопрос: можете ли вы без путаницы развести BeanFactory, ApplicationContext и две фазы старта? Если в ответе нет метаданных до объектов, startup failures будут казаться магией.

BeanFactory — базовый контейнер. ApplicationContext — его “повседневная расширенная версия”: она умеет Environment, ресурсы, сообщения, события и многое из того, что вы реально используете в приложении. Ключевая мысль при этом одна: bean сначала живёт как definition, и только потом становится реальным объектом.

Пусть пример и диагностический, но он делает модель наблюдаемой:

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
    // Здесь мы смотрим на метаданные (definition), а не на реальный объект-сервис.
    BeanDefinition bd = context.getBeanFactory()
            // Имя "orderPlacementService" — это bean name, по нему definition хранится в фабрике.
            .getBeanDefinition("orderPlacementService");

    // Для @Component это обычно будет имя класса, которое Spring собирается создать.
    System.out.println(bd.getBeanClassName()); // com.example.contextflow.application.service.OrderPlacementService
}

Если вы спокойно проговариваете “сначала definition, потом instance”, перестают быть странными BFPP, profiles и ошибки на старте. Именно эта связка и объясняет, почему контейнер умеет падать ещё до первого бизнес-вызова.

5. Регистрация бинов и модули

Здесь проверьте две вещи. Во-первых, можете ли вы быстро назвать, из каких модулей складывается финальный composition root. Во-вторых, понимаете ли вы, почему @Configuration — это место сборки графа, а не склад случайных аннотаций.

Когда проект маленький, хочется держать всё в одном AppConfig. Когда проект чуть-чуть подрос — хочется сделать ещё один AppConfig. Когда проект вырос нормально — хочется спрятаться под стол. Поэтому мы и учили дисциплину: модульная конфигурация и понятные границы scanning.

Финальная сборка ContextFlow выглядит спокойно: один верхний конфиг, который импортирует модули.

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
// Верхний конфиг (composition root): он собирает приложение из модулей и задаёт явные границы.
@Import({
        // База приложения: доменные сервисы/порты/репозитории и т.п.
        CoreConfig.class,

        // Профили (dev/demo/test) и вариативность поведения.
        ProfilesConfig.class,

        // Отчёты, форматирование и вывод.
        ReportingConfig.class,

        // Мост к легаси XML-фрагменту.
        LegacyBridgeConfig.class,

        // Инфраструктурные расширения Spring: BFPP/BPP/FactoryBean/AOP и диагностика.
        SupportConfig.class
})
public class AppConfig {
}

Если, восстанавливая эту картину, вы забываете SupportConfig, значит из головы выпадают post-processors, FactoryBean, диагностика и AOP. А рядом полезно удерживать вторую мысль: scanning — это одна из registration strategies, а не магия “само всё найдётся”.

6. Разрешение зависимостей

Теперь вопрос про wiring: что делает контейнер, когда у интерфейса несколько реализаций? И чем Map<String, NotificationSender> лучше ручного if-else new ...?

Хороший ответ должен звучать так: контейнер либо выбирает кандидата по умолчанию, либо мы делаем выбор явным, либо получаем набор стратегий и маршрутизируем уже готовые beans.

Очень хороший checkpoint — уметь объяснить, почему Map<String, NotificationSender> — это не “странная фишка Spring”, а контейнерный способ построить реестр стратегий. Ключ в Map — это имя bean-а, а не “рандомная строка из воздуха”. Пример:

import org.springframework.stereotype.Service;
import java.util.Map;

@Service
public class NotificationDispatchService {

    private final Map<String, NotificationSender> senders;

    public NotificationDispatchService(Map<String, NotificationSender> senders) {
        // Spring соберёт сюда все NotificationSender-бины, которые есть в контексте.
        // Ключ в Map — это bean name (например, "emailNotificationSender"), а значение — сам bean.
        this.senders = senders;
    }
}

Дальше вы либо выбираете отправителя по конфигурации (например, defaultChannel), либо делаете маленький router. И ключевой момент: вы не создаёте реализации сами, вы не делаете if (channel == EMAIL) new EmailSender(), вы просите контейнер дать вам готовые стратегии.

Optional зависимости — это отдельная зона осторожности. Здесь важно уметь объяснить, что Optional/ObjectProvider — это не “обход DI”, а способ честно сказать: “эта интеграция может отсутствовать”. И ещё важнее — уметь отличать это от service locator, где бизнес-сервис “вдруг” начинает искать бины в рантайме.

7. Lifecycle и scopes

Здесь проверьте, можете ли вы объяснить, когда bean действительно готов к работе и почему shutdown — не декоративный финал. Если в ответе нет init/destroy и scope как свойства контейнера, картина ещё плавает.

Пока вы не знаете lifecycle, вы воспринимаете приложение как “я создал объект — он работает”. В Spring это недостаточно: контейнер должен создать объект, внедрить зависимости, выполнить init-callbacks, и только потом вы можете считать bean готовым. А на shutdown он должен корректно вызвать destroy-callbacks — если вы не забыли закрыть контекст. Это особенно важно для ресурсных штук: каталогов шаблонов, менеджеров файлового вывода, кешей и т.п.

Ниже — короткий, почти “учебник для будущего себя” пример:

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;

@Component
public class NotificationTemplateCatalog {

    @PostConstruct
    void init() {
        // init-callback: выполняется после создания bean-а и внедрения зависимостей
        System.out.println("templates loaded"); // templates loaded
    }

    @PreDestroy
    void shutdown() {
        // destroy-callback: выполнится при закрытии ApplicationContext (например, try-with-resources)
        System.out.println("templates released"); // templates released
    }
}

Теперь scopes. Вы должны уметь проговорить, что Spring-singleton — это singleton per container, а не GoF Singleton на весь JVM-мир. И уметь объяснить ловушку “prototype в singleton”: prototype создаётся при инъекции, а не “на каждый вызов”. Поэтому контролируемое создание prototype обычно делается через deferred lookup (например, ObjectProvider), а не через наивную инъекцию “одного prototype-объекта”.

Если вы можете на словах объяснить, почему “singleton — должен быть stateless”, и как состояние переносится в method-local данные или короткоживущие сессии, значит вы не построите себе будущий баг “иногда работает, иногда нет”.

8. Environment, properties, profiles

Если попросить вас коротко объяснить Environment, properties и profiles, хороший ответ должен звучать не “это строки из файла”, а “это часть runtime-сборки приложения”. Следующий контрольный вопрос — понимаете ли вы, что profile меняет состав контекста, а не просто значение переменной.

В какой-то момент любой проект упирается в то, что значения “вшиты” в код: путь вывода отчёта, режим аудита, дефолтный канал уведомлений. В этот момент появляются либо хардкод и страдания, либо нормальная конфигурация. В Spring Core мы учились именно второму: Environment, property sources, @Value, и аккуратная типизация через conversion, чтобы не тащить по проекту if ("CSV".equalsIgnoreCase(format)).

Самый простой checkpoint: вы можете объяснить, откуда берётся значение для ${contextflow.app-name}, какой у него порядок поиска, и почему дефолтные значения важны, если у пользователя нет файла конфигурации.

Профили — следующая ступень. Здесь важно уметь объяснить, что profiles — это не “переключатель фич”, а режим сборки приложения. В test-профиле мы хотим детерминированный генератор id и no-op уведомления, чтобы тесты были стабильными. В demo — предсказуемое поведение для демонстрации. В dev — максимально простой console-run.

Минимальный пример профильного бина выглядит так:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
public class ProfilesConfig {

    @Bean
    @Profile("test")
    OrderIdGenerator orderIdGenerator() {
        return new DeterministicOrderIdGenerator();
    }
}

Если вы можете объяснить, что профили меняют состав контекста, а не “просто значение переменной”, и что это чище, чем if (mode.equals("test")) внутри бизнес-сервиса, то вы унесли правильную практику из курса.

9. Resources и MessageSource

Проверьте себя на двух различениях: Resource vs File и MessageSource vs “строки прямо в коде”. Если эти пары сливаются, потом трудно понять, почему classpath-ресурс нельзя мыслить как произвольный файл на диске и почему тексты лучше держать вне Java-классов.

Почти у каждого новичка есть фаза “я читаю файлы через new File("...") и абсолютные пути”. И почти у каждого новичка есть фаза “почему в jar это сломалось?”. Spring Resource abstraction как раз создана, чтобы вы перестали связывать “ресурс приложения” и “файл на диске” в одну сущность. В нашем курсе это проявилось в шаблонах уведомлений и заголовках отчётов: они живут в classpath, а артефакты мы пишем только в build/.

Параллельно MessageSource даёт i18n даже без web. В non-web приложении Locale — это не “язык браузера”, а явно выбранная настройка: по клиенту или по дефолту. В checkpoint вы должны уметь объяснить, почему текст сообщений выносится из кода, и как работает fallback, когда локализованного ключа нет.

Вот маленький “обёрточный” сервис для сообщений:

import org.springframework.context.MessageSource;
import org.springframework.stereotype.Service;

import java.util.Locale;

@Service
public class ContextFlowMessages {

    private final MessageSource messageSource;

    public ContextFlowMessages(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    public String orderCreated(Locale locale, String orderId) {
        return messageSource.getMessage("order.created", new Object[]{orderId}, locale);
    }
}

Если вы можете проговорить, что MessageSource — это часть ApplicationContext, а не “какая-то отдельная библиотека”, и что ключи сообщений — это контракт (примерно как API-контракт, только текстовый), то вы действительно понимаете роль контекста как runtime среды.

10. Application events

Здесь главный checkpoint-вопрос такой: что вы публикуете — факт или команду? И помните ли вы, что listeners по умолчанию работают синхронно?

События — это место, где очень легко “переборщить”. Поэтому чекпоинт здесь не “знать аннотацию”, а уметь объяснить границу применимости. В ContextFlow мы вынесли side effects (аудит, уведомления, статистику) из use-case сервисов в listeners, чтобы основной сценарий стал читабельным. Но при этом мы не превращали весь бизнес-процесс в цепочку событий, где невозможно понять, что происходит и в каком порядке.

Публикация события в use-case сервисе выглядит максимально скучно — и это хорошо:

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

@Service
public class OrderPlacementService {

    private final ApplicationEventPublisher publisher;

    public OrderPlacementService(ApplicationEventPublisher publisher) {
        // Паблишер — инфраструктурная зависимость: через него мы публикуем события в ApplicationContext.
        this.publisher = publisher;
    }

    public void place(String orderId) {
        // Публикуем доменное событие: мы сообщаем "что случилось", а не "как делать side effects".
        publisher.publishEvent(new OrderCreatedEvent(orderId));
    }
}

А обработчик side effect — отдельный bean. И ключевое: по умолчанию этот обработчик вызовется синхронно, в том же потоке, до завершения place().

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class AuditOrderEventsListener {

    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        // По умолчанию слушатель вызывается синхронно в потоке, который публикует событие.
        // Поэтому тяжёлые операции здесь могут замедлить основной use-case.
        System.out.println("AUDIT " + event.orderId()); // AUDIT ORD-001
    }
}

Если вы можете объяснить разницу “events внутри приложения” vs “messaging между сервисами” (Kafka/RabbitMQ), и не путаете синхронные listeners с асинхронностью, значит вы держите архитектуру в руках, а не просто “делаете модно”.

11. Extension points Spring

Эта часть проверяет не любовь к API, а понимание pipeline. Можете ли вы спокойно развести BFPP, BPP, FactoryBean и Aware по месту и роли? И можете ли сразу сказать, почему всё это держим в support, а не в business layer?

BeanFactoryPostProcessor работает на уровне метаданных и запускается до создания объектов. Это идеальное место для sanity-check на конфигурацию, но плохое место для “делать бизнес”.

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

@Component
public class ContextFlowSanityCheckBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) {
        System.out.println("Bean definitions: " + bf.getBeanDefinitionCount()); // Bean definitions: 87
    }
}

BeanPostProcessor трогает уже созданные beans и может оборачивать их, добавлять поведение, модифицировать. Через BPP растут многие “магические” механики, включая auto-proxying.

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class TrackedComponentBeanPostProcessor implements BeanPostProcessor {
    public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
        return bean;
    }
}

FactoryBean — особый случай: bean в контейнере является фабрикой, а “наружу” контейнер отдаёт продукт. Вы должны уметь объяснить отличие FactoryBean от обычного @Bean-метода и помнить про &beanName, когда нужен именно объект-фабрика.

import org.springframework.beans.factory.FactoryBean;

public class ReportFormatterFactoryBean implements FactoryBean<ReportFormatter> {
    public ReportFormatter getObject() { return new TextReportFormatter(); }
    public Class<?> getObjectType() { return ReportFormatter.class; }
}

Aware — самый опасный из “удобных” инструментов, потому что им легко заразить business layer контейнерными деталями. В курсе мы держали его в диагностике: да, иногда нужно узнать Environment, имя bean-а или контекст, но это не повод превращать сервисы в “контейнеро-зависимые”.

import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class ContextDiagnosticsBean implements EnvironmentAware {
    public void setEnvironment(Environment environment) {
        System.out.println(environment.getActiveProfiles().length); // 1
    }
}

Если вы можете объяснить, что BFPP — “до объектов”, BPP — “после создания”, FactoryBean — “bean производит другой объект”, а Aware — “контейнер сообщает что-то bean-у”, и при этом добавляете фразу “это не должно течь в business layer”, то вы прошли один из самых важных conceptual узлов Spring.

12. Proxy-модель и AOP basics

Проверьте, можете ли вы объяснить proxy-модель без слова “магия”: кто такой proxy, кто target и почему self-invocation обходит advice.

Понимание proxy-модели — это то, что делает будущие темы (@Transactional, security interception) не магией, а “ну да, прокси вокруг метода”. Чекпоинт тут предельно практичный: вы должны различать proxy и target object, понимать, что перехватываются вызовы через container-managed reference, и помнить ограничения: final, private, self-invocation.

Минимальный аспект в нашем проекте — измерение времени выполнения сервисных методов. Он не должен содержать бизнес-логику, только техническую обвязку.

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class ServiceTimingAspect {

    @Around("execution(* com.example.contextflow.application.service..*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        return pjp.proceed();
    }
}

Даже если этот пример “ничего не логирует”, он уже позволяет вам объяснить главное: Spring не переписывает ваши классы в рантайме “как хочет”, он создаёт proxy, который перехватывает вызовы и делает что-то до/после. И если внутри класса вы вызвали свой же метод напрямую, прокси не участвовал — значит advice не сработал. Это не баг Spring, это честная модель.

13. Legacy XML и тесты контекста

Финальный технический checkpoint: умеете ли вы спокойно читать XML как тот же bean model и понимаете ли вы, что context smoke test проверяет сборку, а не бизнес-математику?

Финальный checkpoint должен снять страх перед двумя вещами: XML и “тесты со Spring-контекстом”. XML — это просто другой синтаксис описания тех же beans и тех же зависимостей. Вы должны уметь посмотреть на такую запись и прочитать её как bean definition, а не как “древнюю магию”.

<bean id="consoleAuditWriter"
      class="com.example.contextflow.infrastructure.audit.ConsoleAuditWriter"/>

Подключение маленького legacy-фрагмента в современный Java-config проект делается мостом, и вы должны уметь объяснить: “мы не переезжаем в XML, мы временно импортируем кусок, потому что legacy так живёт”.

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

@Configuration
@ImportResource("classpath:legacy/legacy-notification-context.xml")
public class LegacyBridgeConfig {
}

А тесты контекста — это не “тесты всего подряд через Spring”, а проверка того, что сборка контейнера реальна, воспроизводима и работает в нужном профиле. Иногда лучший тест — это тот, который просто поднимает контекст и не падает.

import org.junit.jupiter.api.Test; // В курсе используем JUnit Jupiter; версия фиксируется репозиторием.
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig(AppConfig.class)
@ActiveProfiles("test")
class ContextSmokeTest {

    @Test
    void contextStarts() {
        // Smoke test: проверяет сборку контекста (wiring/profiles/properties),
        // а не бизнес-логику создания заказа.
    }
}

Если вы можете объяснить, что этот тест проверяет wiring/profiles/properties, а не “бизнес-логику создания заказа”, вы понимаете границу unit vs context testing, а значит не превратите проект в медленный и хрупкий тестовый монолит.

14. Типичные ошибки

Ошибка №1: отвечать списком терминов вместо истории.
Очень частый “псевдо-успех”: студент говорит «BeanFactory, ApplicationContext, BeanDefinition, @Component, @Bean…», но не может связать это в объяснение “как приложение стартует”. Лечится просто: каждый ответ начинайте с проблемы, потом механизм, потом эффект в проекте. Не “какие аннотации”, а “какой шаг контейнера”.

Ошибка №2: путать BeanFactoryPostProcessor и BeanPostProcessor.
В голове это часто сливается в “какой-то процессор”. Стабильная опора: BFPP работает с метаданными до создания объектов, BPP — с уже созданными объектами (до/после init). Если вы эту фразу можете произнести спокойно, вы уже не перепутаете их в реальной работе.

Ошибка №3: делать вид, что proxy — это “только в AOP-лекции”.
Потом при встрече с @Transactional легко решить, что транзакции “встроены в метод”. Нет: proxy-модель — это фундамент. Если вы не держите в голове “есть proxy и есть target”, вы будете ловить странности instanceof, self-invocation и “почему не сработало”.

Ошибка №4: называть bean-ом всё, что движется.
Доменные объекты (Order, OrderItem, команды) обычно не должны быть bean-ами. Bean — это то, чем управляет контейнер: сервисы, инфраструктура, конфигурационные штуки. Если вы делаете из каждого доменного экземпляра bean, вы быстро получаете кашу из scopes, состояния и неявных зависимостей.

Ошибка №5: скатываться в service locator под видом “так быстрее”.
Как только в бизнес-коде появляется ApplicationContext.getBean(...) или статический holder контекста, вы теряете DI как принцип. Код начинает “искать мир вокруг себя”, а не получать зависимости. На небольшом проекте оно даже будет работать… пока не начнёт ломаться там, где вы не ждёте.

1
Задача
Spring Core, 25 уровень, 4 лекция
Недоступна
Минимальная демонстрация startup pipeline
Минимальная демонстрация startup pipeline
1
Задача
Spring Core, 25 уровень, 4 лекция
Недоступна
Единая bean model для Java config и XML
Единая bean model для Java config и XML
1
Опрос
Spring Core, 25 уровень, 4 лекция
Недоступен
Spring Core
Контекст и конфигурация
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ