JavaRush/Java блог/Архив info.javarush/Spring Framework. Введение
Marchello
20 уровень

Spring Framework. Введение

Статья из группы Архив info.javarush
участников
Привет! Пока администрация JavaRush работает над новыми уровнями я хочу начать серию обучающих статей по Spring Framework. Да, я знаю, что в сети уже много материала на эту тему, но, как показывает практика, все они на уровне Hello World’a. Я же хочу рассказать не о том, как правильно расставить аннотации, а о том, как это все устроено «под капотом». Статья рассчитана на тех, кто уже так или иначе работал с этим фреймворком и знаком с основными понятиями. Spring Framework. Введение - 1

Инициализация контекста.

Итак, начнем с основ. На мой взгляд, одним из наиважнейших моментов является понимание того, как происходит настройка контекста и инициализаци бинов. Как известно, прежде чем Spring начнет работать его необходимо сконфигурировать. В допотопные времена это делали с помощью xml файлов (на некоторых проектах, преимущественно старых, продолжают делать это до сих пор). Вот небольшой пример такого конфигурационного файла:
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

   <bean id="helloWorld" class="ru.javarush.HelloWorld">
       <property name="message" value="Hello World!"/>
   </bean>

</beans>
По-большому счету, этого достаточно, чтобы создать пару контроллеров и запустить стартап (который не взлетит). Но как эта конфигурация заставит работать Spring? А вот тут начинается самое интересно. Для того чтобы наша конфигурация была понята Spring’ом существует XmlBeanDefinitionReader. Это внутренний компонент Spring’a, который сканирует (парсит) xml и на основе того, что мы там написали создает BeanDefinition’ы. BeanDefinition – это объект, который хранит в себе информацию о бине. Сюда входит: из какого класса его (бин) надо создать; scope; установлена ли ленивая инициализация; нужно ли перед данным бином инициализировать другой и иные проперти, которые описаны в xml. Все полученные BeanDefinition’ы складываются в HashMap, в которой идентификатором является имя бина (установленное вами или присвоенное спрингом) и сам BeanDefinition объект. После того, как все BeanDefinition’ы созданы на сцену выходит новый герой – BeanFactory. Этот объект итерируется по HashMap’e с BeanDefinition’ами, создает на их основе бины и складывает в IoC контейнер. Здесь есть нюанс, на самом деле, при старте приложения, в IoC контейнер попадут бины, которые имеют scope Singleton (устанавливается по-умолчанию), остальные же создаются, тогда когда они нужны (prototype, request, session). А теперь небольшое отступление, познакомимся с еще одним персонажем.

Встречайте — BeanPostProcessor. (BPP)

bean post processorДело в том, что бин это не обязательно класс бизнес-логики вашего приложения. Бином также называют инфраструктурный бин. Вкратце, инфраструктурный бин это класс, который настраивает бины вашей бизнес-логики (да-да, слишком много бинов). Подробнее о нём я расскажу ниже, но чтобы было чуточку понятнее, что именно BPP настраивает, приведу пример. Всем же знакома аннотация @Autowired? Так вот, именно AutowiredAnnotationBeanPostProcessor ответственен за то, чтобы все ваши классы были внедрены друг в друга. uknowimean

Вернемся к BeanFactory

Зная теперь о BPP, нужно уточнить, что итерируясь по HashMap’e с BeanDefinition’ами сперва создаются и кладутся отдельно (не в IoC контейнер) все BeanPostProcessor’ы. После этого создаются обычные бины нашей бизнес-логики, складываются в IoC-контейнер и начинается их настройка с помощью отдельно отложенных BPP. А происходит это вот как, каждый BPP имеет 2 метода:
postProcessorBeforeInitialization(Object bean, String beanName);
postProcessorAfterInitialization(Object bean, String beanName);
Происходит итерация по нашим бинам дважды. В первый раз вызывается метод postProcessorBeforeInitialization, а во второй раз вызывается метод postProcessorAfterInitialization. Наверняка возник вопрос, зачем нужны два метода, объясняю. Дело в том, что для обработки некоторых аннотаций (таких как @Transactional, например) наш бин заменяется proxy классом. Чтобы понять зачем это делается, нужно знать как работает @Transactional, а работает это вот как. В метод, помеченный данной аннотацией необходимо налету добавить еще пару строк кода. Как это сделать? Верно, с помощью создания класса proxy, внутри которого и будет добавлен необходимый код в нужный метод. А теперь представим такую ситуацию, у нас есть класс:
class A {
    @Autowired
    private SomeClass someClass;

