Статья "Package You Know Series" четко объясняет принцип и процесс реализации Spring IoC.

Java

Я Кайт, официальный аккаунт "Воздушный змей в древности", технический официальный аккаунт не только с технологиями, но и слэш-разработчик 6 лет, много лет в кружке программирования, в основном работающий на Java, а также на Python и реагировать. Серия статей Spring Cloud завершена, и вы можете перейти кмой гитхабПолную серию смотрите на . Вы также можете ответить на «pdf» в официальном аккаунте, чтобы получить мою тщательно созданную версию полного руководства в формате pdf.

Я отправил прогревочную статью о Spring IoC до того, как "Чтобы понять Spring IoC, вы должны сначала узнать, как расширить пользовательские bean-компоненты Spring.", если вам интересно, вы можете посмотреть, как расширить пользовательский компонент в Spring, например, как реализовать идентификатор и имя атрибута в теге, как мы можем расширить тег с аналогичными функциями, но атрибут не А как насчет разных функций?

Я считаю, что после понимания пользовательского компонента расширения процесс понимания Spring IoC станет более ясным.

Итак, текст начинается.

Spring IoC, полное название Inversion of Control — инверсия управления, есть еще одно, называемое DI (Dependency Injection) — внедрение зависимостей. Можно также сказать, что инверсия управления — это конечная цель, а внедрение зависимостей — это специфический метод достижения этой цели.

Что такое инверсия управления

Почему это называется инверсией управления?

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

Например, предположим, что существует класс Logger, который выводит журналы. Определяется следующим образом:

public class Logger {

    public void log(String text){
        System.out.println("log:" + text);
    }
}

Итак, теперь я хочу вызвать этот метод журнала, что мне делать?

Logger logger = new Logger();
logger.log("日志内容");

Верно, это традиционный шаблон вызова. Когда новый экземпляр объекта контролируется вызывающей стороной или самими нашими разработчиками, когда он используется, он будет новым, когда он используется.

Но когда мы использовали Spring IoC, все изменилось. Проще говоря, в результате разработчикам не нужно заботиться о работе новых объектов. Или этот класс Logger, как мы будем его использовать после введения Spring IoC?

public class UserController {

    @Autowired
    private Logger logger;
    
    public void log(){
        logger.log("please write a log");
    }
    
}

Разработчики не создают объекты, но обеспечить нормальное использование объектов невозможно без действия новых, что не имеет смысла. В данном случае, кто должен был сделать эту операцию за нас, то есть фреймворк Spring, если быть точным, Spring IoC Container сделал это за нас. Таким образом, управление переходит от разработчика к стороннему фреймворку, что называется Inversion of Control.

Что такое внедрение зависимостей

Полное дополнение субъекта, предиката и объекта внедрения зависимостей заключается во внедрении объекта экземпляра класса, от которого зависит вызывающая сторона, в класс вызывающей стороны. Взяв предыдущий пример, класс UserController является вызывающим. Он хочет вызвать метод журнала из созданного объекта Logger. Регистратор, как созданный (то есть новый) объект, является зависимым объектом UserController. использовать в нем ключевое слово new, потому что Spring IoC Container делает это за нас.Эта прозрачная для разработчиков операция называется инъекции.

Существует три способа внедрения: внедрение конструктора, внедрение сеттера и внедрение аннотаций.Первые два метода сейчас в основном редко используются, а методы аннотаций больше используются в разработке, особенно Spring Boot становится все более и более распространенным сегодня. Когда мы разрабатываем с использованием среды Spring, мы обычно используем@Autowired, конечно, вы можете иногда использовать@Resource

@Autowired
private IUserService userService;

@Autowired
private Logger logger;

Spring IoC Container

Как упоминалось ранее, действие инъекции на самом деле выполняется Spring IoC Container, так что же такое Spring IoC Container?

На этот раз мы обсудим часть Core Container на рисунке выше, включая Beans, Core, Context и SpEL.

