[Primavera] Procesamiento de eventos unificado (interceptor, manejo de excepciones unificado, devolución de formato de datos unificado)


prefacio

En el desarrollo de aplicaciones web modernas, a menudo es necesario abordar muchos aspectos, como los derechos de usuario, las excepciones y los formatos de devolución de datos. El marco Spring nos proporciona herramientas y mecanismos poderosos para enfrentar estos desafíos. Este artículo se centrará en los interceptores, el manejo unificado de excepciones y el formato de devolución de datos unificado en el marco Spring.

1. Interceptor de resorte

En el desarrollo de aplicaciones web, los interceptores son un mecanismo muy útil que nos permite realizar algunas acciones específicas en diferentes etapas del procesamiento de solicitudes. Estas operaciones pueden ser verificación de permisos, registro, preprocesamiento de datos, etc. El contenido relacionado con el interceptor en el marco Spring se analizará en detalle a continuación.

1.1 Caso de verificación de la autoridad de inicio de sesión del usuario

Primero introduzca un caso de aplicación común:

  • Verificación del permiso de inicio de sesión del usuario. Por lo general, en una aplicación web, es necesario iniciar sesión en algunos recursos o funciones para poder acceder a ellos, por lo que es necesario verificar el estado de inicio de sesión del usuario. En ausencia de interceptores, podemos utilizar la forma tradicional de implementar esta lógica de validación. Veamos el método de autenticación de inicio de sesión del usuario inicial.

1.1.1 Verificación inicial de inicio de sesión del usuario

Antes de usar interceptores, revisemos el método inicial para usar la autenticación de inicio de sesión del usuario:

@RestController
public class UserController {
    
    

    // 最开始实现用户登录权限验证的方式

    @RequestMapping("/method1")
    public Object method1(HttpServletRequest request){
    
    
        // 获取Session,没有不创建
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute("userinfo") == null){
    
    
            // 没有获取到 session, 此时说明用户未登录
            return false;
        }

        // 此时则说明已经登录了,执行后续业务逻辑

        // ...

        return true;
    }

    @RequestMapping("/method2")
    public Object method2(HttpServletRequest request){
    
    
        // 获取Session,没有不创建
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute("userinfo") == null){
    
    
            // 没有获取到 session, 此时说明用户未登录
            return false;
        }

        // 此时则说明已经登录了,执行后续业务逻辑

        // ...

        return true;
    }

	// 其他方法...
}

Cuando se utiliza el método original para agregar la misma lógica de verificación de inicio de sesión a cada método que requiere verificación del permiso de inicio de sesión del usuario, causará algunos problemas:

  1. Repetición de código: es necesario copiar y pegar el mismo código de verificación en cada método que requiere verificación, lo que aumenta la redundancia del código y no favorece el mantenimiento ni la modificación.

  2. Mala legibilidad: se incluye la misma lógica de verificación en varios métodos, lo que reduce la legibilidad del código y dificulta la comprensión rápida de la función real de cada método.

  3. Mantenimiento difícil: si necesita modificar la lógica de verificación o agregar nuevas condiciones de verificación, debe modificarla en varios lugares, lo que es propenso a errores.

  4. Acoplamiento de código: incorporar la lógica de verificación directamente en cada método conduce a un estrecho acoplamiento entre la lógica empresarial y la lógica de verificación, lo que no favorece el desacoplamiento del código ni las pruebas unitarias.

  5. Mala escalabilidad: si necesita agregar más lógica de verificación o personalizar la lógica de verificación en el futuro, deberá modificar varios métodos, lo que aumenta la carga de trabajo y los riesgos.

1.1.2 El problema de usar Spring AOP para implementar la autenticación de inicio de sesión

Ante los problemas anteriores, es necesario considerar el uso de una autenticación de inicio de sesión de usuario unificada para resolverlos. Cuando se trata de verificación de inicio de sesión unificada, podemos pensar en usar la programación orientada a aspectos de Spring AOP para lograrlo, pero hay otros grandes problemas. Primero, revisemos la implementación del código Spring AOP:

// 创建一个切面(类)
@Aspect
@Component
public class UserAspect{
    
    
    // 创建切点(方法)定义拦截规则
    @Pointcut("execution(public * com.example.demo.controller.UserController.*(..))")
    public void pointcut() {
    
    
    }

