[SpringBoot] Un análisis vernáculo del código fuente del mecanismo de manejo de errores predeterminado y la configuración personalizada (en caso de un pozo)

Prefacio

Versión de SpringBoot: v2.2.6.RELEASE

Este blog resume principalmente los siguientes 3 aspectos:

  • Uno, página de error SpringBoot y datos
  • 2. Introducción de clases relacionadas
  • Tres, seguimiento del código fuente, el flujo del mecanismo de procesamiento predeterminado
  • Cuatro, datos de error personalizados

Desde la perspectiva de los aprendices, este blog presenta toda la jerarquía e ideas de forma clara y clara con vernáculos populares, imágenes y textos.Creo que si terminas de leerlo con paciencia, deberías tener una cosecha diferente. El código fuente de cualquier marco es un sistema enorme. Leer el código fuente no requiere una comprensión exacta de cada detalle del código, pero debe prestar atención y comprender la idea principal del código principal (clave). Finalmente, ¿por qué no me gusta la colección después de leerla? Pasé la mayor parte del día resumiendo y siento que he ganado mucho y tengo una comprensión más clara y profunda de SpringBoot.

Uno, página de error SpringBoot y datos

1. El visitante es un navegador

2. El visitante es otro cliente (aquí, utilizo Postman para probar)

3. ¿Cómo distinguir a los visitantes? Solicitud de encabezado!

El visitante es un navegador:

El visitante es otro cliente:

Desde el punto de vista anterior, podemos dividir el manejo de errores de SpringBoot en dos categorías:

(1) El visitante es un navegador y se devuelve una página de error correspondiente.

(2) Si el visitante es un cliente que no es el navegador, Duan devolverá los datos Json correspondientes.

 

2. Introducción de clases relacionadas

Para las clases clave aquí, solo una breve introducción, solo necesita conocer el papel principal que desempeña esta clase en todo el mecanismo predeterminado de manejo de errores. En la cuarta parte de la descripción del proceso, diseñaremos estas clases en profundidad.

ErrorMvcAutoConfiguration : como su nombre indica, esta es una clase de configuración automática del mecanismo de manejo de errores

Al observar el código fuente, se encuentra que esta clase agrega los siguientes 3 componentes importantes relacionados con el mecanismo de manejo de errores al contenedor:

 

1. ErrorPageCustomizer : personalice las reglas de generación de errores y genere solicitudes de error

2. BasicErrorController : como sugiere el nombre, es un controlador que procesa / solicita errores

(1) Regrese a la página Html

(2) Devolver datos Json

3. DefaultErrorViewResolver : resolución de la vista de error

4. DefaultErrorAttributes : comparte información sobre errores

 

Tres, seguimiento del código fuente, el flujo del mecanismo de procesamiento predeterminado

Tome la página HTML de respuesta como ejemplo

1. Como puede ver en la introducción anterior, cuando se devuelve una página de error, se llamará a este método en BasicErrorController (consulte la figura siguiente).

En este método, obtenga el código de estado solicitado y algunos datos del modelo (el método getErrorAttributes () también es muy importante, que se presentará más adelante), y luego llame al método resolveErrorView () para resolver la vista y devolver la vista de error correspondiente ModelAndView contiene la dirección de la página de error y el contenido de la página.

Haga clic en el método this.resolveErrorView () y vaya a la clase AbstractErrorController, como se muestra en la siguiente figura.

resolveErrorView (), como su nombre lo indica: ¡resolver vista de error! El significado general de este método es obtener todos los solucionadores de vista de error (ErrorViewResolver) y luego recorrerlos uno por uno, siempre que se obtenga un ModelAndView (que coincida con la vista de error correspondiente), regresará inmediatamente.

 

2. Preste atención al código cerca de la línea horizontal en la captura de pantalla anterior.

¿Qué es ErrorViewResolver? Haga clic para ver que es una interfaz. El resolveErrorView () llamado por la parte discontinua también es un método en esta interfaz. Coloque el mouse cerca de "ErrorViewResolver" y luego presione Ctrl + H para ver su arquitectura. Descubrí que la interfaz tiene una clase de implementación: DefaultErrorViewResolver

Te es familiar DefaultErrorViewResolver es un componente mencionado en la introducción anterior. Luego ingresamos DefaultErrorViewResolver para ver cómo resuelve la vista de error (la captura de pantalla a continuación).

¿Qué significan estos 3 métodos? Vea los comentarios del código a continuación.

public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, 
                                        Map<String, Object> model) {
        // 根据状态码精确匹配错误页面
	ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);

	if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
                // 如果无法找到准确状态码的错误页面,就模糊匹配:4xx(客户端错误),5xx(服务端错误)
		modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
	}

	return modelAndView;
}

