Spring: Web

Spring: Web


Делится на 2 больших группы - Spring Web MVC и Spring WebFlow.

Spring Web MVC

Основан на паттерне Model-View-Control. Точкой входа всегда является DispatcherServlet, который выполняет много работы (определяет карту запросов, локали, временные зоны, добавлеяет возможность загрузки файлов и т.д.). Он преобразует HTTP запрос в команды для контроллеров и помпонентов программы. Так же он возвращает результат действия по запросу.

Dispatcher плотно работает с ContextLoadListener, который контролирует работу Spring Context.

Конфигурирование

Компоненты Spring Web можно разделить на 2 группы:

  • Компоненты MVC инфраструктуры;
    • Handler mappings - определяют куда пойдет запрос (к какому контроллеру), а так же определяет Listeners для запроса;
    • Handler adapters - определяет методы для обработки запросов;
    • View resolver - определяют обработчики для ответов;
    • Exception resolvers - обработчики ошибок;
  • Компоненты, определенные пользователями;
    • Handler interceptor - перехватчик событий, который вносит дополнения в обработку;
    • Контроллеры - опрделяют поведение при обработки запроса.

Конфигурационные бины

Помимо DispatcherServlet важными элементами являются:

  • HandlerMapping
  • HandlerAdapter
  • ViewResolver
  • View
  • HandlerExceptionResolver

Всех их DispatcherServlet пытается найти в качестве реализованных компонентов Spring приложения. По ум. для всех существует реализация в Spring Web MVC, а конфигурация находится в DispatcherServlet.

MVC Flow

Управление компронентами

Используемые аннотации:

  • @Controller. Определеяет класс как контроллер;
  • @RequestMapping. Применим как к классу, так и к методу. Добавляет маппинг на компонент. Может принимать параметр method, который определяет HTTP метод запроса (GET, PUT, POST, DELETE, OPTION и т.д.);
  • @Get/Post/Put/../DeleteMapping. Быстрая замена для аннотации @RequestMapping с добавлением параметра method;
  • @PathVariable. Аннотация параметра метода, который является частью строки URI. Если параметр value не указан, то будет выполнена попытка поиска по схожему имени переменной;
  • @RequestParam. Параметр, который будет прочитан из строки URI;
  • @RequestHeader. Параметр, который MVC попытается получить из заголовков запроса;
  • @CookieValue. Похож на @RequestHeader, но содержит cookie со стороны клиента;
  • @SessionAttribute. Содержит именованный атрибуты сессии;
  • @RequestAttribute. Содержит именованный аттрибут запроса;
  • @ModelAttribute. Накладывается на метод, который необходимо рассматривать как обработчик каждого метода в контроллере:
1
2
3
4
5
6
@ModelAttribute
protected User requestUser(@PathVariable long id) {
return new User(id);
}
@PostMapping
public String save(User user) {...}
  • @ResponseStatus. Определяет HTTP код ответа. Рекомендации:
    • Для POST: если успешно - 200(OK) или 204(No Content). Если
    • Для PUT: если успешно - 201(Created), в противном случае - 404(Not found)

Сущности для взаимодействия:

  • WebRequest. Объект, который содержит собранную информацию по веб-запросу. Аналоги: NativeWebRequest, ServletRequest;
  • ServletResponse. Объект, который можно использовать для собственной реализации ответа;
  • HttpSession. Объект с HTTP сессией. Может быть null;
  • PushBuilder. Позволяет выполнять Push сообщение для клиента. Клиент должен поддерживать HTTP/2. Push технология помогает снизить время общего отображения результатов, отправляя Push уведомления на загрузку какого-либо ресурса до(!) получения всего ответа клиентом. Утилита очень удобная и положительно сказывается на времени отображения данных у клиента, но несколько увеличивает нагрузку на сеть;
  • Principal. Объект содержит инфо об авторизованном пользователе (если используется пакет Spring Security);
  • HTTP Method. Объект для аудирования и дополнительных проверок;
  • BindingResult, Error, Locale. Вспомогательные объекты. В современной парадигме сервисной архитектуры, устарели.
  • Reader, InputStream. Позволяют читать содержимое тела запроса;
  • Writer, OutputStream. Позволяет записывать в содержимое тела ответа;
  • MatrixVariable. Аналог @RequestParam, но считывает параметры из URI особым образом. Пример, при URI: some/my/service/path;a=1;b=28;c=7 . Переменные матрицы будут выглядеть так: @MatrixVariable("a") int a, @MatrixVariable("b") int b, @MatrixVariable("c") int c; значениям присвоятся соответствующие параметры. Но в полной мере матрица раскрывается при @MatrixVariable Map<String, Integer> matrix. По умолчанию, матричные параметры не поддерживаются. Чтобы их включить следует:
1
2
3
4
5
6
7
8
9
@Configuration
class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper helper = new UrlPathHelper();
helper.removeSemicolonContent(false);
configurer.setUrlPathHelper(helper);
}
}
  • RedirectAttributes. Объект, который управляет перенаправлением клиента с использование redirect://... К нему можно добавить атрибуты любого типа: redirectAttributes.addFlashAttribute("name", "value")

View и View Resolvers

Handler - полезный и гибки механизм для управления HTTP запросами. В случае с web приложением, возврат имени View и параметров для его отображения переданные через Model, применяется повсеместно.

Если Controller в качестве View возващает void, то будет отображаться URL по умолчанию (зависит от конфигурации Spring Web MVC).

В зависимости от провайдера, предоставляющего View, выполянется соответствующее отображение. В Spring изначально уже есть возможность использования провайдеров JSP, Velocity шаблоны, XSLT представления.

