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.

Dependiendo de cómo se esté ejecutando la aplicación, el IDE puede organizar el classpath de manera diferente. Ejecutar una aplicación en el IDE desde su método principal da como resultado un orden diferente que ejecutar la aplicación usando Maven o Gradle, o desde su archivo jar empaquetado. Esto puede provocar que Spring Boot no pueda encontrar el patrón esperado. Si esto es un problema, puede cambiar el orden de las rutas de clases en el IDE para que las clases y los recursos del módulo sean lo primero.

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. :

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

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:

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

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:

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) {
         // 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;
    }
}
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? {
        // 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.

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"))
    }
}
Si registra una 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:
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
    }
}

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:

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/**")
            }
        }
    }
}