Контейнер отвечает за создание экземпляра, настройку и сборку компонента и его внедрение в класс вызывающего объекта зависимостей. Контейнер — это менеджер, который управляет всем жизненным циклом проектов Bean в Spring, включая создание Bean, регистрацию, хранение, получение, уничтожение и так далее.

Начнем с базового примера. в предыдущем примере@BeanОн реализован в виде аннотаций, о которых речь пойдет позже. Поскольку это базовая модель, xml нельзя экранировать.Хотя сейчас используется Spring Boot, процесс внедрения зависимостей можно более четко наблюдать с помощью исходного метода xml.Вы должны знать, что, когда Spring Boot не было, xml мог сказать быть ссылкой на проект Spring, большая часть информации о конфигурации поступает из файла конфигурации xml.

Сначала добавьте файл объявления bean-компонента в формате xml, предполагая, что имя — application.xml. Если вы раньше использовали Spring MVC, это определение в большинстве случаев будет вам знакомо.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bean="http://www.springframework.org/schema/c"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="logger" class="org.kite.spring.bean.Logger" />
</beans>

пройти через<bean>элемент для объявления объекта bean-компонента и указать идентификатор и класс.Это стандартный способ объявления объекта bean-компонента в xml.Если вы использовали Spring Boot с тех пор, как вступили в контакт с Java, все равно необходимо понимать этот способ.

Затем протестируйте его через консольную программу и вызовите метод log класса Logger.

public class IocTest {

    public static void main(String[] args){
        ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
        Logger logger = (Logger) ac.getBean("logger");
        logger.log("hello log");
    }
}

ApplicationContext— класс интерфейса, реализующий контейнер, гдеClassPathXmlApplicationContextЭто конкретная реализация контейнера, похожая наFileSystemXmlApplicationContext, оба из которых являются контейнерами для разбора конфигурации в формате xml. Давайте взглянемClassPathXmlApplicationContextсхема наследования.

Означает ли это, что он выглядит очень сложным, и он прошел несколько слоев только до слоя ApplicationContext.

Это наш активный вызов в консолиClassPathXmlApplicationContext, вообще не нужно заботиться в нашем проектеApplicationContextНапример, для используемого нами проекта Spring Boot нам нужны только следующие строки.

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Однако эти строки не означают, что Spring Boot не делает внедрение зависимостей — аналогично, это будет реализовано внутри.ApplicationContext, конкретная реализация называетсяAnnotationConfigServletWebServerApplicationContext, Давайте взглянем на диаграмму отношений наследования этого класса реализации. Она еще сложнее. Сначала не заботьтесь о деталях, просто поймите ее.

Анализ процесса закачки

Продолжайте брать приведенный выше базовый код, и наш анализ начнется с него.

public class IocTest {
    public static void main(String[] args){
        ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
        Logger logger = (Logger) ac.getBean("logger");
        logger.log("hello log");
    }
}

В процессе внедрения есть много статей, в которых анализируется исходный код, поэтому я не буду здесь заострять внимание на исходном коде.

Короче говоря, если мы только проанализируемClassPathXmlApplicationContextВ этом простом контейнере исходный код всего процесса внедрения на самом деле очень легко читается, я должен сказать, что исходный код Spring очень чистый. мы начинаем сClassPathXmlApplicationContextЗайди в конструктор и найди пошаговоrefresh()метод, а затем прочитайте, чтобы понять самый основной процесс Spring IoC. Следующий код является основным методом метода обновления:

@Override
 public void refresh() throws BeansException, IllegalStateException {
  synchronized (this.startupShutdownMonitor) {
   // Prepare this context for refreshing.
   prepareRefresh();
   // Tell the subclass to refresh the internal bean factory.
   ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
   // Prepare the bean factory for use in this context.
   prepareBeanFactory(beanFactory);
   try {
    // Allows post-processing of the bean factory in context subclasses.
    postProcessBeanFactory(beanFactory);
    // Invoke factory processors registered as beans in the context.
    invokeBeanFactoryPostProcessors(beanFactory);
    // Register bean processors that intercept bean creation.
    registerBeanPostProcessors(beanFactory);
    // Initialize message source for this context.
    initMessageSource();
    // Initialize event multicaster for this context.
    initApplicationEventMulticaster();
    // Initialize other special beans in specific context subclasses.
    onRefresh();
    // Check for listener beans and register them.
    registerListeners();
    // Instantiate all remaining (non-lazy-init) singletons.
    finishBeanFactoryInitialization(beanFactory);
    // Last step: publish corresponding event.
    finishRefresh();
   }

   catch (BeansException ex) {
    }
    destroyBeans();
    cancelRefresh(ex);
    throw ex;
   }

   finally {
    resetCommonCaches();
   }
  }
 }

Комментарии написаны очень четко, а процесс инъекции ядра на самом деле находится в этой строке:

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

Я нарисовал схему логических вызовов этой основной части.На этой диаграмме перечислены только основные методы, но она смогла четко представить процесс. (Чтобы получить векторный формат, вы можете ответить на «векторную диаграмму» в публичном аккаунте, чтобы получить его.)

Не по теме: О чтении исходного кодаБольшинство людей не в состоянии читать исходный код, в том числе и я, не говоря уже о таком огромном фреймворке с открытым исходным кодом, даже если это новый проект, который я взял на себя, я не могу много читать. Самое главное при чтении исходного кода - это детали. Детали, упомянутые здесь, не для того, чтобы вы копались в деталях. Наоборот, вы не должны слишком углубляться в детали. Никто не может полностью понять весь исходный код. структуры и найти ключевую логику.Отношения в порядке, иначе есть большая вероятность, что вы получите головную боль, раздражение от детали, а затем бросите читать.

Некоторые студенты узнают, когда посмотрят на картинку или исходный код, почему задействовано так много классов, эта цепочка вызовов действительно достаточно длинная. Не беда, можно трактовать их как единое целое (понимать как вызов, происходящий в классе), как видно из предыдущей схемы классов, отношения наследования очень сложные, различное наследование, реализация, поэтому в end call Цепочки усложняются.

Краткое содержание

Кратко подытоживая, суть внедрения состоит в том, чтобы на самом деле проанализировать содержимое файла xml, найти элемент, затем пройти ряд операций обработки и, наконец, сохранить эти обработанные объекты в общедоступном пространстве для получения и использования вызывающей стороной.

Что касается bean-компонентов, использующих аннотации, такие как использование@Bean,@Service,@ComponentДля аннотаций отличается только шаг синтаксического анализа, а остальные операции в основном такие же.

Итак, нам просто нужно прояснить здесь несколько основных вопросов.

Связь между BeanFactory и ApplicationContext

Вышеупомянутая строка основного кода, окончательный возврат - этоConfigurableListableBeanFactoryобъект, а следующие методы используют возвращенный beanFactory в качестве параметра.

BeanFactoryэто интерфейс,ApplicationContextтакже является интерфейсом, и,BeanFactoryдаApplicationContextРодительский интерфейс, там сказаноBeanFactoryЭто контейнер Spring IoC. На самом деле, только в первые дниBeanFactory, на тот момент это действительно был контейнер Spring IoC, а позже добавили больше функций из-за обновлений версииApplicationContext. Самая большая разница между ними в том, чтоApplicationContextВсе bean-компоненты создаются при инициализации, иBeanFactoryИспользуемые bean-компоненты создаются при их использовании, поэтому более ранняя версия Spring по умолчанию использует ленивую загрузку, а новая версия по умолчанию создает экземпляры всех bean-компонентов при инициализации, поэтому процесс запуска Spring не так быстр, это одна из причин .

Где хранится BeanDefinition

В приведенном выше резюме упоминается, что он хранится в общественном месте. Где это общественное место? На самом деле это Map, и это ConcurrentHashMap, чтобы обеспечить безопасность параллелизма. Объявлен следующим образом, вDefaultListableBeanFactoryсередина.

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256)

