Внедрение зависимостей в Spring. Погружение | OTUS

Курсы

Программирование
iOS Developer. Basic
-23%
Python Developer. Professional
-13%
Golang Developer. Professional
-17%
Python Developer. Basic
-16%
iOS Developer. Professional
-13%
C# ASP.NET Core разработчик
-18%
Unity Game Developer. Professional
-11%
React.js Developer
-12%
Android Developer. Professional
-7%
Software Architect
-12%
C++ Developer. Professional
-8%
Разработчик C#
-8%
Backend-разработчик на PHP
-8%
Архитектура и шаблоны проектирования
-12%
Программист С Разработчик на Spring Framework MS SQL Server Developer AWS для разработчиков Cloud Solution Architecture Разработчик голосовых ассистентов и чат-ботов Vue.js разработчик VOIP инженер Нереляционные базы данных Супер - интенсив по паттернам проектирования Супер-практикум по использованию и настройке GIT IoT-разработчик Advanced Fullstack JavaScript developer Супер-интенсив Azure
Инфраструктура
Мониторинг и логирование: Zabbix, Prometheus, ELK
-17%
DevOps практики и инструменты
-18%
Архитектор сетей
-21%
Инфраструктурная платформа на основе Kubernetes
-22%
Супер-интенсив «IaC Ansible»
-16%
Супер-интенсив по управлению миграциями (DBVC)
-16%
Administrator Linux. Professional
-5%
Administrator Linux.Basic
-10%
Супер-интенсив «ELK»
-10%
Базы данных Сетевой инженер AWS для разработчиков Cloud Solution Architecture Разработчик голосовых ассистентов и чат-ботов Внедрение и работа в DevSecOps Супер-практикум по работе с протоколом BGP Супер - интенсив по паттернам проектирования Супер - интенсив по Kubernetes Супер-интенсив «СУБД в высоконагруженных системах»
Специализации Курсы в разработке Подготовительные курсы
+7 499 938-92-02

Внедрение зависимостей в Spring. Погружение

Spring-фреймворк содержит огромное количество разных технологий, но в первую очередь он знаменит своим механизмом внедрения зависимостей (Dependency Injection, DI). Одним из самых популярных способов является внедрение с помощью аннотации @Autowired. Она может располагаться над конструктором бина (или класса конфигурации), его полем, сеттером или другим методом, принимающим в качестве аргумента другой бин. В перечисленных случаях, Spring во время создания контекста подставит нужный бин в отмеченные аннотацией места.

Большинство разработчиков знают, что за обработку аннотации @Autowired отвечает некий BeanPostProcessor, который и делает всю магию внедрения. И это действительно так. За исключением одного момента. BeanPostProcessor-ы взаимодействуют с уже созданными экземплярами бинов. Т. е. в случае, когда @Autowired расположена над конструктором, BeanPostProcessor не сработает т. к. бин еще не существует.

Далее мы попробуем разобраться, как происходит внедрение зависимостей. Для этого создадим простое Spring boot-приложение из 4-х классов.

// Бин, который будем внедрять через @Autowired над конструктором
@Component
public class DependencyForConstructorInjection {}

// Бин, который будем внедрять через @Autowired над полем
@Component
public class DependencyForFieldInjection {}

// Бин, в который будем внедрять зависимости
@Component
public class DependencyOwner {

    @Autowired
    private DependencyForFieldInjection dependencyForFieldInjection;

    private final DependencyForConstructorInjection dependencyForConstructorInjection;

    @Autowired
    public DependencyOwner(DependencyForConstructorInjection dep) {
        this.dependencyForConstructorInjection = dep;
    }

    @Override
    public String toString() {
        return "DependencyOwner{\n" +
                "\tdependencyForFieldInjection=" + dependencyForFieldInjection +
                ", \n\tdependencyForConstructorInjection=" + dependencyForConstructorInjection +
                "\n}";
    }
}

// Запускающий класс, точка входа в приложение
@SpringBootApplication
public class AutowiredDemoApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(AutowiredDemoApplication.class, args);
        DependencyOwner dependencyOwner = ctx.getBean(DependencyOwner.class);
        System.out.println(dependencyOwner);
    }
}

Наше приложение содержит три бина, два из которых являются зависимостями третьего. Причем DependencyForFieldInjection внедряется через @Autowired, а DependencyForConstructorInjection внедряется через конструктор. В главном классе мы достаем бин, куда внедрены зависимости из контекста и печатаем его в консоль. В результате работы программы должно получиться примерно следующее:

DependencyOwner{
    dependencyForFieldInjection=ru.otus.autowireddemo.components.DependencyForFieldInjection@6b5894c8, 
    dependencyForConstructorInjection=ru.otus.autowireddemo.components.DependencyForConstructorInjection@1433046b
}

Т. е. все бины успешно созданы и связаны друг с другом, как и ожидалось.

Можно приступать к исследованию

