JavaRush /Java блог /Random UA /Spring Framework. Вступ
Marchello
20 рівень
Санкт-Петербург

Spring Framework. Вступ

Стаття з групи Random UA
Вітання! Поки адміністрація 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="com.codegym.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. Готово.
Власне, на цьому все, пишіть, сподобалася вам стаття і чи варто продовжувати писати подібні туторіали.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