Рубрики
Без рубрики

Вступление к весеннему диспетчерскому сервлету

Краткое и практическое руководство по рабочему процессу обработки запросов в DispatcherServlet Spring с акцентом на интерфейсы HandlerAdapter.

Автор оригинала: baeldung.

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

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

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