Перейти к основному содержимому

Введение в Spring DispatcherServlet

· 8 мин. чтения

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;
}
}

Очень прямолинейно! В этом есть три основных части:

  1. установка префикса, который задает путь URL-адреса по умолчанию для поиска заданных представлений внутри
  2. тип представления по умолчанию, который устанавливается с помощью суффикса
  3. установка класса представления в распознавателе, который позволяет связывать такие технологии, как 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 .