Spring Series (2): Родительско-дочерний контейнер Spring MVC

Spring

1. Предпосылки

При использовании Spring MVC большинство учащихся определят два файла конфигурации: один — файл конфигурации Spring Spring.xml, а другой — файл конфигурации Spring MVC spring-mvc.xml.

Вот вопрос к вам, что если одноэлементный компонент с одним и тем же идентификатором определен как в файлах spring.xml, так и в файлах spring-mvc.xml? Вы можете сначала подумать об этом, а затем продолжить смотреть вниз.

Я провел эксперимент и вывод такой, что в контейнере одновременно будет два бина с одинаковым id, и они не будут мешать друг другу.

Почему это? Студенты, изучавшие Spring, обязательно зададутся вопросом: как мы все знаем, id — единственный идентификатор бина, как могут быть одновременно два бина с одинаковым id? Я дурачусь?

Простите меня за то, что я всем здесь навязываю занозу, на самом деле все правы, потому что это касается знаний родительско-дочернего контейнера Spring MVC.

Этот пункт знаний таков: в процессе использования Spring MVC будет два контейнера IOC, Spring MVC и Spring, а Spring MVC является подконтейнером Spring.

Итак, что же представляет собой этот родительско-дочерний контейнер?

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

2. Родительско-дочерний контейнер Spring MVC

2.1 конфигурация web.xml

Или сначала найдите запись программы, просмотрите файл конфигурации web.xml и найдите конфигурацию, связанную с Spring MVC.

<servlet>
        <servlet-name>spring-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
</servlet>

Конфигурация очень проста, просто настройте сервлет типа DispatcherServlet и установите параметры инициализации. Так что же такое DispatcherServlet?

2.2 Введение в класс DispatcherServlet

ПроверитьДокументация API

Из диаграммы наследования видно, что в конечном итоге он наследуется от HttpServlet, который на самом деле является обычным сервлетом. Так почему же этот сервлет может выполнять ряд сложных функций Spring MVC? Продолжайте смотреть вниз.

2.3 Рабочий процесс DispatcherServlet

Рабочий процесс DispatcherServlet выглядит следующим образом:

  • (1) Все запросы сначала отправляются в DispacherServlet.
  • (2) DispacherServlet запрашивает соответствующий контроллер в соответствии с адресом запроса, а затем возвращается к DispacherServlet.
  • (3) После того, как DispacherServlet получит контроллер, пусть контроллер обрабатывает соответствующую бизнес-логику.
  • (4) Контроллер возвращает результат DispacherServlet после обработки.
  • (5) DispacherServlet анализирует результат с помощью синтаксического анализатора представления для получения соответствующей страницы.
  • (6) DispacherServlet переходит к проанализированной странице.

В течение всего процесса DispatcherServlet берет на себя роль центрального контроллера для обработки различных запросов.

2.4 Отношение наследования контекста DispatcherServlet

Изображение выше взято с официального сайта Spring:

https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html

Как видно из рисунка, в DispatcherServlet есть Servlet WebApplicationContext, который наследуется от Root WebApplicationContext.

отПредыдущая статьяМы знаем, что WebApplicationContext на самом деле является контейнером IOC, а корневой WebApplicationContext — это контейнер Spring.

Это показывает, что контейнер IOC создается в DispatcherServlet, и этот контейнер наследует контейнер Spring, который является дочерним контейнером Spring.

И официальный документ также имеет следующее текстовое описание:

For many applications, having a single WebApplicationContext is simple and suffices. It is also possible to have a context hierarchy where one root WebApplicationContext is shared across multiple DispatcherServlet (or other Servlet) instances, each with its own child WebApplicationContext configuration. See Additional Capabilities of the ApplicationContext for more on the context hierarchy feature.

The root WebApplicationContext typically contains infrastructure beans, such as data repositories and business services that need to be shared across multiple Servlet instances.
Those beans are effectively inherited and can be overridden (that is, re-declared) in the Servlet-specific child WebApplicationContext, which typically contains beans local to the given Servlet.

Объединив рисунок и приведенный выше текст, мы можем получить следующую информацию:

  1. Приложение может содержать несколько контейнеров IOC.
  1. Подконтейнер, созданный DispatcherServlet, в основном содержит некоторые веб-компоненты, такие как контроллер, преобразователи представлений и т. д.
  1. Корень родительского контейнера WebApplicationContext в основном содержит некоторые основные компоненты, такие как некоторые компоненты, такие как dao и service, которые необходимо совместно использовать для нескольких сервлетов.
  1. Если вы не можете найти компонент в дочернем контейнере, вы можете перейти в родительский контейнер, чтобы найти компонент.

Увидев это, вы можете понять контейнер «родитель-потомок» в Spring MVC, о котором я упоминал в начале статьи, и иметь собственное мнение и ответ на этот вопрос.

Конечно статья еще не закончена.Ведь это ограничивается только пониманием официальных документов.Для дальнейшей проверки придумываем ультимативное оружие:

Читайте исходный код!

2.5 Анализ исходного кода DispatcherServlet

В этом разделе мы разделим анализ на две части: создание контейнера Spring MVC и получение bean-компонентов.

2.5.1 Создание контейнера Spring MVC

Предыдущий анализ показывает, что DispatcherServlet по существу является сервлетом.Поскольку это сервлет, студенты, понимающие жизненный цикл сервлетов, знают, что первым шагом при загрузке сервлета в веб-контейнере является выполнение функции init(), поэтому функция init функция DispatcherServlet используется как прорыв для анализа.