    @Transactional
    public void method() {
        // модификатор доступа обязательно public
    }
}
В этом классе 2 аннотации @Autowired и @Transactional. Обе аннотации обрабатываются разными BPP. Если первым отработает AutowiredBPP, то все будет в порядке, но если нет, то мы столкнемся с вот какой проблемой. Дело в том, что когда создается класс proxy, то вся мета-информация теряется. Другими словами, информации об аннотации @Autowired в proxy классе не будет, а значит и AutowiredBPP не отработает, а значит наше поле someClass будет иметь значение null, что, скорее всего, приведет к NPE. Также стоит знать, что между вызовами методов postProcessorBeforeInitialization и postProcessorAfterInitialization происходит вызов init-метода, если он есть. Это по-большому счету второй конструктор, но отличие в том, что в этот момент все наши зависимости уже внедрены в класс и мы можем к ним обратиться из init-метода. Итак, еще раз алгоритм инициализации контекста:
  1. XmlBeanDefinitionReader сканирует наш xml-конфигурационный файл.
  2. Создает BeanDefinition’ы и кладет их в HashMap.
  3. Приходит BeanFactory и из этой HashMap отдельно складывает все BeanPostProcessor’ы.
  4. Создает из BeanDefinition’ов бины и кладет их в IoC-контейнер.
  5. Тут приходят BPP и настраивают эти бины с помощью 2х методов.
  6. Готово.
Собственно, на этом всё, пишите понравилась вам статья и стоит ли продолжать писать подобные туториалы.
Комментарии (13)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Дмитрий
Уровень 39
19 июня 2022, 11:10
А где BeanFactoryPostProcessor?
Sergey Bolgov
Уровень 25
12 апреля 2021, 09:03
Это конспект лекции "Spring потрошитель". Спасибо за конспект.
Павел Full Stack Developer в Diasoft
5 июля 2020, 04:09
Мои проекты на Spring. Тут можно посмотреть как все это работает в комплексе. Плюс есть комментарии в коде. https://github.com/Novoselov-pavel/small-spring-log https://github.com/Novoselov-pavel/small-site-spring-log
apache888
Уровень 40
16 января 2017, 14:47
Все отлично. Обязательно продолжай. с подробным разложением всего по полкам )) (иногда по-заумному написанный текст не сразу воспринимается)
Joysi
Уровень 41
14 января 2017, 00:24
Начало норм. Можно только объем побольше.
P.S. Нечаянно заминусовал рейтинг в профиле (на планшете не там тыкнул). Был бы рад если администрация (Fry) поправит, заранее спасибо.
Marchello
Уровень 20
15 января 2017, 12:36
Спасибо за отзыв, приму во внимание насчет объема. На самом деле и в этой статье можно побольше раписать, но решил, что это может спугнуть читателя :)
shcho_isle
Уровень 11
13 января 2017, 11:22
Статья понравилась! А именно, понравился объем и простота изложения.
Продолжайте, пожалуйста.
Marchello
Уровень 20
15 января 2017, 12:34
Спасибо за отзыв, однозначно буду продолжать!
Fry
Уровень 41
11 января 2017, 00:28
Продолжать конечно же стоит, но сначала нужно испавить кучу ошибок.
Хотя бы пример xml application context, так как выше Вы привели deployment descriptor (web.xml), что вкорне не то, что имелось в виду.
Marchello
Уровень 20
11 января 2017, 00:55
Согласен, пример с xml заменил. В чём еще ошибся?
Fry
Уровень 41
11 января 2017, 11:36
Программировать нужно через интерфейсы (SOLID, L — liskov substitution principle), а не реализации. Тогда никаких проблем с @Autowired и @Transactional не будет. Поэтому пример с NPE — пример нарушения принципа подстановки Барбары Лисков, что не есть хорошо.
tanzwud
Уровень 34
11 января 2017, 14:00
Я бы не был стол категоричен насчет интерфейсов. Абстракции придутся к месту не всегда. Опять же SOLID хорошо выглядит на интервью, в реальной жизни програмиста тот-же GRASP как по мне имеет более интересен.
Насчет Spring. Везде по разному, что я вижу вокрут себя, так это Spring Boot буквально захватил рынок в Англии к примеру. Думаю стоит больше уделить внимания именно этой ветке, а детали как бины инжектятся и тп, лучше кидать линки на метириалы, при условии что они публично доступны.
Fry
Уровень 41
4 февраля 2017, 13:38
в такм случае вам не нужен спринг. Одно из его свойств это слабое связывание посредством внедрения зависимостей и ориентированности на интерфейсы,