    // 前置通知
    @Before("pointcut()")
    public void doBefore() {
    
    
        System.out.println("执行了前置通知:" + LocalDateTime.now());
    }

    // 后置通知
    @After("pointcut()")
    public void doAfter() {
    
    
        System.out.println("执行了后置通知:" + LocalDateTime.now());
    }

    // 返回后通知
    @AfterReturning("pointcut()")
    public void doAfterReturning() {
    
    
        System.out.println("执行了返回后通知:" + LocalDateTime.now());
    }

    // 抛异常后通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing() {
    
    
        System.out.println("抛异常后通知:" + LocalDateTime.now());
    }

    // 环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
    
    

        Object proceed = null;
        System.out.println("Around 方法开始执行:" + LocalDateTime.now());
        try {
    
    
            // 执行拦截的方法
            proceed = joinPoint.proceed();
        } catch (Throwable e) {
    
    
            e.printStackTrace();
        }
        System.out.println("Around 方法结束执行: " + LocalDateTime.now());
        return proceed;
    }
}

Si desea utilizar el método de notificación de Spring AOP anterior para implementar la verificación de inicio de sesión del usuario, existen dos problemas principales:

1. Obtener HttpSessionproblema de objeto

  • En AOP, el asesoramiento se realiza en el punto de conexión (punto de unión) que coincide con el punto de corte (Pointcut), pero estos puntos de conexión están en el nivel del método y no pueden proporcionar acceso directo a objetos relacionados como o HttpServletRequest. HttpSessionPor lo tanto, no se puede acceder directamente a los objetos en aspectos AOP HttpSession.

2. Problema de interceptación selectiva

  • En aplicaciones reales, es posible que solo necesitemos verificar los permisos de inicio de sesión para algunas funciones需要排除一些其他方法,比如用户的注册和登录 .
  • 切点表达式Es difícil lograr esto precisamente con métodos basados ​​en nombres de métodos, ya que esto puede implicar una coincidencia compleja de expresiones regulares y, si hay nuevos métodos que deben excluirse, la expresión de punto de corte debe actualizarse manualmente .

Para resolver estos problemas, el mecanismo interceptor de Spring es más adecuado para manejar tales situaciones. Los interceptores pueden acceder a objetos relacionados con solicitudes, como HttpServletRequesty HttpSession, y pueden configurar más fácilmente qué rutas deben interceptarse y cuáles no.

1.2 Uso de interceptores de resorte

1.2.1 Concepto y pasos de uso del interceptor Spring

El interceptor Spring es un mecanismo de interceptación en el marco Spring, que se utiliza para realizar operaciones específicas antes y después de que la solicitud ingrese al controlador. Los interceptores se pueden utilizar para implementar funciones como la verificación de permisos de inicio de sesión, el registro y el preprocesamiento de datos. Utilizando la idea de programación tangencial (AOP), el interceptor puede insertar su propia lógica en diferentes etapas de la solicitud para lograr varias funciones .

Estos son los pasos generales para usar interceptores Spring:

1. Crea una clase interceptora

Primero, cree una clase que implemente la interfaz de Spring HandlerInterceptory anule los métodos en ella. Por lo general, incluye los siguientes tres métodos:

  • preHandle: Se ejecuta antes de que se procese la solicitud y se puede utilizar para la verificación de permisos y otras operaciones. Si se devuelve, falsese interrumpe el procesamiento de la solicitud.
  • postHandle: Se ejecuta después de procesar la solicitud, pero antes de que se represente la vista. Se pueden realizar operaciones como el registro.
  • afterCompletion: Se ejecuta después de renderizar la vista y se pueden realizar algunas operaciones, como la limpieza de recursos.

2. Agregar y configurar interceptores

Configurar y agregar interceptores en Spring Boot se puede dividir aproximadamente en los dos pasos siguientes:

1) Agregue la clase de interceptor creada al contenedor Spring: en Spring Boot, puede usar @Componentanotaciones para marcar la clase de interceptor creada y entregarla al contenedor Spring para su administración. De esta manera, Spring Boot escaneará e incluirá automáticamente esta clase de interceptor en el contexto de la aplicación.

