1. введение
Проще говоря, в Переднем контроллере шаблон проектирования , один контроллер отвечает за направление входящих HttpRequests для всех других контроллеров и обработчиков приложения .
Spring DispatcherServlet реализует этот шаблон и, следовательно, отвечает за правильную координацию HttpRequests с их правыми обработчиками.
В этой статье мы рассмотрим рабочий процесс Spring Dispatcherservlet обработки запросов и как реализовать несколько интерфейсов, участвующих в этом рабочем процессе.
2. Обработка запроса DispatcherServlet
По сути, DispatcherServlet обрабатывает входящий Http-запрос , делегирует запрос и обрабатывает этот запрос в соответствии с настроенными 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 облегчает использование контроллеров, сервлетов, Http-запросов и 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 заключается в отправке входящих Http-запросы к правильным обработчикам , указанным с помощью аннотаций @Controller или @RestController|/.
В качестве примечания, основное различие между @Controller и @RestController заключается в том, как генерируется ответ – @RestController также определяет @ResponseBody по умолчанию.
Запись, в которой мы углубляемся в гораздо большую глубину относительно контроллеров Spring, можно найти здесь .
3.3. Интерфейс ViewResolver
/| ViewResolver присоединяется к DispatcherServlet в качестве параметра конфигурации объекта ApplicationContext .
A ViewResolver определяет, какие виды представлений обслуживаются диспетчером и откуда они обслуживаются .
Вот пример конфигурации, которую мы поместим в наш App Config для рендеринга страниц JSP:
@Configuration @EnableWebMvc @ComponentScan("com.baeldung.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 или плитки, с отображаемыми представлениями
Один из распространенных вопросов связан с тем, как именно связаны ViewResolver | диспетчера и общая структура каталогов проекта . Давайте взглянем на основы.
Вот пример конфигурации пути для InternalViewResolver с использованием XML-конфигурации Spring:
Для нашего примера мы предположим, что наше приложение размещается на:
http://localhost:8080/
Это адрес и порт по умолчанию для локально размещенного сервера Apache Tomcat.
Предполагая , что наше приложение называется dispatcher example-1.0.0 , наши представления JSP будут доступны из:
http://localhost:8080/dispatcherexample-1.0.0/jsp/
Путь для этих представлений в рамках обычного весеннего проекта с Maven таков:
src -| main -| java resources webapp -| jsp WEB-INF
Расположение представлений по умолчанию находится в WEB-INF. Путь, указанный для нашего InternalViewResolver в приведенном выше фрагменте, определяет подкаталог “src/main/webapp”, в котором будут доступны ваши представления.
3.4. Интерфейс LocaleResolver
Основной способ настройки сеанса, запроса или информации о файлах cookie для нашего диспетчера-через интерфейс LocaleResolver .
CookieLocaleResolver – это реализация, позволяющая настраивать свойства приложения без сохранения состояния с помощью файлов cookie. Давайте добавим его в App Config .
@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 ( ) определяет соответствующий часовой пояс объект для рассматриваемого приложения Bean .
Оба метода доступны в каждой из вышеперечисленных реализаций LocaleResolver .
3.5. Интерфейс ThemeResolver
Весна обеспечивает стилистическую тематизацию наших взглядов.
Давайте посмотрим, как настроить наш диспетчер для обработки тем.
Во-первых, давайте настроим всю конфигурацию, необходимую для поиска и использования наших статических файлов тем . Нам нужно установить статическое местоположение ресурса для нашего источника Темы , чтобы настроить фактические Темы сами по себе (объекты Темы содержат всю информацию о конфигурации, указанную в этих файлах). Добавьте это в App Config :
@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 object . Добавить в Конфигурацию приложения:
@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 добавляется в наше представление для отображения правильного стиля:
Следующий запрос URL-адреса отображает тему example с помощью параметра “тема”, переданного в наш настроенный ThemeChangeIntercepter:
http://localhost:8080/dispatcherexample-1.0.0/?theme=example
3.6. Интерфейс MultipartResolver
Реализация MultipartResolver проверяет запрос на наличие составных частей и оборачивает их в MultipartHttpServletRequest для дальнейшей обработки другими элементами процесса, если обнаружена хотя бы одна составная часть. Добавить в App Config :
@Bean public CommonsMultipartResolver multipartResolver() throws IOException { CommonsMultipartResolver resolver = new CommonsMultipartResolver(); resolver.setMaxUploadSize(10000000); return resolver; }
Теперь, когда мы настроили наш MultipartResolver bean, давайте настроим контроллер для обработки 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. Заключение
В этом уроке мы рассмотрели Spring DispatcherServlet и несколько способов его настройки.
Как всегда, исходный код, используемый в этом учебнике, доступен на Github .