При разработке веб приложений на Spring всегда описывается как минимум два контекста. Один (root-context.xml) — это общий, расшаренный контекст, содержащий бины, которые будут использоваться всеми сервлетами приложения. В web.xml загрузка этого контекста описывается обычно так:
1 2 3 4 5 6 |
<!-- The definition of the Root Spring Container shared by all Servlets and Filters --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml</param-value> </context-param> |
Второй — это контекст самого сервлета. Сервлетов в одном приложении может быть несколько. В web.xml можно задавать их порядок загрузки, если нужно. Загрузка контекста сервлета указывается в web.xml так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/site/*</url-pattern> </servlet-mapping> |
В примере, приведённом выше xml описание контекста сервлета находится в /WEB-INF/spring/appServlet/servlet-context.xml, а load-on-startup — описывает что он будет загружаться первым. Сервлет будет отвечать на запросы по пути <наш_сайт>/<имя_приложения/war_файла>/site/**.
При желании мы можем описать подобным же образом второй сервлет:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<servlet> <servlet-name>apiServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/apiServlet/api-servlet-context.xml</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>apiServlet</servlet-name> <url-pattern>/api/*</url-pattern> </servlet-mapping> |
Каждый сервлет может содержать разное описание инициализаций Spring MVC, локалей, тем, диспетчеров представлений и прочего.
На вопрос о том, как правильно разделить контекст приложения и общий контекст, далеко не все могут правильно ответить. Я недавно правил проект, где это разделение было весьма условным и бины были сильно перепутаны. В результате этого плохого разделения было множество костылей и неточностей.
Так вот, правильное разделение должно быть такое.
Общий или корневой контекст (root-context.xml) должен содержать:
- Бин dataSource.
- Бины слоя доступа к данным и сервисов.
- Инициализация транзакции. Менеджер транзакций и tx:annotation-driven должен находится здесь. Контейнер менеджеров сущностей EntityManagerFactory также должен описываться здесь.
- Spring Security. Всё описание должно располагаться здесь.
Контекст сервлета (servlet-context.xml) должен содержать:
- Инициализация Spring MVC (mvc:annotation-driven).
- Все бины контроллеров сервлета.
- Бины локализации: messageSource, localeResolver.
- Бины тем представлений: themeSource, themeResolver.
- Обработчик представлений и всё, что связано непосредственно с пользовательским вводов/выводом.
Также нужно иметь в виду, что бины, объявленные в корневой или общем (root-context.xml) контексте не имеют доступа к бинам, описанным в контекстах сервлетов, но бины, описанные в контекстах сервлетов имеют доступ к бинам в общем контексте.
Из этого вытекают следующие ограничения:
- Поскольку tx:annotation-driven описан в root-context.xml, то аннотация @Transactional может использоваться только для сервисов и слоя доступа к данным. Не используйте его для контроллеров. Это всё равно не будет иметь никакого эффекта. Да и вообще, контроллер не должен заниматься транзакциями и бизнес-логикой, это не его обязанность. Он должен принять данные от пользователя, вызвать слой сервисов и показать результат.
- Бин messageSource не доступен для слоя сервисов и слоя доступа к данным. Это тоже вполне верно. Слой сервисов не работает с пользовательским вводом выводом и ничего не отображает ему. Следовательно, ему не нужно знать ничего о локалях и локализованных сообщениях. Вся работа с messageSource должна быть в контроллерах и представлениях.
- Spring Security общий для всех сервлетов. То есть, если пользователь авторизовался в одном сервлете, то он автоматически считается авторизованным с той же ролью и тем же Principal в других сервлетах. Но вы можете объявить для каждого сервлета разные формы входа, разные способы аутентификации. Используйте для этого несколько тегов <http pattern=»…».
А что скажете насчет интерсепторов? Их лучше в корневой или сервлетный контекст?
Полагаю, что в тот же контекст, что и бины, вызовы которых они перехватывают.