@Override
public final void init() throws ServletException {
   // 1.读取init parameters 等参数,其中就包括设置contextConfigLocation 
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
   //2.初始化servlet中使用的bean
   initServletBean();
}

Функция, которая считывает параметр инициализации на шаге 1, в конечном итоге вызовет setContextConfigLocation(), чтобы установить путь к файлу конфигурации. Сосредоточьтесь на initServletBean() здесь, продолжайте следовать.

Override
protected final void initServletBean() throws ServletException {
      //初始化webApplicationContext
      this.webApplicationContext = initWebApplicationContext();
}
protected WebApplicationContext initWebApplicationContext() {
    //1.获得rootWebApplicationContext
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    //2.如果还没有webApplicatioinContext,创建webApplicationContext
    if (wac == null) {
	//创建webApplicationContext
        wac = createWebApplicationContext(rootContext);
    }
   return wac;
}

Вы можете видеть, что приведенная выше инициализация webApplicationContext разделена на 2 этапа.

  • (1) Получите родительский контейнер rootWebApplicationContext.
  • (2) Создайте подконтейнеры.

Давайте сначала посмотрим, как получается rootWebApplicationContext.

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
   return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
   Object attr = sc.getAttribute(attrName);
   return (WebApplicationContext) attr;
}

Из приведенного выше кода я не вижу, что webApplicationContext с именем "WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE" получен из ServletContext.

внимательно прочитайтеПредыдущая статьяУчащиеся должны помнить, что это свойство устанавливается на третьем шаге в функции initWebApplicationContext() контейнера инициализации Spring, а полученное значение является контейнером IOC Spring.

Продолжайте видеть, как создать webApplicationContext.

protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
   return createWebApplicationContext((ApplicationContext) parent);
}
createWebApplicationContext(ApplicationContext parent) {
  //1.获取WebApplicationContext实现类,此处其实就是XmlWebApplicationContext
  Class<?> contextClass = getContextClass();
  //生成XmlWebApplicationContext实例
  ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
  //2.设置rootWebApplicationContext为父容器 
   wac.setParent(parent);
  //3.设置配置文件
   wac.setConfigLocation(getContextConfigLocation());
  //4.配置webApplicationContext.
   configureAndRefreshWebApplicationContext(wac);
   return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
   //开始处理bean
   wac.refresh();
}

Посмотрите, есть ли у студентов ощущение, что они знали друг друга. Да, эта логика иПредыдущая статьяЛогика создания IOC Spring аналогична.

Единственное отличие состоит в том, что на шаге 2 контейнер Spring устанавливается как собственный родительский контейнер. Что касается регистрации, парсинга, инстанцирования и других процессов bean-компонентов в новом контейнере, так же, как и в контейнере IOC Spring, то все они переданы для обработки классу XmlWebApplicationContext, студенты, которые еще не освоили их, могут посмотретьПредыдущая статья.

2.5.2 Получение Spring MVC Bean-компонентов

На самом деле, мы уже рассказывали о приобретении Spring MVC bean-компонентов в предыдущей статье, на этот раз я расскажу об этом и углублю свою память.

protected <T> T doGetBean(
    // 获取父BeanFactory
    BeanFactory parentBeanFactory = getParentBeanFactory();
    //如果父容器不为空,且本容器没有注册此bean就去父容器中获取bean
    if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
         // 如果父容器有该bean,则调用父beanFactory的方法获得该bean
         return (T) parentBeanFactory.getBean(nameToLookup,args);
    }
    //如果子容器注册了bean,执行一系列实例化bean操作后返回bean.
    //此处省略实例化过程
    .....
    return (T) bean;
}

Приведенный выше код может соответствовать объяснению в официальном документе «Если компонент не может быть найден в дочернем контейнере, перейдите в родительский контейнер, чтобы найти его».

3. Резюме

Прочитав предыдущее введение, я полагаю, что у всех есть некоторое представление о концепции родительско-дочернего контейнера Spring MVC.Теперь мы разберем проблемы в начале статьи.

Что, если spring.xml и spring-mvc.xml определяют bean-компоненты с одним и тем же идентификатором? Предположим, что идентификатор = тест.

1. Сначала инициализируется Spring, и в контейнере Spring IOC создается экземпляр тестового компонента с идентификатором test.

2. Spring MVC начинает инициализацию и создает экземпляр с идентификатором тестового компонента.

На данный момент в каждом из двух контейнеров есть bean-компонент с одинаковым идентификатором. Будет ли это запутанно в использовании?

Ответ - нет.

Когда вы используете этот bean-компонент в бизнес-логике Spring MVC, Spring MVC напрямую возвращает свой собственный bean-компонент-контейнер.

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

Хотя написанное выше проблем не вызовет. Однако при фактическом использовании всем рекомендуется записывать определения bean-компонентов в файл spring.xml.

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

4. Эпилог

Сейчас все в основном не используют форму определения bean-компонентов в xml-файлах, а используют аннотации для определения bean-компонентов, а затем определяют пакеты сканирования в xml-файлах. следующим образом:

<context:component-scan base-package="xx.xx.xx"/>

Что делать, если в spring.xml и spring-mvc.xml настроены дубликаты пакетов?

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

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

Некоторые учащиеся могут подумать, что простая установка пакета сканирования в spring.xml может предотвратить возникновение подобных проблем.

Можете попробовать, что будет. Если нет, то почему?

Чтобы узнать, следите за обновлениями для следующей разбивки!

Хотите узнать больше, обратите внимание на публичный номер: семь приготовленных пицц

在这里插入图片描述