Для начала заглянем внутрь аннотации @Autowired, скачаем и прочитаем Javadoc. Там будет примерно следующее:

Note that actual injection is performed through a BeanPostProcessor which in turn means that you cannot use @Autowired to inject references into BeanPostProcessor or BeanFactoryPostProcessor types. Please consult the javadoc for the AutowiredAnnotationBeanPostProcessor class (which, by default, checks for the presence of this annotation)

Внедрение действительно происходит с помощью BeanPostProcessor-а, и это AutowiredAnnotationBeanPostProcessor. Перейдем к его описанию:

BeanPostProcessor implementation that autowires annotated fields, setter methods, and arbitrary config methods. Such members to be injected are detected through annotations: by default, Spring's @Autowired @Value annotations

Вроде то, что нужно. Обрабатывает аннотации @Autowired и @Value (по умолчанию еще может @Inject, если она есть в проекте), но, судя по всему, как и ожидалось, работает только для внедрения, через поля, сеттеры и аргументы методов конфигураций. Постпроцессор реализует множество методов разных интерфейсов, но уже даже по названию можно догадаться, что на роль метода, который внедрит нам значения в поле, подходит postProcessProperties. Вот его код:

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        metadata.inject(bean, beanName, pvs);
    }
    catch (BeanCreationException ex) {
        throw ex;
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    return pvs;
}

В том, что мы угадали, несложно убедиться экспериментально. Поставим точку останова на строку metadata.inject(bean, beanName, pvs);. Зададим ей условие beanName.equals("dependencyOwner"), чтобы сработала только для интересующего нас бина. Запустим программу в режиме отладки. В итоге видим, что если до выполнения заданной строки переменная bean в поле dependencyForFieldInjection имела значение null, то после выполнения значение уже появилось. То же самое справедливо, если внедрение будет производиться через сеттер).

ОК. С этим все ясно, а что с конструкторами?

Среди содержимого AutowiredAnnotationBeanPostProcessor можно обнаружить любопытный метод determineCandidateConstructors, который очень похож на то, что могло бы определять конструктор(ы), необходимые для создания бина. Сам метод публичный, пришел из интерфейса SmartInstantiationAwareBeanPostProcessor и внутри класса не вызывается. Просто отдает наружу массив конструкторов:

public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)

Это значит, что его использует кто-то извне. Поставим точку останова с тем же условием, что и ранее, на последнюю строку метода, запустим приложение под отладчиком, дождемся, когда выполнение программы остановится на нашей точке, и сделаем шаг вперед. Мы оказались в методе determineConstructorsFromBeanPostProcessors, класса AbstractAutowireCapableBeanFactory:

protected Constructor<?>[] determineConstructorsFromBeanPostProcessors(@Nullable Class<?> beanClass, String beanName)
    throws BeansException {

    if (beanClass != null && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                Constructor<?>[] ctors = ibp.determineCandidateConstructors(beanClass, beanName);
                if (ctors != null) {
                    return ctors;
                }
            }
        }
    }
    return null;
}

По сути, BeanFactory перед созданием бина прогоняет все имеющиеся SmartInstantiationAwareBeanPostProcessor и вызывает у них determineCandidateConstructors, пока один из них не вернет массив конструкторов. Сам полученный массив используется в методе createBeanInstance того же класса для создания бина:

Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
        mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
    return autowireConstructor(beanName, mbd, ctors, args);
}

Внутренности autowireConstructor(beanName, mbd, ctors, args) и есть конечная точка наших изысканий:

protected BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] ctors, 
        @Nullable Object[] explicitArgs) {

    return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
}

Все вместе

Если дальше опускаться по стеку вызовов, можно составить полную картину происходящего.

  1. При создании контекста вызывается метод refresh класса AbstractApplicationContext.
  2. Внутри происходит вызов this.finishBeanFactoryInitialization(beanFactory);.
  3. Который, в свою очередь, вызывает beanFactory.preInstantiateSingletons();. (beanFactory = DefaultListableBeanFactory)
  4. DefaultListableBeanFactory вызывает цепочку методов предка (AbstractAutowireCapableBeanFactory):
  5. getBean ->
  6. doGetBean ->
  7. getSingleton ->
  8. createBean ->
  9. doCreateBean ->
  10. createBeanInstance.
  11. Что делает createBeanInstance, и что будет после него, мы уже знаем).

Итоги

Мы узнали, как происходит внедрение зависимостей для вариантов внедрения через конструктор и через поле/сеттер. Нужно отметить, что все бины в приложении были синглтонами. Для остальных скоупов местами процесс будет отличаться. Как минимум, будут работать другие участки кода). Если вы хотите продолжить изучение жизненного цикла бинов самостоятельно, рекомендуем обратить внимание на метод doCreateBean в классе AbstractAutowireCapableBeanFactory. Успехов!)

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
0 комментариев
Для комментирования необходимо авторизоваться