beanName — это ключ, который является регистратором в примере, значение — это тип BeanDefinition, а BeanDefinition используется для описания определения bean-компонента, в нем все атрибуты элементов, которые мы определили в xml-файле, а также некоторые другие необходимые атрибуты.

Добавьте элемент в beanDefinitionMap, называемый регистрацией компонента, можно использовать только зарегистрированный компонент.

Где хранятся экземпляры bean-компонентов

Кроме того, существует карта с именем singletonObjects, которая объявлена ​​следующим образом:

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

В процессе refresh() здесь также будет сохранен Бин Этот сохраненный процесс происходит в методе finishBeanFactoryInitialization(beanFactory), и его роль заключается в помещении Бин без ленивой инициализации в singletonObjects.

В дополнение к определенным нами bean-компонентам существует также несколько системных bean-компонентов.

Например, мы вызываем это так в коде:

ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
StandardEnvironment env = (StandardEnvironment) ac.getBean("environment");

Используйте зарегистрированные бобы

В этом примере мы передаемApplicationContextПоказан метод getBean() для получения зарегистрированного компонента. Как упоминалось ранее, в дополнение к размещению Bean, который мы определили в beanDefinitionMap, он также хранит копию в singletonObjects, который является кешем Когда мы вызываем метод getBean, мы сначала обращаемся к нему, чтобы получить его. Если он не найден (для тех bean-компонентов, которые активно устанавливают lazy-init), перейдите в beanDefinitionMap, чтобы получить его и добавить в singletonObjects.

Блок-схема вызовов для получения Bean выглядит следующим образом (Официальный аккаунт отвечает на «Векторную иллюстрацию», чтобы получить векторную графику высокого разрешения.)

Ниже приведен пример bean-компонента, настроенного в режиме отложенной инициализации.

<bean id="lazyBean" lazy-init="true" class="org.kite.spring.bean.lazyBean" />

Если не установлено, по умолчанию регистрируется при инициализации.

способ аннотации

Сейчас мало проектов используют метод настройки xml, в основном все они Spring Boot, даже если они не используются, то регистрируются и используются в Spring MVC с помощью аннотаций. По сути, весь процесс аналогичен, за исключением того, что при регистрации и приобретении задействовано больше аннотаций. В СерпингеBeanFactoryиApplicationContextВсе они интерфейсы.Кроме того, существует множество абстрактных классов, которые позволяют нам гибко настраивать собственные процессы регистрации и вызова.Можно считать, что метод аннотации является одним из них. Просто найдите время, чтобы разобрать соответствующий аннотационный знак.

Однако процесс регистрации и вызова Spring Boot не такой гладкий, как метод xml, который все определяется характеристиками аннотаций. Аннотации просты и удобны в использовании и имеют много преимуществ. Но в то же время аннотации будут разделять традиционный процесс.Традиционный процесс активно вызывается шаг за шагом.Просто следуйте коду и смотрите вниз, и способ аннотации приведет к отключению процесса, поэтому ему нужны дополнительные методы читать. .

Процесс IoC в Spring Boot, о нем мы поговорим в следующий раз.

Чтобы получить карту дорожек для плавания в высоком разрешении из этой статьи, ответьте на «Векторную иллюстрацию» в общедоступном аккаунте.

Приятно ставить лайки, я всегда занимаюсь проституцией ни за что, и мое тело этого не выдерживает!

Справочная документация:

https://docs.spring.io/spring/docs

Я воздушный змей, общественный счет»древний воздушный змей», слэш-разработчик, который много лет занимается программированием, в основном на Java, а также играет с Python и React. Вы можете добавить меня в друзья в официальном аккаунте, вступить в группу для общения и обучения, так же в группе много одноклассников с крупных заводов.

Для технического обмена вы также можете добавить группу или добавить меня напрямую в WeChat.