JavaRush /Курсы /Модуль 5. Spring /Spring Boot и Web MVC, часть 3

Spring Boot и Web MVC, часть 3

Модуль 5. Spring
16 уровень , 2 лекция
Открыта

Шаблонизаторы

Помимо веб-служб REST, можно также использовать Spring MVC для обработки динамического HTML-содержимого. Spring MVC поддерживает различные технологии шаблонизации, включая Thymeleaf, FreeMarker и JSP. Кроме того, многие другие шаблонизаторы содержат свои собственные средства интеграции с Spring MVC.

Spring Boot предусматривает поддержку автоконфигурации для следующих шаблонизаторов:

Если один из этих шаблонизаторов используется с конфигурацией по умолчанию, то шаблоны автоматически подбираются из src/main/resources/templates.

В зависимости от того, каким образом выполняется приложение, IDE может по-разному упорядочивать classpath. Выполнение приложения в IDE из его основного метода приводит к другому упорядочиванию, чем при выполнении приложения с помощью Maven или Gradle, или из его упакованного jar-файла. Это может привести к тому, что Spring Boot не сможет найти ожидаемый шаблон. Если возникла такая проблема, то можно изменить упорядочивание classpath в IDE, чтобы классы и ресурсы модуля шли первыми.

Обработка ошибок

По умолчанию Spring Boot предусматривает отображение /error, которое обрабатывает все ошибки адекватным образом, и оно регистрируется как "глобальная" страница ошибок в контейнере сервлетов. Для машинных клиентов оно создает ответ в формате JSON с подробным описанием ошибки, кодом состояния HTTP и сообщением об исключении. Для браузерных клиентов существует представление ошибок "whitelabel", которое визуализирует те же данные в формате HTML (чтобы настроить его, добавьте View, разрешающий error).

Существует ряд свойств server.error, которые можно установить, если нужно персонализировать настройки логики обработки ошибок по умолчанию.

Чтобы полностью заменить логику работы по умолчанию, можно реализовать ErrorController и зарегистрировать определение бина этого типа или добавить бин типа ErrorAttributes, чтобы использовать существующий механизм, но заменить содержимое.

BasicErrorController можно использовать в качестве базового класса для кастомного ErrorController. Это особенно практично, если нужно добавить обработчик для нового типа содержимого (обработчик по умолчанию обрабатывает только text/html и передает запасной вариант для всего остального). Для этого расширьте BasicErrorController, добавьте публичный метод с аннотацией @RequestMapping, имеющий атрибут produces, и создайте бин нового типа.

Помимо этого, можно определить класс, аннотированный @ControllerAdvice, чтобы настроить возвращаемый JSON-документ под определенный контроллер и/или тип исключения, как показано в следующем примере:

Java
import javax.servlet.RequestDispatcher; import javax.servlet.http.HttpServletRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; @ControllerAdvice(basePackageClasses = SomeController.class) public class MyControllerAdvice extends ResponseEntityExceptionHandler { @ResponseBody @ExceptionHandler(MyException.class) public ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) { HttpStatus status = getStatus(request); return new ResponseEntity<>(new MyErrorBody(status.value(), ex.getMessage()), status); } private HttpStatus getStatus(HttpServletRequest request) { Integer code = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); HttpStatus status = HttpStatus.resolve(code); return (status != null) ? status : HttpStatus.INTERNAL_SERVER_ERROR; } } 
Kotlin
import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.ControllerAdvice import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.ResponseBody import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler import javax.servlet.RequestDispatcher import javax.servlet.http.HttpServletRequest @ControllerAdvice(basePackageClasses = [SomeController::class]) class MyControllerAdvice : ResponseEntityExceptionHandler() { @ResponseBody @ExceptionHandler(MyException::class) fun handleControllerException(request: HttpServletRequest, ex: Throwable): ResponseEntity<*> { val status = getStatus(request) return ResponseEntity(MyErrorBody(status.value(), ex.message), status) } private fun getStatus(request: HttpServletRequest): HttpStatus { val code = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE) as Int val status = HttpStatus.resolve(code) return status ?: HttpStatus.INTERNAL_SERVER_ERROR } } 