Por ejemplo, agregue anotaciones en la clase interceptora @Componentde la siguiente manera:

import org.springframework.stereotype.Component;

@Component
public class AuthInterceptor implements HandlerInterceptor {
    
    
    // 拦截器的具体实现
}

2) Implemente WebMvcConfigurerla interfaz y reescriba addInterceptorsel método para agregar el interceptor a la configuración del sistema: en Spring Boot, puede implementar WebMvcConfigurerla interfaz y reescribir addInterceptorsel método para agregar el interceptor. Este método agregará el interceptor creado al sistema para que entre en vigor cuando se procese la solicitud.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    

    @Autowired
    private AuthInterceptor authInterceptor; // 注入你的拦截器类

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        // 添加拦截器,并指定拦截的路径
        registry.addInterceptor(authInterceptor)
        		.addPathPatterns("/**")
        		.excludePathPatterns("/user/login")
        		.excludePathPatterns("/user/reg")
        		;
    }
}

En el código anterior, addInterceptorsel método agregará la clase de interceptor authInterceptora la configuración del sistema, addPathPatternsespecificará la ruta a través de la cual se interceptará y luego usará excludePathPatternsel método para especificar la interfaz especificada que abandona la interceptación, como las funciones de inicio de sesión y registro. De esta forma, cuando el programa se esté ejecutando, el interceptor configurado entrará en vigor automáticamente.

3. Configurar el orden de los interceptores
Si se utilizan varios interceptores al mismo tiempo, su orden de ejecución se puede definir mediante la configuración. En Spring MVC, los interceptores se ejecutan en el mismo orden en que se declaran en el archivo de configuración.
4. Uso de interceptores
Después de configurar los interceptores, ejecutarán la lógica correspondiente antes, después o después de que se procese la solicitud en la fase de renderizado. De esta manera, se pueden implementar las funciones requeridas en el interceptor, como verificación de permisos, registro, etc.

1.2.2 Utilice interceptores para verificar los permisos de inicio de sesión del usuario

1. Cree un interceptor para la verificación de inicio de sesión del usuario:

/**
 * Spring MVC拦截器(LoginInterceptor),用于检查用户是否已登录。
 * 如果用户未登录,则重定向到登录页面。
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
    
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        // 判断用户登录
        HttpSession session = request.getSession(false); // 默认值是true
        if (session != null && session.getAttribute(ApplicationVariable.SESSION_KEY_USERINFO) != null) {
    
    
            // 用户已经登录了
            return true;
        }

        // 当代码执行到此次,表示用户未登录
        response.sendRedirect("/login.html");
        return false;
    }
}

2. Agregar y configurar reglas de interceptación

/**
 * 配置拦截规则
 */
@Configuration
public class MyConfig implements WebMvcConfigurer {
    
    

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") // 拦截所有的 url
                // 放开拦截的内容
                .excludePathPatterns("/**/*.js")
				.excludePathPatterns("/**/*.css")
				.excludePathPatterns("/**/*.jpg")
				.excludePathPatterns("/login.html")
                .excludePathPatterns("/user/reg")
                .excludePathPatterns("/user/login")
                // ...
        ;
    }
}

1.3 Principio de implementación del interceptor

El principio de implementación del interceptor implica el concepto central del marco Spring: programación orientada a aspectos (AOP). Con la ayuda de AOP, los interceptores se pueden integrar en cadenas de llamadas a métodos para realizar las acciones correspondientes en diferentes etapas del procesamiento de solicitudes. A continuación se presentará el principio de implementación y el flujo de trabajo del interceptor.

1. Revisión del concepto de AOP

AOP es un paradigma de programación diseñado para resolver el problema de las preocupaciones transversales (Cross-cutting Concerns). Nos permite gestionar y mantener mejor el código al separar las preocupaciones transversales de la lógica empresarial principal. En Spring, AOP se implementa mediante modo proxy y proxy dinámico.