private ModelAndView resolve(String viewName, Map<String, Object> model) {
	// 从上面的方法可以看出来,这里传入的viewName实际上就是状态码
	// 拼接视图名称,例如:error/404
	String errorViewName = "error/" + viewName;
	
	// 模板引擎可以解析这个页面地址就用模板引擎解析
	TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
			.getProvider(errorViewName, this.applicationContext);
								
	
	
	return provider != null ? 
        // 模板引擎可用的情况下返回到errorViewName指定的视图地址
                new ModelAndView(errorViewName, model) 
        // 否则就在静态资源文件夹下找errorViewName对应的页面,例如:error/404.html
                : this.resolveResource(errorViewName, model);
}

 

3. Siga el hilo del pensamiento y llegue aquí. De hecho, ¡podemos saber claramente cómo personalizar nuestra propia página de error! Aquí lo mencionaré en la línea de pensamiento, y lo resumiré más adelante.

Por lo anterior, sabemos que siempre que creemos una nueva carpeta de error en la carpeta estática o de plantillas en recursos, y luego coloquemos la página Html correspondiente al código de estado (nombrada después del código de estado, por ejemplo: 404.html) en la carpeta de errores. O simplemente coloque 4xx.html, 5xx.html, de modo que todos los errores del cliente usen la página 4xx.html y los errores del servidor usen la página 5xx.html.

Sin embargo, del análisis de código anterior, también podemos saber que si 404.html y 4xx.html existen al mismo tiempo, SpringBoot dará prioridad a 404.html.

 4. Hasta lo anterior, sabemos cómo cambiar la página estática correspondiente al estado de error. ¿Pero qué pasa si necesito recuperar el mensaje de error de la página? Por ejemplo, quiero recuperar la marca de tiempo, el código de estado, la información de excepción, etc. de este error. ¿Cómo sacamos esta información y la mostramos en nuestra propia página estática?

¿Esta información le resulta familiar? Sí, cuando el visitante no es un navegador, los datos Json devueltos por SpringBoot incluyen esta información. Luego, en la página de error predeterminada de SpringBoot, también se muestra información relacionada. Entonces, cuando personalizamos la página de error nosotros mismos, SpringBoot también debe proporcionarnos estos datos para que los usemos. ¡Incluso podemos agregar información adicional!

Con respecto a este tema, ¿dónde está el punto de entrada para el código fuente? De hecho, también se mencionó cuando se mencionó BasicErrorController al principio (consulte la figura siguiente). Esta vez prestamos atención al método getErrorAttributes (), y puedes adivinar lo que significa mirando el nombre. Clickea en.

Vaya a la clase base AbstractErrorController de BasicErrorController (consulte la figura siguiente) y luego haga clic para ingresar (getErrorAttributes () en la parte subrayada de la figura siguiente).

Acceda a la interfaz de ErrorAttributes. Presionamos Ctrl + H para ver la arquitectura de esta interfaz.

Efectivamente, tiene una clase de implementación: DefaultErrorAttributes. ¿No te resulta familiar? También es uno de los 4 componentes clave mencionados anteriormente. ¡Atención aquí! ! ! Hay dos DefaultErrorAttributes con el mismo nombre en SpringBoot. Lo que queremos ver está en el paquete org.springframework.boot.web.servlet.error. ¡No cometer errores! ! !

 

5. Acceda a DefaultErrorAttributes y vea cómo comparte información de error y cómo compartir información de error.

Al observar esta clase, encontramos: La siguiente información se almacena en el mapa:

timestamp: marca de tiempo

estado: código de estado

error: mensaje de error

excepción: objeto de excepción

mensaje: mensaje de excepción

errores: los errores de verificación de datos JSR303 están aquí

seguimiento: información de la pila

En realidad, esta información se puede recuperar en forma de variables de la página a través de la etiqueta thymeleaf. Los ejemplos son los siguientes:

Preste atención a un pequeño problema aquí: la versión de SpringBoot que estoy usando no tiene forma de recuperar la información de excepción por defecto. El motivo se puede conocer a partir del código fuente (no entraré en él aquí, publicaré una imagen como punto de entrada, puede seguir el código fuente usted mismo), para resolver este problema, solo necesita agregar lo siguiente configuración en el archivo de configuración: server.error. include-exception = true

la razón:

Cuatro, datos de error personalizados

Hasta ahora, sabemos:

(1) Cómo personalizar su propia página de error

(2) Cómo utilizar la información de error existente en la página de error personalizada

(3) Comprender el flujo del mecanismo de manejo de errores predeterminado de SpringBoot

Creo que tiene una comprensión más profunda de las categorías clave mencionadas en "II". Entonces, ahora también quiero personalizar los datos de error. En otras palabras, quiero compartir la información incorrecta que quiero. Este mensaje de error está personalizado por mí, lo agregué al dominio compartido y luego lo usé en mi página de error.

