1. Введение
Проще говоря, в шаблоне
проектирования Front Controller
один контроллер отвечает за направление входящих запросов HttpRequest
ко всем другим контроллерам и обработчикам приложения . **
**
DispatcherServlet
Spring реализует этот шаблон и, следовательно, отвечает за правильную координацию HttpRequests
с их правыми обработчиками.
В этой статье мы рассмотрим рабочий процесс обработки запросов Spring DispatcherServlet
и способы реализации нескольких интерфейсов, участвующих в этом рабочем процессе.
2. Обработка запроса DispatcherServlet
По сути, DispatcherServlet
обрабатывает входящий HttpRequest
, делегирует запрос и обрабатывает этот запрос в соответствии с настроенными интерфейсами HandlerAdapter
, которые были реализованы в приложении Spring вместе с сопутствующими аннотациями, определяющими обработчики, конечные точки контроллера и объекты ответа.
Давайте более подробно рассмотрим, как DispatcherServlet
обрабатывает компонент:
- WebApplicationContext
,
связанный сDispatcherServlet
под ключомDispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
, ищется и становится доступным для всех элементов процесса. DispatcherServlet
находит все реализацииинтерфейса HandlerAdapter
, настроенные для вашего диспетчера, с помощьюgetHandler() —
каждая найденная и настроенная реализация обрабатывает запрос черезhandle()
до конца процесса.- LocaleResolver
необязательно
привязан к запросу, чтобы разрешить элементам в процессе разрешать локаль. - ThemeResolver
необязательно
привязан к запросу, чтобы позволить элементам, таким как представления, определять, какую тему использовать. - если указан
MultipartResolver
, запрос проверяется на наличие MultipartFile
s — все найденные файлы помещаются вMultipartHttpServletRequest
для дальнейшей обработки . Реализации HandlerExceptionResolver,
объявленные вWebApplicationContext
, собирают исключения, возникающие во время обработки запроса.
Подробнее обо всех способах регистрации и настройки DispatcherServlet можно узнать
здесь .
3. Интерфейсы HandlerAdapter
Интерфейс HandlerAdapter
упрощает использование контроллеров, сервлетов, HttpRequests
и путей HTTP через несколько конкретных интерфейсов. Таким образом, интерфейс HandlerAdapter
играет важную роль на многих этапах рабочего процесса обработки запросов DispatcherServlet
.
Во- первых, каждая реализация HandlerAdapter
помещается в HandlerExecutionChain
из метода getHandler()
вашего диспетчера . Затем каждая из этих реализаций обрабатывает()
объект HttpServletRequest
по мере выполнения цепочки выполнения.
В следующих разделах мы более подробно рассмотрим несколько наиболее важных и часто используемых HandlerAdapters
.
3.1. Сопоставления
Чтобы понять сопоставления, нам нужно сначала посмотреть, как аннотировать контроллеры, поскольку контроллеры очень важны для интерфейса HandlerMapping
.
SimpleControllerHandlerAdapter позволяет явно реализовать контроллер без аннотации
@Controller
.
RequestMappingHandlerAdapter поддерживает методы, аннотированные аннотацией
@RequestMapping
.
Здесь мы сосредоточимся на аннотации @Controller
, но также доступен полезный ресурс с несколькими примерами использования SimpleControllerHandlerAdapter .
Аннотация @RequestMapping
устанавливает конкретную конечную точку, в которой обработчик будет доступен в связанном с ним WebApplicationContext .
Давайте посмотрим на пример контроллера
, который предоставляет и обрабатывает конечную точку «/user/example»
:
@Controller
@RequestMapping("/user")
@ResponseBody
public class UserController {
@GetMapping("/example")
public User fetchUserExample() {
// ...
}
}
Пути, указанные в аннотации @RequestMapping
, управляются внутри через интерфейс HandlerMapping
.
Структура URL-адресов, естественно, связана с самим DispatcherServlet
и определяется отображением сервлета.
Таким образом, если DispatcherServlet
сопоставлен с '/', то все сопоставления будут покрыты этим сопоставлением.
Однако, если вместо этого сопоставление сервлета ' /dispatcher
', то любые аннотации @RequestMapping
будут относиться к этому корневому URL-адресу.
Помните, что '/' — это не то же самое, что '/*' для отображений сервлетов! '/' — это сопоставление по умолчанию, и все URL-адреса отображаются в зоне ответственности диспетчера.
'/*' сбивает с толку многих новых разработчиков Spring. В нем не указывается, что все пути с одинаковым контекстом URL находятся в зоне ответственности диспетчера. Вместо этого он переопределяет и игнорирует другие сопоставления диспетчера. Таким образом, «/ пример» будет отображаться как 404!
По этой причине '/*' не следует использовать, кроме как в очень ограниченных случаях (например, при настройке фильтра).
3.2. Обработка HTTP-запросов
Основной обязанностью DispatcherServlet
является отправка входящих HttpRequests
правильным обработчикам , указанным в аннотациях @Controller
или @RestController
.
В качестве примечания: основное различие между @Controller
и @RestController
заключается в том, как генерируется ответ — @RestController
также определяет @ResponseBody
по умолчанию.
Рецензию, в которой мы углубляемся в контроллеры Spring, можно найти здесь .
3.3. Интерфейс ViewResolver
_
ViewResolver присоединяется
к DispatcherServlet
в качестве параметра конфигурации объекта ApplicationContext .
ViewResolver определяет, какие
представления обслуживаются диспетчером и откуда они обслуживаются .
Вот пример конфигурации, которую мы поместим в наш AppConfig
для рендеринга страниц JSP:
@Configuration
@EnableWebMvc
@ComponentScan("com.foreach.springdispatcherservlet")
public class AppConfig implements WebMvcConfigurer {
@Bean
public UrlBasedViewResolver viewResolver() {
UrlBasedViewResolver resolver
= new UrlBasedViewResolver();
resolver.setPrefix("/WEB-INF/view/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
return resolver;
}
}
Очень прямолинейно! В этом есть три основных части:
- установка префикса, который задает путь URL-адреса по умолчанию для поиска заданных представлений внутри
- тип представления по умолчанию, который устанавливается с помощью суффикса
- установка класса представления в распознавателе, который позволяет связывать такие технологии, как JSTL или Tiles, с отображаемыми представлениями.
Один общий вопрос касается того, насколько точно связаны ViewResolver
диспетчера и общая структура каталогов проекта . Давайте взглянем на основы.
Вот пример конфигурации пути для InternalViewResolver
с использованием XML-конфигурации Spring:
<property name="prefix" value="/jsp/"/>
Для нашего примера предположим, что наше приложение размещено на:
http://localhost:8080/
Это адрес и порт по умолчанию для локального сервера Apache Tomcat.
Предполагая, что наше приложение называется dispatcherexample-1.0.0
, наши представления JSP будут доступны из:
http://localhost:8080/dispatcherexample-1.0.0/jsp/
Путь для этих представлений в обычном проекте Spring с Maven таков:
src -|
main -|
java
resources
webapp -|
jsp
WEB-INF
Расположение по умолчанию для представлений находится в WEB-INF. Путь, указанный для нашего InternalViewResolver
в приведенном выше фрагменте, определяет подкаталог «src/main/webapp», в котором будут доступны ваши представления.
3.4. Интерфейс LocaleResolver
_
Основной способ настроить информацию о сеансе, запросе или файле cookie для нашего диспетчера — через интерфейс LocaleResolver
.
CookieLocaleResolver
— это реализация, позволяющая настраивать свойства приложения без сохранения состояния с помощью файлов cookie. Добавим его в AppConfig
.
@Bean
public CookieLocaleResolver cookieLocaleResolverExample() {
CookieLocaleResolver localeResolver
= new CookieLocaleResolver();
localeResolver.setDefaultLocale(Locale.ENGLISH);
localeResolver.setCookieName("locale-cookie-resolver-example");
localeResolver.setCookieMaxAge(3600);
return localeResolver;
}
@Bean
public LocaleResolver sessionLocaleResolver() {
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(Locale.US);
localResolver.setDefaultTimeZone(TimeZone.getTimeZone("UTC"));
return localeResolver;
}
SessionLocaleResolver
позволяет настраивать сеанс в приложении с отслеживанием состояния.
Метод setDefaultLocale
() представляет географический, политический или культурный регион, тогда как setDefaultTimeZone
( ) определяет соответствующий объект TimeZone для рассматриваемого
компонента
приложения .
Оба метода доступны в каждой из приведенных выше реализаций LocaleResolver
.
3.5. Интерфейс ThemeResolver
_
Spring обеспечивает стилистическую тематику для наших представлений.
Давайте посмотрим, как настроить наш диспетчер для обработки тем.
Во- первых, давайте настроим всю конфигурацию, необходимую для поиска и использования наших статических файлов темы . Нам нужно установить местоположение статического ресурса для нашего ThemeSource
, чтобы настроить сами фактические темы
( объекты Theme
содержат всю информацию о конфигурации, указанную в этих файлах). Добавьте это в AppConfig
:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/", "/resources/")
.setCachePeriod(3600)
.resourceChain(true)
.addResolver(new PathResourceResolver());
}
@Bean
public ResourceBundleThemeSource themeSource() {
ResourceBundleThemeSource themeSource
= new ResourceBundleThemeSource();
themeSource.setDefaultEncoding("UTF-8");
themeSource.setBasenamePrefix("themes.");
return themeSource;
}
Запросы, управляемые DispatcherServlet
, могут модифицировать тему через указанный параметр, переданный в setParamName
(), доступный в объекте ThemeChangeInterceptor
.
Добавьте в AppConfig:
@Bean
public CookieThemeResolver themeResolver() {
CookieThemeResolver resolver = new CookieThemeResolver();
resolver.setDefaultThemeName("example");
resolver.setCookieName("example-theme-cookie");
return resolver;
}
@Bean
public ThemeChangeInterceptor themeChangeInterceptor() {
ThemeChangeInterceptor interceptor
= new ThemeChangeInterceptor();
interceptor.setParamName("theme");
return interceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(themeChangeInterceptor());
}
Следующий тег JSP добавляется к нашему представлению, чтобы отображался правильный стиль:
<link rel="stylesheet" href="${ctx}/<spring:theme code='styleSheet'/>" type="text/css"/>
Следующий URL-запрос отображает пример
темы с использованием параметра 'theme', переданного в наш настроенный ThemeChangeIntercepter:
http://localhost:8080/dispatcherexample-1.0.0/?theme=example
3.6. Интерфейс MultipartResolver
_
Реализация MultipartResolver
проверяет запрос на составные части и упаковывает их в MultipartHttpServletRequest
для дальнейшей обработки другими элементами в процессе, если найдена хотя бы одна составная часть. Добавьте в AppConfig
:
@Bean
public CommonsMultipartResolver multipartResolver()
throws IOException {
CommonsMultipartResolver resolver
= new CommonsMultipartResolver();
resolver.setMaxUploadSize(10000000);
return resolver;
}
Теперь, когда мы настроили наш bean-компонент MultipartResolver
, давайте настроим контроллер для обработки запросов MultipartFile :
@Controller
public class MultipartController {
@Autowired
ServletContext context;
@PostMapping("/upload")
public ModelAndView FileuploadController(
@RequestParam("file") MultipartFile file)
throws IOException {
ModelAndView modelAndView = new ModelAndView("index");
InputStream in = file.getInputStream();
String path = new File(".").getAbsolutePath();
FileOutputStream f = new FileOutputStream(
path.substring(0, path.length()-1)
+ "/uploads/" + file.getOriginalFilename());
int ch;
while ((ch = in.read()) != -1) {
f.write(ch);
}
f.flush();
f.close();
in.close();
modelAndView.getModel()
.put("message", "File uploaded successfully!");
return modelAndView;
}
}
Мы можем использовать обычную форму для отправки файла в указанную конечную точку. Загруженные файлы будут доступны в папке CATALINA_HOME/bin/uploads.
3.7. Интерфейс HandlerExceptionResolver
_
Spring HandlerExceptionResolver
обеспечивает единую обработку ошибок для всего веб-приложения, отдельного контроллера или набора контроллеров.
Чтобы обеспечить настраиваемую обработку исключений для всего приложения, создайте класс с аннотацией @ControllerAdvice
:
@ControllerAdvice
public class ExampleGlobalExceptionHandler {
@ExceptionHandler
@ResponseBody
public String handleExampleException(Exception e) {
// ...
}
}
Любые методы в этом классе, аннотированные @ExceptionHandler
, будут доступны на каждом контроллере в зоне ответственности диспетчера.
Реализации интерфейса HandlerExceptionResolver в
ApplicationContext DispatcherServlet
доступны для перехвата конкретного контроллера в зоне ответственности этого диспетчера всякий раз , когда @ExceptionHandler
используется в качестве аннотации , а правильный класс передается в качестве параметра:
@Controller
public class FooController{
@ExceptionHandler({ CustomException1.class, CustomException2.class })
public void handleException() {
// ...
}
// ...
}
Метод handleException()
теперь будет служить обработчиком исключений для FooController
в нашем примере выше, если возникнет исключение CustomException1
или CustomException2
.
Вот статья, в которой более подробно рассматривается обработка исключений в веб-приложении Spring.
4. Вывод
В этом руководстве мы рассмотрели DispatcherServlet
Spring и несколько способов его настройки.
Как всегда, исходный код, используемый в этом руководстве, доступен на Github .