En AOP, hay dos conceptos importantes: Pointcut y Advice.

  • Pointcut: Un pointcut define sobre qué puntos de unión (Join Points) aplicar consejos. Un punto de unión puede ser una llamada a un método, una ejecución de un método, un acceso a campos, etc. Los puntos de corte utilizan expresiones para hacer coincidir los puntos de unión y determinar qué puntos de unión se ven afectados por el consejo.

  • Consejo: El consejo define el código que se ejecutará en el punto de corte. En Spring, hay varios tipos de consejos, incluidos los consejos previos (que se ejecutan antes de la invocación del método), los consejos posteriores (que se ejecutan después de la invocación del método), los consejos envolventes (que se ejecutan antes y después de la invocación del método), los consejos de excepción (que se ejecutan después de que se lanza el método). cuando ocurre una excepción), notificación final (ejecutada después de que finaliza la llamada al método).

2. El flujo de trabajo del interceptor.

El interceptor es en realidad una aplicación de AOP, que realiza la intervención en el proceso de procesamiento de solicitudes a través de AOP. El siguiente es el flujo de trabajo del interceptor:

  1. Cuando llega una solicitud a DispatcherServlet (controlador frontal), el interceptor primero juzgará si intercepta la solicitud de acuerdo con las reglas de interceptación configuradas (punto de corte).

  2. Si el interceptor decide interceptar la solicitud, ejecutará la lógica de la notificación antes del punto de corte, como la notificación previa. De esta manera, el interceptor puede realizar algunas operaciones de preprocesamiento, como verificación de permisos, registro, etc., antes del procesamiento de la solicitud.

  3. A continuación, la solicitud seguirá pasándose al responsable del tratamiento correspondiente para su tramitación. El interceptor no interrumpe el flujo de la solicitud, simplemente inserta su propia lógica antes de procesarla.

  4. Una vez que el controlador termina de procesar la solicitud, el interceptor ejecutará nuevamente la lógica de notificación, como la notificación posterior. De esta manera, el interceptor puede realizar algunas operaciones posteriores después del procesamiento de la solicitud, como encapsulación de datos, registro, etc.

  5. Finalmente, el interceptor ejecutará la lógica de la notificación después del punto de corte, como la notificación final. De esta manera, el interceptor puede realizar algunas operaciones de limpieza una vez que se completa el procesamiento de la solicitud.

A través de interceptores, podemos intervenir en diferentes etapas del procesamiento de solicitudes, para lograr algunas preocupaciones transversales. Este mecanismo nos permite gestionar y mantener mejor el código y mejora la capacidad de mantenimiento y reutilización del código.

1.4 La diferencia entre Spring interceptor y Spring AOP

Spring Interceptors y Spring AOP (Programación orientada a aspectos), dos mecanismos utilizados en Spring Framework para manejar inquietudes transversales, son algo similares pero tienen algunas diferencias clave. Estas son las principales diferencias entre ellos:

1. Diferentes áreas objetivo

  • Interceptor de primavera: se utiliza principalmente para procesar solicitudes web. Realiza operaciones específicas en diferentes etapas del procesamiento de solicitudes, como verificación de permisos, registro, etc. Los interceptores se ocupan principalmente de intervenir en el flujo de procesamiento de solicitudes web e insertar lógica personalizada antes y después de la solicitud.

  • Spring AOP: para manejar inquietudes transversales en aplicaciones, no solo limitadas a solicitudes web. AOP puede realizar notificaciones en diferentes puntos de conexión, como llamadas a métodos, ejecución de métodos y acceso a campos, para realizar algunas funciones desacopladas de la lógica empresarial principal, como gestión de transacciones, monitoreo del rendimiento, etc.

2. El ámbito de aplicación es diferente.

  • Interceptor de primavera: se utiliza principalmente en la capa web para procesar solicitudes web. Puede controlar el flujo de procesamiento de la solicitud, pero solo actúa en la capa web y no afectará la llamada al método de la capa empresarial.

  • Spring AOP: se puede aplicar a múltiples niveles, incluida la capa empresarial, la capa de persistencia, la capa web, etc. AOP puede abarcar diferentes capas y realizar el procesamiento de preocupaciones comunes entre diferentes capas interceptando puntos de conexión.

