Шаблонизаторы
Помимо веб-служб REST, можно также использовать Spring MVC для обработки динамического HTML-содержимого. Spring MVC поддерживает различные технологии шаблонизации, включая Thymeleaf, FreeMarker и JSP. Кроме того, многие другие шаблонизаторы содержат свои собственные средства интеграции с Spring MVC.
Spring Boot предусматривает поддержку автоконфигурации для следующих шаблонизаторов:
Если один из этих шаблонизаторов используется с конфигурацией по умолчанию, то шаблоны автоматически подбираются из src/main/resources/templates
.
Обработка ошибок
По умолчанию Spring Boot предусматривает отображение /error
, которое обрабатывает все ошибки адекватным образом, и оно регистрируется как "глобальная" страница ошибок в контейнере сервлетов. Для машинных клиентов оно создает ответ в формате JSON с подробным описанием ошибки, кодом состояния HTTP и сообщением об исключении. Для браузерных клиентов существует представление ошибок "whitelabel", которое визуализирует те же данные в формате HTML (чтобы настроить его, добавьте View
, разрешающий error
).
Существует ряд свойств server.error
, которые можно установить, если нужно персонализировать настройки логики обработки ошибок по умолчанию.
Чтобы полностью заменить логику работы по умолчанию, можно реализовать ErrorController
и зарегистрировать определение бина этого типа или добавить бин типа ErrorAttributes
, чтобы использовать существующий механизм, но заменить содержимое.
BasicErrorController
можно использовать в качестве базового класса для кастомного ErrorController
. Это особенно практично, если нужно добавить обработчик для нового типа содержимого (обработчик по умолчанию обрабатывает только text/html
и передает запасной вариант для всего остального). Для этого расширьте BasicErrorController
, добавьте публичный метод с аннотацией @RequestMapping
, имеющий атрибут produces
, и создайте бин нового типа.Помимо этого, можно определить класс, аннотированный @ControllerAdvice
, чтобы настроить возвращаемый JSON-документ под определенный контроллер и/или тип исключения, как показано в следующем примере:
@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;
}
}
@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
.
В некоторых случаях ошибки, обработанные на уровне контроллера, не регистрируются инфраструктурой метрик. Приложения могут обеспечить запись таких исключений в метрики запроса, настроив обработанное исключение в качестве атрибута запроса:
@Controller
public class MyController {
@ExceptionHandler(CustomException.class)
String handleCustomException(HttpServletRequest request, CustomException ex) {
request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex);
return "errorView";
}
}
@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
, как показано в следующем примере:
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;
}
}
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.
@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"));
}
}
@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
, как показано в следующем примере:@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;
}
}
@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)
, как показано в следующем примере:
@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**");
}
};
}
}
@Configuration(proxyBeanMethods = false)
class MyCorsConfiguration {
@Bean
fun corsConfigurer(): WebMvcConfigurer {
return object : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
}
}
}
}
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