Motores de plantillas
Además de los servicios web REST, también puede utilizar Spring MVC para manejar contenido HTML dinámico. Spring MVC admite varias tecnologías de plantillas, incluidas Thymeleaf, FreeMarker y JSP. Además, muchos otros motores de plantillas proporcionan sus propias integraciones con Spring MVC.
Spring Boot proporciona soporte de configuración automática para los siguientes motores de plantillas:
Si uno de estos motores de plantillas se utiliza con la configuración predeterminada, las plantillas se
seleccionan automáticamente de src/main/resources/templates
.
Manejo de errores
De forma predeterminada, Spring Boot muestra /error
que maneja todos
los errores de manera adecuada y se registra como una página de error "global" en el contenedor de servlets. Para
los clientes de la máquina, genera una respuesta JSON con una descripción detallada del error, un código de estado
HTTP y un mensaje de excepción. Para los clientes de navegador, hay una vista de error de "etiqueta blanca" que
muestra los mismos datos en HTML (para personalizar esto, agregue una Vista
que permita
error
).
Hay una serie de propiedades server.error
que puede configurar si desea
personalizar la lógica de error predeterminada.
Para reemplazar completamente la lógica predeterminada, puede
implementar un ErrorController
y registrar una definición de bean de ese tipo, o agregar un bean de
tipo ErrorAttributes
para usar el mecanismo existente pero reemplazar el contenido.
BasicErrorController
se puede utilizar como clase base para un
ErrorController
personalizado. Esto es especialmente práctico si necesita agregar un controlador para
un nuevo tipo de contenido (el controlador predeterminado solo maneja text/html
y pasa un respaldo para
todo lo demás). Para hacer esto, extienda BasicErrorController
, agregue un método público con la
anotación @RequestMapping
que tenga el atributo produces
y cree un bean de un nuevo type.
Además, puede definir una clase anotada con @ControllerAdvice
para personalizar el documento JSON
devuelto para un controlador específico y/o tipo de excepción, como se muestra en el siguiente ejemplo. :
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;
}
}
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
}
}
En el ejemplo anterior, si MyException
es lanzada por un controlador definido en el mismo
paquete que SomeController
, en lugar de la vista ErrorAttributes
, la representación JSON
del objeto POJO MyErrorBody
se utiliza .
En algunos casos, los errores manejados en el nivel del controlador no son capturados por el marco de métricas. Las aplicaciones pueden garantizar que dichas excepciones se registren en las métricas de solicitud configurando la excepción manejada como un atributo de solicitud:
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";
}
}
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"
}
}
Páginas de error personalizadas
Si desea mostrar una página de error HTML personalizada para un
código de estado determinado, puede agregar el archivo al directorio /error
. Las páginas de error
pueden ser HTML estático (es decir, agregarse a cualquiera de los directorios de recursos estáticos) o crearse
mediante plantillas. El nombre del archivo debe ser el código de estado exacto o la máscara de serie.
Por
ejemplo, para mostrar un 404
con un archivo HTML estático, la estructura del directorio debería verse
así:
src/ +- main/ +- java/ | + <source code> +- resources/ +- public/ +- error/ | +- 404.html +- <other public assets>
Para mostrar todos los errores 5xx
usando la plantilla FreeMarker, la estructura del directorio
debe ser de la siguiente manera:
src/ +- main/ +- java/ | + <source code> +- resources/ +- templates/ +- error/ | +- 5xx.ftlh +- <other templates>
Para visualizaciones más complejas, también puede agregar beans que implementen la interfaz ErrorViewResolver
,
como se muestra en el siguiente ejemplo:
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) {
// Utilice la solicitud o el estado que desee para devolver ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// Aquí podemos agregar valores de modelo personalizados
new ModelAndView("myview");
}
return null;
}
}
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? {
// Utilice la solicitud o el estado que desee para devolver ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// Aquí podemos agregar valores de modelo personalizados
return ModelAndView("myview")
}
return null
}
}
También puede utilizar las funciones habituales de Spring MVC, como los métodos marcados con la anotación
@ExceptionHandler
y anotación de @ControllerAdvice.
. ErrorController
luego
detecta cualquier excepción no controlada.
Mostrar páginas de error fuera de Spring MVC
Para
aplicaciones que no usan Spring MVC, puede usar ErrorPageRegistrar
interfaz para el registro directo
de ErrorPages
. Esta abstracción funciona directamente con el contenedor de servlet incorporado
subyacente y funciona bien incluso si no tiene DispatcherServlet
para Spring MVC.
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"));
}
}
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
usando una ruta que finalmente se
procesa usando un Filter
(que es común en algunos marcos web que no son de Spring, como Jersey y
Wicket), el Filtro
debe registrarse explícitamente como un administrador ERROR
, como
se muestra en el siguiente ejemplo:
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;
}
}
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
}
}
Tenga en cuenta que FilterRegistrationBean
no contiene el tipo de administrador ERROR
de forma predeterminada.
Manejo de errores durante la implementación de WAR
Cuando se implementa en un contenedor de servlets, Spring Boot usa su filtro de página de error para redirigir una solicitud con un código de error a la página de error apropiada. Esto es necesario porque la especificación del servlet no proporciona una API para registrar páginas de error. Dependiendo del contenedor en el que implemente su archivo war y las tecnologías que utilice su aplicación, es posible que se requiera una configuración adicional.
El filtro de la página de error solo podrá redirigir la solicitud a la página de error correcta si la respuesta
aún no ha sido registrado. De forma predeterminada, WebSphere Application Server 8.0 y posteriores capturan la
respuesta después de que el método del servicio de servlet se completa correctamente. Debe deshabilitar esta
lógica configurando el parámetro com.ibm.ws.webcontainer.invokeFlushAfterService
en
false
.
Si está utilizando Spring Security y desea Para acceder al principal en la página de error, debe configurar el
filtro Spring Security para que se llame cuando se envíen errores. Para hacer esto, establezca la propiedad
spring.security.filter.dispatcher-types
en async, error, forward, request
.
Soporte CORS
El intercambio de recursos entre orígenes (CORS) es una especificación W3C implementada por la mayoría de los navegadores, lo que permite flexibilidad para determinar qué tipos de solicitudes entre dominios se autorizarán y tiene como objetivo reemplace algunos enfoques menos seguros y menos eficientes, como IFRAME o JSONP.
Desde la versión 4.2, Spring MVC admite CORS. El uso
de un método de controlador para la configuración de CORS con anotaciones @CrossOrigin
en una
aplicación Spring Boot no requiere ninguna configuración especial. La configuración global de CORS solo se puede
definir registrando un bean WebMvcConfigurer
con el método configurado
>addCorsMappings(CorsRegistry)
, como se muestra en el siguiente ejemplo:
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/**");
}
};
}
}
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/**")
}
}
}
}