3. Diferentes métodos de procesamiento

  • Spring Interceptor: el interceptor es un mecanismo basado en el proxy dinámico de Java, que interviene en la solicitud entrelazándola en el proceso de procesamiento de la solicitud. El interceptor se centra principalmente en los enlaces frontal y posterior del procesamiento de solicitudes y puede realizar operaciones como preprocesamiento, posprocesamiento y liberación de recursos.

  • Spring AOP: AOP se implementa a través del modo proxy, que puede utilizar el proxy dinámico JDK o CGLIB para generar objetos proxy. AOP puede realizar notificaciones en puntos de conexión antes y después de las llamadas a métodos, cuando se lanzan excepciones y al final de la ejecución del método, para lograr diferentes tipos de preocupaciones transversales.

4. Principio y flexibilidad

  • Interceptor Spring: el interceptor es un mecanismo proporcionado por Spring MVC, que está más dirigido al procesamiento de la capa web. Tiene cierta flexibilidad, pero es relativamente limitado a la hora de abordar preocupaciones comunes entre capas.

  • Spring AOP: AOP es una de las características principales del marco Spring y se puede aplicar en múltiples niveles, incluido el procesamiento de varios puntos de conexión, como llamadas a métodos y acceso a campos. AOP es más general y adecuado para manejar diversas preocupaciones transversales.

En resumen, tanto los interceptores Spring como Spring AOP son mecanismos importantes para abordar preocupaciones transversales, pero existen algunas diferencias en el alcance de la aplicación, los métodos de procesamiento y la flexibilidad. La elección del mecanismo apropiado depende de los requisitos reales y del nivel en el que deben abordarse las preocupaciones transversales.

2. Manejo unificado de excepciones

2.1 ¿Por qué unificar el manejo de excepciones?

Durante el proceso de desarrollo, la aplicación inevitablemente encontrará varias situaciones anormales, como fallas en la conexión de la base de datos, excepciones de puntero nulo, etc. Para proporcionar una mejor experiencia de usuario y una mejor gestión de mensajes de error, es necesario adoptar un mecanismo unificado de manejo de excepciones. A continuación se presentan algunas razones por las que debería adoptar el manejo unificado de excepciones:

  1. Experiencia de usuario amigable: el manejo unificado de excepciones puede detectar varias excepciones y devolver una respuesta de error unificada. De esta manera, los usuarios pueden obtener mensajes de error más amigables y comprensibles sin exponer directamente los detalles del error dentro de la aplicación.

  2. Reducir la duplicación de código: en una aplicación, el mismo tipo de excepción puede ocurrir en varios lugares. Al unificar el manejo de excepciones, se puede extraer la misma lógica de manejo de errores en un solo lugar, lo que reduce la duplicación de código y mejora la capacidad de mantenimiento del código.

  3. Gestión de errores centralizada: la gestión de excepciones unificada puede gestionar de forma centralizada la información de errores para facilitar el registro, la supervisión y el análisis de errores. Esto ayuda a localizar rápidamente problemas y mejorar la estabilidad de la aplicación.

2.2 Uso del manejo unificado de excepciones

En el marco Spring, puede utilizar anotaciones @ControllerAdvicey @ExceptionHandlerpara lograr un manejo unificado de excepciones.

Proceder de la siguiente:

  1. Cree una clase de manejo de excepciones y @ControllerAdvicemárquela con anotaciones para indicar que es una clase de notificación de controlador para manejar excepciones globales.

  2. En la clase de manejo de excepciones, use @ExceptionHandleranotaciones para definir métodos de manejo de excepciones. Puede escribir diferentes controladores para diferentes tipos de excepciones o utilizar un método común para manejar todas las excepciones.

  3. En el método de manejo de excepciones, puede personalizar el mensaje de error, crear la respuesta de error y devolver una vista adecuada o una respuesta JSON.

A continuación se muestra un código de muestra que muestra cómo utilizar el manejo unificado de excepciones:


/**
 * 统一异常处理
 */
@ControllerAdvice
@ResponseBody
public class MyExceptionAdvice {
    
    

    /**
     * 处理空指针异常
     * @param e
     * @return
     */
    @ExceptionHandler(NullPointerException.class)
    public HashMap<String, Object> doNullPointerException(NullPointerException e){
    
    
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", -300);
        result.put("msg", "空指针: " + e.getMessage());
        result.put("data", null);
        return result;
    }

