Статьи

Типичные ошибки при использовании Spring MVC

spring_framework Когда я начал свою карьеру около 10 лет назад, Struts MVC был нормой на рынке. Однако с годами я наблюдал, как Spring MVC постепенно набирает популярность. Это меня не удивляет, учитывая полную интеграцию Spring MVC с контейнером Spring и гибкость и расширяемость, которые он предлагает.

Из моего путешествия с Spring до сих пор я обычно видел людей, делающих некоторые типичные ошибки при настройке Spring Framework. Это случалось чаще по сравнению с тем временем, когда люди все еще использовали среду Struts. Я предполагаю, что это компромисс между гибкостью и удобством использования. Кроме того, документация Spring полна примеров, но не содержит объяснений. Чтобы помочь восполнить этот пробел, в этой статье мы попытаемся разработать и объяснить 3 распространенных проблемы, с которыми я часто сталкиваюсь.

Объявление бинов в файле определения контекста сервлета

Итак, каждый из нас знает, что Spring использует ContextLoaderListener для загрузки контекста приложения Spring. Тем не менее, при объявлении
DispatcherServlet , нам нужно создать файл определения контекста сервлета с именем «$ {servlet.name} -context.xml». Вы никогда не задумывались, почему?

Иерархия контекста приложения

Не все разработчики знают, что контекст приложения Spring имеет иерархию. Давайте посмотрим на этот метод:

org.springframework.context.ApplicationContext.getParent ()

Это говорит нам о том, что Spring Application Context имеет родителя. Итак, для чего этот родитель?

Если вы загрузите исходный код и выполните быстрый поиск ссылок, вы обнаружите, что Spring Application Context рассматривает parent как его расширение. Если вы не против прочитать код, позвольте мне показать вам один пример использования в методе BeanFactoryUtils.beansOfTypeIncключAncestors () :

01
02
03
04
05
06
07
08
09
10
if (lbf instanceof HierarchicalBeanFactory) {
    HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf;
    if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
 Map parentResult =
              beansOfTypeIncludingAncestors((ListableBeanFactory) hbf.getParentBeanFactory(), type);
 ...
    }
}
return result;
}

Если вы выполните весь метод, вы обнаружите, что Spring Application Context Context сканирует, чтобы найти bean-компоненты во внутреннем контексте, прежде чем искать родительский контекст. С этой стратегией, эффективно, Spring Application Context будет сначала выполнять поиск в обратном направлении для поиска bean-компонентов.

ContextLoaderListener

Это хорошо известный класс, который должен знать каждый разработчик. Это помогает загрузить контекст приложения Spring из предопределенного файла определения контекста. Поскольку он реализует ServletContextListener , контекст приложения Spring будет загружен сразу после загрузки веб-приложения. Это дает неоспоримые преимущества при загрузке контейнера Spring, который содержит компоненты с аннотацией @PostContruct или пакетными заданиями.

Напротив, любое определение компонента в файле определения контекста сервлета не будет создано до тех пор, пока сервлет не будет инициализирован. Когда сервлет инициализируется? Это неопределенно. В худшем случае вам может потребоваться подождать, пока пользователи не выполнят первый переход по URL-адресу отображения сервлета, чтобы загрузить контекст Spring.

С вышеупомянутой информацией, где вы должны объявить все свои драгоценные бобы? Я чувствую, что лучшее место для этого — файл определения контекста, загруженный ContextLoaderListener, и больше нигде. Хитрость здесь заключается в хранении ApplicationContext в качестве атрибута сервлета под ключом org.springframework.web.context.WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
Позднее DispatcherServlet загрузит этот контекст из ServletContext и назначит его в качестве контекста родительского приложения.

1
2
3
4
5
protected WebApplicationContext initWebApplicationContext() {
   WebApplicationContext rootContext =
      WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   ...
}

Из-за этого поведения настоятельно рекомендуется создать пустой файл определения контекста приложения сервлета и определить компоненты в родительском контексте. Это поможет избежать дублирования создания компонента при загрузке веб-приложения и гарантирует, что пакетные задания выполняются немедленно.

Теоретически, определение компонента в файле определения контекста приложения сервлета делает компонент уникальным и видимым только для этого сервлета. Однако за 8 лет использования Spring я практически не нашел применения этой функции, кроме определения конечной точки веб-службы.

Объявите Log4jConfigListener после ContextLoaderListener

Это небольшая ошибка, но она поймает вас, когда вы не обращаете на нее внимания. Log4jConfigListener — мое предпочтительное решение по сравнению с -Dlog4j.configuration, так как мы можем контролировать загрузку log4j без изменения процесса начальной загрузки сервера.

Очевидно, это должен быть первый слушатель, который будет объявлен в вашем web.xml. В противном случае все ваши усилия по объявлению правильной конфигурации журналирования будут потрачены впустую.

Дублированные Бобы из-за неправильного управления исследованием бобов

В начале весны разработчики тратили больше времени на набор XML-файлов, чем на Java-классы. Для каждого нового компонента нам нужно самим объявить и связать зависимости, что является чистым, аккуратным, но очень болезненным. Не удивительно, что более поздние версии Spring Framework эволюционировали в сторону большего удобства использования. В настоящее время разработчикам может потребоваться только объявить диспетчер транзакций, источник данных, источник свойств, конечную точку веб-службы, а остальное оставить для сканирования компонентов и автоматического подключения.

Мне нравятся эти новые функции, но эта великая сила должна прийти с большой ответственностью; в противном случае все будет очень быстро. Сканирование компонентов и объявление компонентов в файлах XML полностью независимы. Следовательно, вполне возможно иметь идентичные bean-компоненты одного и того же класса в контейнере bean-компонента, если bean-компоненты аннотированы для сканирования компонентов и также объявлены вручную. К счастью, такая ошибка должна случиться только с новичками.

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

spring_component

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

Вот моя предложенная стратегия:

  • Убедитесь, что каждый компонент должен начинаться с выделенного имени пакета. Это делает нашу жизнь проще, когда нам нужно выполнить компонентное сканирование.
  • Не предписывайте команде, которая разрабатывает компонент, подходить к объявлению bean-компонента в самом компоненте (аннотация вместо объявления xml). Разработчик обязан упаковать компоненты в конечный продукт, чтобы избежать дублирования декларации bean-компонента.
  • Если внутри компонента есть файл определения контекста, передайте ему пакет, а не в корень classpath. Еще лучше дать ему конкретное имя. Например, src / main / resources / spring-core / spring-core-context.xml намного лучше, чем src / main / resource / application-context.xml. Представьте, что мы можем сделать, если упакуем несколько компонентов, содержащих один и тот же файл application-context.xml, в один и тот же пакет!
  • Не предоставляйте аннотации для сканирования компонентов ( @Component , @Service или @Repository ), если вы уже объявили компонент в одном файле контекста.
  • Разделите bean-компонент среды, такой как источник данных , свойство-источник, на отдельный файл и используйте его повторно.
  • Не выполняйте компонентное сканирование на общем пакете. Например, вместо сканирования пакета org.springframework легче управлять, если мы сканируем несколько подпакетов , таких как org.springframework.core , org.springframework.context , org.springframework.ui ,…

Вывод

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