В предыдущем примере, если MyException генерируется контроллером, определенным в том же пакете, что и SomeController, вместо представления ErrorAttributes используется JSON-представление POJO-объекта MyErrorBody.

В некоторых случаях ошибки, обработанные на уровне контроллера, не регистрируются инфраструктурой метрик. Приложения могут обеспечить запись таких исключений в метрики запроса, настроив обработанное исключение в качестве атрибута запроса:

Java
import javax.servlet.http.HttpServletRequest; import org.springframework.boot.web.servlet.error.ErrorAttributes; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ExceptionHandler; @Controller public class MyController { @ExceptionHandler(CustomException.class) String handleCustomException(HttpServletRequest request, CustomException ex) { request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex); return "errorView"; } } 
Kotlin
import org.springframework.boot.web.servlet.error.ErrorAttributes import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.ExceptionHandler import javax.servlet.http.HttpServletRequest @Controller class MyController { @ExceptionHandler(CustomException::class) fun handleCustomException(request: HttpServletRequest, ex: CustomException?): String { request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex) return "errorView" } } 

Кастомные страницы ошибок

Если необходимо вывести на экран кастомную HTML-страницу ошибки для заданного кода состояния, можно добавить файл в каталог /error. Страницы ошибок могут быть либо статическими HTML (то есть добавленными в любой из каталогов статических ресурсов), либо построенными с помощью шаблонов. Имя файла должно быть точным кодом состояния или маской серии.

Например, чтобы отобразить 404 со статическим HTML-файлом, структура каталогов должна выглядеть следующим образом:

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- public/
             +- error/
             |   +- 404.html
             +- <other public assets>

Чтобы отобразить все ошибки 5xx с помощью шаблона FreeMarker, структура каталога должна быть следующей:

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- templates/
             +- error/
             |   +- 5xx.ftlh
             +- <other templates>

Для более сложных отображений можно также добавить бины, реализующие интерфейс ErrorViewResolver, как показано в следующем примере:

Java
import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver; import org.springframework.http.HttpStatus; import org.springframework.web.servlet.ModelAndView; public class MyErrorViewResolver implements ErrorViewResolver { @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { // Используем запрос или статус, чтобы по желанию вернуть ModelAndView if (status == HttpStatus.INSUFFICIENT_STORAGE) { // Здесь мы можем добавить кастомное значения модели new ModelAndView("myview"); } return null; } } 
Kotlin
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver import org.springframework.http.HttpStatus import org.springframework.web.servlet.ModelAndView import javax.servlet.http.HttpServletRequest class MyErrorViewResolver : ErrorViewResolver { override fun resolveErrorView(request: HttpServletRequest, status: HttpStatus, model: Map<String, Any>): ModelAndView? { // Используем запрос или статус, чтобы по желанию вернуть ModelAndView if (status == HttpStatus.INSUFFICIENT_STORAGE) { // Здесь мы можем добавить кастомное значения модели return ModelAndView("myview") } return null } } 

Также можно использовать обычные функции Spring MVC, такие как методы, помеченные аннотацией @ExceptionHandler и аннотацию @ControllerAdvice. ErrorController затем подхватывает все необработанные исключения.

Отображение страниц ошибок вне Spring MVC

В случае приложений, не использующих Spring MVC, можно использовать интерфейс ErrorPageRegistrar для прямой регистрации ErrorPages. Эта абстракция работает напрямую с базовым встроенным контейнером сервлетов и хорошо себя показывает, даже если у вас нет DispatcherServlet для Spring MVC.

Java
import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.ErrorPageRegistrar; import org.springframework.boot.web.server.ErrorPageRegistry; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; @Configuration(proxyBeanMethods = false) public class MyErrorPagesConfiguration { @Bean public ErrorPageRegistrar errorPageRegistrar() { return this::registerErrorPages; } private void registerErrorPages(ErrorPageRegistry registry) { registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400")); } } 
Kotlin
import org.springframework.boot.web.server.ErrorPage import org.springframework.boot.web.server.ErrorPageRegistrar import org.springframework.boot.web.server.ErrorPageRegistry import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.http.HttpStatus @Configuration(proxyBeanMethods = false) class MyErrorPagesConfiguration { @Bean fun errorPageRegistrar(): ErrorPageRegistrar { return ErrorPageRegistrar { registry: ErrorPageRegistry -> registerErrorPages(registry) } } private fun registerErrorPages(registry: ErrorPageRegistry) { registry.addErrorPages(ErrorPage(HttpStatus.BAD_REQUEST, "/400")) } } 
Если вы регистрируете ErrorPage с использованием пути, который в конечном итоге обрабатывается с помощью Filter (что характерно для некоторых веб-фреймворков, не относящихся к Spring, таких как Jersey и Wicket), то Filter должен быть явным образом зарегистрирован в качестве диспетчера ERROR, как показано в следующем примере:
Java
import java.util.EnumSet; import javax.servlet.DispatcherType; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) public class MyFilterConfiguration { @Bean public FilterRegistrationBean<MyFilter> myFilter() { FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(new MyFilter()); // ... registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class)); return registration; } } 
Kotlin
import org.springframework.boot.web.servlet.FilterRegistrationBean import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import java.util.EnumSet import javax.servlet.DispatcherType @Configuration(proxyBeanMethods = false) class MyFilterConfiguration { @Bean fun myFilter(): FilterRegistrationBean<MyFilter> { val registration = FilterRegistrationBean(MyFilter()) // ... registration.setDispatcherTypes(EnumSet.allOf(DispatcherType::class.java)) return registration } } 

Обратите внимание, что FilterRegistrationBean по умолчанию не содержит тип диспетчера ERROR.

Обработка ошибок при WAR-развертывании

При развертывании в контейнере сервлетов Spring Boot использует свой фильтр страницы ошибок для перенаправления запроса с кодом ошибки на соответствующую страницу ошибок. Это необходимо, поскольку спецификация сервлетов не предусматривает API для регистрации страниц ошибок. В зависимости от контейнера, в который вы развертываете свой war-файл, и технологий, которые использует ваше приложение, может потребоваться дополнительное конфигурирование.

Фильтр страницы ошибки сможет перенаправить запрос на правильную страницу ошибки только в том случае, если ответ еще не был зафиксирован. По умолчанию WebSphere Application Server 8.0 и более поздних версий фиксирует ответ после успешного завершения метода службы сервлета. Следует отключить эту логику работы, установив для параметра com.ibm.ws.webcontainer.invokeFlushAfterService значение false.

Если вы используете Spring Security и хотите получить доступ к принципалу на странице ошибки, то необходимо настроить фильтр Spring Security так, чтобы он вызывался при отправке ошибок. Для этого установите свойство spring.security.filter.dispatcher-types в async, error, forward, request.

Поддержка CORS

Совместное использование ресурсов между разными источниками (CORS) является спецификацией W3C, реализованной большинством браузеров, которая позволяет гибко определять, какие типы междоменных запросов будут авторизованы, и она призвана заменить некоторые менее безопасные и менее эффективные подходы, таких как IFRAME или JSONP.

Начиная с версии 4.2, Spring MVC поддерживает CORS. Использование метода контроллера для конфигурации CORS с аннотациями @CrossOrigin в приложении Spring Boot не требует какой-либо специальной конфигурации. Глобальную конфигурацию CORS моно определить путем регистрации бина WebMvcConfigurer с настроенным методом addCorsMappings(CorsRegistry), как показано в следующем примере:

Java
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration(proxyBeanMethods = false) public class MyCorsConfiguration { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**"); } }; } } 
Kotlin
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.web.servlet.config.annotation.CorsRegistry import org.springframework.web.servlet.config.annotation.WebMvcConfigurer @Configuration(proxyBeanMethods = false) class MyCorsConfiguration { @Bean fun corsConfigurer(): WebMvcConfigurer { return object : WebMvcConfigurer { override fun addCorsMappings(registry: CorsRegistry) { registry.addMapping("/api/**") } } } } 
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