    /**
     * 默认异常处理(当具体的异常处匹配不到时,会执行此方法)
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public HashMap<String, Object> doException(Exception e){
    
    
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", -300);
        result.put("msg", "Exception: " + e.getMessage());
        result.put("data", null);
        return result;
    }

}

En este ejemplo de código, se crea una MyExceptionAdviceclase de manejo de excepciones global denominada @ControllerAdvicey se marca con una anotación. Esta clase contiene dos métodos de manejo de excepciones:

  1. doNullPointerExceptionmétodo para manejar excepciones de puntero nulo ( NullPointerException). Cuando la aplicación arroja una excepción de puntero nulo, se llamará a este método y luego se construirá un archivo que contenga información de error HashMapy se devolverá.

  2. doExceptionmétodo para manejar otros tipos de excepciones. Si el tipo de excepción lanzada por la aplicación no coincide con el método de manejo definido previamente, se ejecutará este método de manejo de excepciones predeterminado. También construye una respuesta de error y la devuelve.

De esta manera, se realiza un procesamiento personalizado para diferentes tipos de excepciones y se proporciona un formato de respuesta de error unificado, mejorando así la experiencia del usuario y la capacidad de mantenimiento del código.

3. Formato de devolución de datos unificado

3.1 ¿Por qué es necesario unificar el formato de devolución de datos?

Durante el desarrollo, diferentes interfaces pueden devolver diferentes formatos de datos, lo que puede hacer que el front-end realice diferentes lógicas de procesamiento de acuerdo con los formatos de retorno de diferentes interfaces al procesar datos, lo que aumenta la complejidad del código y los costos de mantenimiento. Para simplificar la lógica de procesamiento de front-end y mejorar la capacidad de mantenimiento del código, puede considerar unificar el formato de devolución de datos.

Las ventajas de un formato de devolución de datos unificado incluyen:

  1. Reduzca la complejidad de la lógica del front-end: el front-end no necesita escribir una lógica de procesamiento diferente para diferentes interfaces y puede procesar formatos de datos de manera uniforme para reducir la complejidad del código.

  2. Mejore la eficiencia de la colaboración de front-end y back-end: el front-end y el back-end pueden implementar el desarrollo de interfaces y la depuración conjunta más rápido y reducir los costos de comunicación a través de acuerdos de formato de datos claros.

  3. Manejo de errores estandarizado: el formato de devolución de datos unificado puede estandarizar el método de manejo de errores, lo que facilita que la interfaz juzgue si la solicitud fue exitosa y obtenga información de error.

3.2 Implementación del formato de devolución de datos unificado

Al regresar en un formato de datos unificado, se utilizan principalmente ResponseBodyAdvicela interfaz y las anotaciones proporcionadas por el marco Spring, y los métodos @ControllerAdviceen la interfaz deben reescribirse . El método indica si se debe usar un formato de datos unificado para regresar, y es para darse cuenta la unificación del formato de datos.supportsbeforeBodyWritesupportsbeforeBodyWrite

El siguiente es un ejemplo que muestra cómo implementar un formato de devolución de datos unificado, que estipula que el formato de datos devuelto es:

{
    
    
	"cede":code,
	"msg":msg,
	"data":data
}

Se puede utilizar para organizarse al regresar HashMap. El siguiente es el código de implementación específico:

/**
 * 统一数据格式处理
 */
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    
    

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
    
    
        return true;
    }

    /**
     * 返回数据之前进行处理
     */
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    
    
        //规定标准格式为:HashMap<String, Object> -> code,msg.data

        if (body instanceof HashMap) {
    
    
            // 如果已经是标准格式
            return body;
        }

        // 重写返回结果,让其返回一个统一的数据格式
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", body);
        result.put("msg", "");

        return result;
    }
}

El código de muestra anterior demuestra claramente cómo utilizar @ControllerAdviceanotaciones e ResponseBodyAdviceinterfaces para lograr un formato de devolución de datos unificado. Al reescribir los métodos supportsy beforeBodyWrite, se logra el procesamiento unificado del formato de datos devuelto.