Después de que ocurra un error, llegará a la solicitud / error, que será procesada por BasicErrorController. Los datos de error que se pueden obtener en respuesta se obtienen mediante getErrorAttributes (el método especificado por AbstractErrorController (ErrorController)). Si queremos agregar información de error personalizada, podemos escribir una clase de implementación de ErrorController [o una subclase de AbstractErrorController], ponerla en el contenedor y reemplazar completamente el componente DefaultErrorAttributes de SpringBoot.

el código se muestra a continuación:

package com.ysq.springbootweb.component;


import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.util.Map;

/**
 *
 * @author passerbyYSQ
 * @create 2020-07-29 16:16
 */
@Component // 给容器中加入我们自己的ErrorAttributes
public class MyErrorAttributes extends DefaultErrorAttributes {

    //  如果不复写,Exception又获取不到了
    public MyErrorAttributes() {
        super(true);
    }

    /**
     * 这里返回的Map,就是页面和Json数据能获取到的所有字段
     * @param webRequest
     * @param includeStackTrace
     * @return
     */
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        // 额外信息
        map.put("company", "ysq");

        return map;
    }

}

Aquí encontraremos dos problemas:

(1) Si el método de construcción del parámetro nulo no se sobrescribe, la versión actual de SpringBoot no obtendrá información de excepción. Con respecto a la adquisición de Exception, mencioné anteriormente. Pero, ¿cuál es la situación ahora?

Originalmente, SpringBoot usaba DefaultErrorAttributes y DefaultErrorAttributes no colocaba información de excepción en el dominio compartido de forma predeterminada. Lo que hemos modificado previamente a través del archivo de configuración es permitir que DefaultErrorAttributes coloque información de excepción en el dominio compartido. Hoy, reemplazamos completamente el componente DefaultErrorAttributes escribiendo una subclase de DefaultErrorAttributes y poniéndola en el contenedor. SpringBoot no usará DefaultErrorAttributes, sino que usará completamente nuestro componente personalizado MyErrorAttributes. Por tanto, la configuración del archivo de configuración no es válida. Por lo tanto, podemos anular directamente la estructura de parámetros nulos de la clase principal de MyErrorAttributes, lo que permite obtener información de excepción.

Esta pregunta es un episodio. La siguiente pregunta (2) es el foco.

(2) Escriba MyErrorAttributes directamente, anule el método principal getErrorAttributes y agregue información de error personalizada al mapa antes de que el método devuelva el resultado. Esto conduce a un problema: no importa cuál sea el error, se agregará el mismo mensaje de error. Quiero agregar información específica para diferentes errores.

Por ejemplo: un error 500 causado por una excepción personalizada, agregue su información única para que acceda la página de error.

¿Cómo solucionar esta demanda? Ver código a continuación

package com.ysq.springbootweb.controller;

import com.ysq.springbootweb.exception.UserNotExistException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @author passerbyYSQ
 * @create 2020-07-29 15:47
 */
@ControllerAdvice // 在SpringMVC要成为异常处理器,需要增加此注解
public class MyExceptionHandler {

    // 只要发生UserNotExistException异常,SpringMVC就调用该方法,并将异常对象对传递进来
    @ExceptionHandler(UserNotExistException.class) 
    public String handleException(Exception e, HttpServletRequest request) {

        Map<String, Object> map = new HashMap<>();

        // 需要传入我们自己的错误状态
        // 如果不传,就默认200(成功),就不会进入自己定制的错误页面的解析流程
        // 虽然有定制的页面效果了,但是我定制的Map数据不生效
        request.setAttribute("javax.servlet.error.status_code", 500);

        // 自定义的错误信息
        map.put("code", "user not exist");
        map.put("message", "用户不存在!!!!");
        // 将额外的信息添加到请求域中,以供MyErrorAttributes取出
        request.setAttribute("ext", map);

        // 转发到/error请求,让BasicErrorController处理
        return "forward:/error"; 
    }
}

Al mismo tiempo, realice los siguientes cambios en MyErrorAttributes:

package com.ysq.springbootweb.component;


import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.util.Map;

/**
 *
 * @author passerbyYSQ
 * @create 2020-07-29 16:16
 */
@Component // 给容器中加入我们自己的ErrorAttributes
public class MyErrorAttributes extends DefaultErrorAttributes {

    //  如果不复写,Exception又获取不到了
    public MyErrorAttributes() {
        super(true);
    }

    /**
     * 这里返回的Map,就是页面和Json数据能获取到的所有字段
     * @param webRequest
     * @param includeStackTrace
     * @return
     */
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        // 额外信息
        map.put("company", "ysq");

        // 针对UserNotExistException异常的错误信息
        Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
        if (ext != null) {
            map.put("ext", ext);
        }

        return map;
    }

}

Mi efecto de página:

Mira a los amigos aquí, no olvides darles me gusta y coleccionarlos. ¡Gracias!

Supongo que te gusta

Origin blog.csdn.net/qq_43290318/article/details/107669722
Recomendado
Clasificación