Реализацией ViewResolver по умолчанию является InternalResourceViewResolver. В конфигурации или XML это можно изменить.

Конфигурирование ViewResolver

В приложении допустимо использование нескольких ViewResolver, но в этом случае придется выдать им приоритезацию:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Bean
public ViewResolver xlsViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(0);
return resolver;
}

@Bean
public ViewResolver jspViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("my/views/location");
resolver.setSuffix(".jsp");
resolver.setOrder(1);
return resolver;
}

@Bean(name="users/list.xlsx")
public View excelView() {
return new ExcelView();
}

При возврате web методом имени View, который необходимо использовать, String обходит ViewResolver согласно указанным приоритетам и вытается получить View - до тех пор пока не получит его (не null). Если обошел все View, но ничего не нашел, будет выкинуто исключение ServerException.

Клиент так же может выбрать View, который он хочет получить (введено с Spring 3.0). Существует следующие стратегии выбора View:

  • Посредством URL, к которому выполняется обращение;
  • Посредством заголовка Accept с указание допускаемого типа клиентом;
  • Посредством передачи требуемого типа в параметре запроса.

@MVC

Каждый запрос в Spring следует рассматривать так:

  1. Запрос приходит в DispatcherServlet;
  2. Он пытается найти контроллер, который должен обработать соответствующий запрос по требуемому URI;
  3. Если такого нет - Spring считает что идет обращение к статичному ресурсу (например, jpeg, font, css и т.д.) и передает управление default-servlet-handler (с реализацией по умолчанию DefaultServletHttpRequestHandler). По умолчанию в данной реализации поиск осуществляется для всех ресурсов (/*).

Настройка аннотациями

  • В каком-либо конфигурационном бине (@Configuration) добавить аннотацию @EnableWebMvc;
  • и/или добавить реализацию этим бином интерфейса WebMvcConfigurerAdapter (или наследоваться от стандартной реализации WebMvcConfigurer).

Упрощенная настройка

Начиная с Spring 3.0 инициализация упрощается с введением WebApplicationInitializer.

1
2
3
4
5
6
7
8
9
public class MyWebINit implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext ctx) throws ServletException {
ServletRegistration.Dynamic registeration = ctx.addServlet("my-servlet", new MyServlet());
registration.setLoadOnStartup(1);
registration.addMapping("my-servlet/exec/svc");
registration.setInitialParameter("contextConfigurationLocation", "WEB-INF/mvc.xml");
}
}

Но если использовать аннотацию @EnableWebMvc, то инициализация может немного измениться:

1
2
registration.setInitialParameter("contextConfigurationLocation", "my.conf.mvc.WebConfig");
registration.setInitParameter("contextClass", "org.springframework.AnnotationConfigWebApplicationContext");

Перехват ошибок

В Spring осуществляется реализацией HandlerExceptionResolver. Handler может быть несколько и всех их можно упорядочить в определенном порядке для обработки специфическим способом особые исключения.
Эту задачу выполяняет ExceptionHandlerExceptionResolver - вызывает методы с аннотацией @ExceptionHandler у класса с аннотацией @ControllerAdvice.
Так же следует обратить внимание на ResponseStatusExceptionResolver - он выполняет методы с аннотацией @ResponseStatus для возврата соответствующего статуса HTTP.
И, наконец, DefaultHandlerExceptionResolver - обрабатывает ошибки Spring MVC и отдает соответствующий статус.

HandlerExceptionResolver

Реализация данного интерфейса важна для перехвата исключений в Spring MVC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyExceptionHandler extends SimpleMappingExceptionResolver {
@Override
protected ModelAndView doResolveException(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception exception) {
if(ex instanceof ...) {
...
} else if(ex instanceof ...) {
...
} else {
...
}
return null;
}
}

Существует сделаюущие особенности с HandlerExceptionResolver:

  • Возвращает ModelAndView, который определяет тип исключения и выдает соответствующую ошибку;
  • Может вернуть null, если не удалось установить обработку исключения - в таком случае исключение передается другим Handler. Если ни один Handler не смог установить отбработчика исключения, оно передается на обработку в Server Container (т.е. серверу приложений);
  • Можно создать бин этого типа и определить ему максимальный приоритет - HIGHEST_PRECEDENCE. Тогда бин будет обрабатывать исключения первый и всегда.

Обработка через @ExceptionHandler

Можно обязать компонент (в т.ч. Controller) обрабатывать исключения определенного типа:

1
2
3
4
5
6
7
@Controller
public class MyController {
@ExceptionHandler
public ModelAndView someError(HttpServletRequest request, MyCustomException exception) {
...
}
}

При этом, почти все параметры (точнее, типы), которые получает web-метод перехватчика (Controller) может получать и @ExceptionHandler. Причем, тип ошибки можно передать в аннотации, а из параметров убрать:

1
2
@ExceptionHandler(MyCustomException.class)
public ModelAndView someError() {...}

@ControllerAdvice

Является негласным стандартным бином для отлова всех исключений. Аннотация наследуется от @Controller, так что уже является Spring компонентом:

1
2
3
4
5
@ControllerAdvice
public class SomeMultiHandler {
@ExceptionHandler ...
@ExceptionHandler ...
}

Тестирование MVC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ExtendWith(MockitoExtension.class)
class UserControllerTest {
@Mock
UserService svc;

@InjectMocks
UserController controller;

@Test
public void listAll() {
when(svc.findAll()).thenReturn(...);
controller.listAll();
// asserts
}
}
 Comments
Comment plugin failed to load
Loading comment plugin