En este ejemplo, se especifica un formato de datos unificado, es decir, el JSON devuelto incluye los campos de "código", "mensaje" y "datos". Si los datos devueltos ya están en el formato estándar ( HashMap<String, Object>), devuélvalos directamente; de ​​lo contrario, cree un objeto de devolución en el formato estándar y coloque los datos originales en él.

3.3 Para el problema de que el cuerpo es un error tipo String

Cuando se trata de tipos de datos primitivos, por ejemplo:

@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @RequestMapping("/login")
    public int login() {
    
    
        return 1;
    }
}

Resultados del acceso al navegador:

en este momento, no se encontró ningún problema y la devolución unificada del formato de datos se realizó con éxito. Pero ¿qué pasa si el tipo de retorno es String?


@RestController
@RequestMapping("/user")
public class UserController {
    
    
	@RequestMapping("/hello")
	public String hello(){
    
    
	    return "hello world";
	}
}

Visita nuevamente a través del navegador:

1. Análisis de la causa del error del tipo de cadena de retorno:

El contenido del error es aproximadamente que HashMapel tipo no se puede convertir en Stringun tipo. ¿Por qué esto es tan? Primero, debe comprender el flujo de ejecución de los datos durante el proceso de devolución :

  1. El método del controlador devuelve el tipo String;
  2. Realice un procesamiento de formato de datos unificado para el tipo String, es decir, establezca el como del tipo String en bodyel de ;valuekeydataHashMap
  3. Conviértalo HashMapen application/jsonuna cadena y envíelo a través de la red al front-end.

Cabe resaltar que:

  • Entre ellos, si bodyel tipo es String, se utilizará StringHttpMessageConverterpara la conversión de tipo;
  • De lo contrario, utilícelo HttpMessageConverterpara la conversión de tipos.

Pero el uso es para el tercer paso. Como bodyes de tipo String, utiliza StringHttpMessageConverteruna clase que solo puede convertir datos de tipo String en una cadena JSON, y HashMapse producirá un error si se convierte.

2. Solución:

Las soluciones se pueden dividir a grandes rasgos en dos tipos:

  1. Solución 1: si bodyel tipo es String, devuelva el tipo String directamente. Puede usar la concatenación de cadenas o usar jacksonObjectMapper para convertirlo al formato JSON.
    1) Empalmar para formar una Cadena para retorno:
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    
    

    if (body instanceof String) {
    
    
        // 返回一个 String 字符串
        return "{\"code\" : 200, \"msg\": \"\", \"data\":\"" + body + "\"}";
    }

    if (body instanceof HashMap) {
    
    
        // 如果已经是标准格式
        return body;
    }

    // 重写返回结果,让其返回一个统一的数据格式
    HashMap<String, Object> result = new HashMap<>();
    result.put("code", 200);
    result.put("data", body);
    result.put("msg", "");
    return result;
}

2) Utilizando jacksonen ObjectMapper:

@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    
    

    if (body instanceof HashMap) {
    
    
        // 如果已经是标准格式
        return body;
    }

    // 重写返回结果,让其返回一个统一的数据格式
    HashMap<String, Object> result = new HashMap<>();
    result.put("code", 200);
    result.put("data", body);
    result.put("msg", "");

    if(body instanceof String){
    
    
        // 返回一个 String 字符串
        return objectMapper.writeValueAsString(result);
    }
    return result;
}

2. Solución 2: deshabilite la clase directamente StringHttpMessageConverter.

configureMessageConvertersSe puede desactivar anulando el método en la clase de configuración StringHttpMessageConverter:

@Configuration
public class MyConfig implements WebMvcConfigurer {
    
    
    /**
     * 移除 StringHttpMessageConverter
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    
    
        converters.removeIf(converter -> converter instanceof StringHttpMessageConverter);
    }
}

donde converter -> converter instanceof StringHttpMessageConverterhay una Lambdaexpresión que comprueba convertersi es StringHttpMessageConverteruna instancia de . En caso afirmativo, elimine la instancia.

Después de seleccionar cualquier solución, visite el método del controlador que devuelve el tipo String nuevamente y el resultado es el siguiente:


En este punto, el formato de datos unificado se puede devolver correctamente.

Supongo que te gusta

Origin blog.csdn.net/qq_61635026/article/details/132207168
Recomendado
Clasificación